对图像数据进行加密后保存
Purpose
The purpose of the project is to show how to encrypt/decrypt BMP and JPEG image files.
Background
The image encryption/decryption technique described here will be "good enough" for many business applications, but would not be comparable to techniques used by the U.S. Government's National Security Agency. There are many ways to improve on the encryption technique used here. Here are some other sources for information about encryption:
Cryptography section of efg's Math Reference Library page
Cryptography and Multiple-Precision Arithmetic page of the efg's Delphi Math Functions pages
Book: Cryptography: A Primer
Book: Applied Cryptography
Book: Handbook of Applied Cryptography
There are many ways also to improve on the efficiency of the technique described below. The focus of this page is to explain the technique, not optimize it.
One simple way to encrypt a string of character data or binary data is to form a "random" string of bits as long as the original "message," and "exclusive or" (XOR) this random string of bits with the original message to give an "encrypted message." If the receiver of the message knows how to form the same "random" string of bits, a second "exclusive or" of the random string of bits with the encrypted message will decrypt the message.
Let's review the mechanics of this process. First, recall how the XOR function works with bits (0 or 1 values):
Boolean Exclusive OR (XOR) Function
x y x XOR y
0 0 0
0 1 1
1 0 1
1 1 0
Now let's start with the single-byte message "A" and the random bit string "01111010" (or $7A in hex). The following table shows how the encryption/decryption process works:
Step Boolean Expression Hexadecimal Binary Comments
1. ASCII "A" a $41 0100 0001 Original "message:
2. "Random" Bits b $7A 0111 1010 Pseudo-random value from "random" number generator
3. XOR to encrypt a XOR b $3B 0011 1011 Encrypted "message"
4. "Random" Bits b $7A 0111 1010 Same "Random bits" as above
5. XOR to decrypt (a XOR b) XOR b $41 0100 0001 Decrypted "message" (same as original)
A pseudo-random number generator can be used to compute "random" numbers. If the same "seed" is used with a random number generator for both encryption and decryption processes, the same "random" sequence will be generated. Only the "seed" must be communicated if both sender and receiver are using the same random number generator.
Borland's Online Help Note: " Because the implementation of the Random function may change between compiler versions, we do not recommend using Random for encryption or other purposes that require reproducible sequences of pseudo-random numbers."
A variety of random-number generators exist but Delphi's Random function is used in the programs described below. The RandSeed variable (in the System unit) is the "seed" for the random number generator. (See the Probability section of the efg's Delphi Math Functions for alternative random number generators.) If long-term reproducibility is critical, you should use a random number generator for which you have the source code, as recommended by Borland.
A BMP file can be encrypted and still used as a BMP file if only the scanline pixel data is encrypted and the file header is not changed. A JPEG file once encrypted cannot be used as a JPEG file until it is properly decrypted. Study the BMP encryption/decryption process first, since it is easier to understand. Then study the JPEG encryption/decryption process.
--------------------------------------------------------------------------------
BMP Files
--------------------------------------------------------------------------------
BMP Background
A BMP file (or a TBitmap in memory) consists of a header record of various information and the scanlines with pixel data. If the header record of a BMP is encrypted, the BMP can no longer be treated as a bitmap. If only the scanlines of pixel data are encrypted -- with no change in the header record -- the resulting encrypted BMP can still be displayed as a bitmap.
Only the scanline pixel data are encrypted in this project so the resulting TBitmap/BMP file can still be used as an image. If present, the palette could also be encrypted, but that is not covered here.
See the Scanline Tech Note for information about how to access pixel data within a TBitmap.
Materials and Equipment
Software Requirements
Windows 95/98
Delphi 3/4/5 (to recompile)
CryptBMP.EXE
Hardware Requirements
VGA display with 640-by-480 screen in high/true color display mode
Procedure
Double click on the CryptBMP.EXE icon to start the program.
Press the Load button and select a BMP file (not provided). Press the Open button.
If desired, uncheck the "stretch" button. Normally, the image is stretched to fit the space available. In some cases, such as with small images, this may not be desirable.
Press the Encrypt button to display the encrypted image. This encrypted image can be saved to a BMP file by selecting the Save button.
Experiment with the Encyrpt and Decrypt Seed Numbers. As shown below, the decrypted image will not match the original when these two numbers are not the same.
Note that the Decrypt button is more of a label than a button that does anything. The decryption process is automatically called after any encryption, or when the decrypt seed number is changed.
Discussion
Consult the complete source code for all the details, but the main Encryption/Decryption routines are shown here.
The EncryptImage method looks at each scanline, regardless of PixelFormat, and XORs a random bit string with the original pixel data. The resulting encrypted image is displayed in the ImageEncrypted TImage.
For palletized images, the original palette is copied to the encrypted image.
Encrypt BMP File
// Don't bother trying to understand structure of pixels within scanline.
// Just find length of scanline in bytes and process all bytes.
PROCEDURE TFormCrypt.EncryptImage;
VAR
i : INTEGER;
j : INTEGER;
RandomValue : BYTE;
rowIn : pByteArray;
rowOut : pByteArray;
ScanlineByteCount: INTEGER;
BEGIN
IF Assigned(BitmapEncrypted)
THEN BitmapEncrypted.Free;
BitmapEncrypted := TBitmap.Create;
BitmapEncrypted.Width := BitmapOriginal.Width;
BitmapEncrypted.Height := BitmapOriginal.Height;
BitmapEncrypted.PixelFormat := BitmapOriginal.PixelFormat;
// Copy palette if palletized image
IF BitmapOriginal.PixelFormat IN [pf1bit, pf4bit, pf8bit]
THEN BitmapEncrypted.Palette := CopyPalette(BitmapOriginal.Palette);
// This finds the number of bytes per scanline regardless of PixelFormat
ScanlineByteCount := ABS(Integer(BitmapOriginal.Scanline[1]) -
Integer(BitmapOriginal.Scanline[0]));
TRY
RandSeed := StrToInt(EditSeedEncrypt.Text)
EXCEPT
RandSeed := 79997 // use this prime number if entry is invalid
END;
FOR j := 0 TO BitmapOriginal.Height-1 DO
BEGIN
RowIn := BitmapOriginal.Scanline[j];
RowOut := BitmapEncrypted.Scanline[j];
FOR i := 0 TO ScanlineByteCount-1 DO
BEGIN
RandomValue := Random(256); // 0..255 value
RowOut := RowIn XOR RandomValue
END
END;
ImageEncrypted.Picture.Graphic := BitmapEncrypted;
DecryptImage;
ButtonDecrypt.Enabled := TRUE;
ButtonSave.Enabled := TRUE
END {EncryptImage};
See Andreas Filsinger's original summary of this encryption method and an updated version for Delphi 6.01.
The DecryptImage method works much like the EncryptImage routine.
Decrypt BMP File
PROCEDURE TFormCrypt.DecryptImage;
VAR
BitmapDecrypted : TBitmap;
i : INTEGER;
j : INTEGER;
RandomValue : BYTE;
rowIn : pByteArray;
rowOut : pByteArray;
ScanlineByteCount: INTEGER;
BEGIN
BitmapDecrypted := TBitmap.Create;
BitmapDecrypted.Width := BitmapEncrypted.Width;
BitmapDecrypted.Height := BitmapEncrypted.Height;
BitmapDecrypted.PixelFormat := BitmapEncrypted.PixelFormat;
// Copy palette if palletized image
IF BitmapEncrypted.PixelFormat IN [pf1bit, pf4bit, pf8bit]
THEN BitmapDecrypted.Palette := CopyPalette(BitmapEncrypted.Palette);
// This finds the number of bytes per scanline regardless of PixelFormat
ScanlineByteCount := ABS(Integer(BitmapEncrypted.Scanline[1]) -
Integer(BitmapEncrypted.Scanline[0]));
TRY
RandSeed := StrToInt(EditSeedDecrypt.Text)
EXCEPT
RandSeed := 79997 // use this prime number if entry is invalid
END;
FOR j := 0 TO BitmapEncrypted.Height-1 DO
BEGIN
RowIn := BitmapEncrypted.Scanline[j];
RowOut := BitmapDecrypted.Scanline[j];
FOR i := 0 TO ScanlineByteCount-1 DO
BEGIN
RandomValue := Random(256); // 0..255 value
RowOut := RowIn XOR RandomValue
END
END;
ImageDecrypted.Picture.Graphic := BitmapDecrypted;
EditSeedDecrypt.Enabled := TRUE;
END {DecryptImage};
In the pf24bit example shown at the top of this page, the encrypted image gives no hint as to what colors might be present in the original image.
For palletized images (i.e., pf1bit, pf4bit, pf8bit), the palette is copied from the original image to the encrypted image and only the scanlines are encrypted. Because some information about the image is contained in the palette, encrypting the palette may also be a good idea, but that was not done in this project.
For example, only the colors of the pf1bit "Smiley" (from the Single-Bit Bitmaps Lab Report) are present in the encrypted form (see below). Hiding these colors by encrypting the palette entries may be desirable.
"Smiley" Encrypted Smiley
Using a different "Encrypt Seed Number" for each and every encrypted image is quite important. If the same Encrypt Seed Number is used for two images, some information about both images can be learned without the Encrypt Seed Number. For example, assume you have two images, A and B:
Original Images
A B
If you encrypt both of these images with the Encrypt Seed Number 19937, the results seem to hide the images:
Images Encrypted Using Seed 19937
A19937 B19937
But now if you take both of these images, and without any knowledge of the original encryption key, perform certain operations with the images, some information about the original images can be seen. For example, if you use XOR with corresponding color components for each pixel, (R,G,B) = (R1 XOR R2, G1 XOR G2, B1 XOR B2), some information about the original can be extracted. In particular, many traces of image B can be seen:
A19937 XOR B19937
This image is the equivalent to XORing the original images A and B:
A XOR B
If we assume R is the random sequence of bits, this is the math that explains why the randomness does not hide the composite image, A XOR B:
(A XOR R) XOR (B XOR R) = A XOR B
The solution is to use unique random sequences with A and B:
(A XOR R1) XOR (B XOR R2) <> A XOR B
This emphasizes why a unique key should be used with each image. If Image A had been encrypted with the key, 66547, the image A66547 looks much like A19937:
A66547 A19937
With the unique key, the A XOR B operations are quite different:
A66547 XOR B19937 A19937 XOR B19937
(Thanks to Christian Berger for stressing this limitation in a posting to the Borland Community site.)
--------------------------------------------------------------------------------
JPEG Files
--------------------------------------------------------------------------------
JPG Background
Unlike the BMP file, manipulating the "pixel" scanlines separately from any JPG "header" information is not possible (at least without modifying the existing Delphi TJPEGImage definition). So a different approach must be used. With JPGs, the whole file is encrypted. But this means that the resulting file cannot be treated as a JPG image.
Materials and Equipment
Software Requirements
Windows 95/98
Delphi 3/4/5 (to recompile)
CryptJPEG.EXE
Hardware Requirements
VGA display with 640-by-480 screen in high/true color display mode
Procedure
Double click on the CryptJPEG.EXE icon to start the program.
Press the Load button and select a JPG file (Flower.JPG is provided). Press the Open button.
If desired, uncheck the "stretch" button. Normally, the image is stretched to fit the space available. In some cases, such as with small images, this may not be desirable.
Press the Encrypt button to display the encrypted image (see discussion below). This encrypted image can be saved to a .Binary file by selecting the Save BIN button.
Experiment with the Encyrpt and Decrypt Seed Numbers. When the number match, the encrypted image will be correctly decrypted.
Load a previously saved .Binary file by pressing the Load BIN button. If the Decrypt Seed is correct, this encrypted .Binary file can be decrypted and displayed.
Discussion
Consult the complete source code for all the details, but the main Encryption/Decryption routines are shown here.
The technique shown here for a JPEG file could be used with any graphics file, such as GIFs, or any other type of file. Unlike the BMP encryption/decryption process described above, once a JPEG file is encrypted, it cannot be displayed as an image file. The encrypted JPEG file is a file of binary data that must be decrypted before it can be used in any way.
The processing of loading a BMP file wasn't explained above since it was so straightforward. However, in addition to the "normal" process of loading and displaying a TJPEGImage in a TImage, the JPEG file is loaded in to a JPEGOriginalBinary TMemoryStream for later processing by the encryption routine.
Load JPEG Image
procedure TFormCrypt.ButtonLoadJPGClick(Sender: TObject);
VAR
JPEGOriginal: TJPEGImage;
begin
IF OpenPictureDialog.Execute
THEN BEGIN
// Load JPEG Image for Display
JPEGOriginal := TJPEGImage.Create;
TRY
JPEGOriginal.LoadFromFile(OpenPictureDialog.Filename);
ImageOriginal.Picture.Graphic := JPEGOriginal
FINALLY
JPEGOriginal.Free
END;
// Load JPEG Image as Binary Stream
IF Assigned(JPEGOriginalBinary)
THEN JPEGOriginalBinary.Free;
JPEGOriginalBinary := TMemoryStream.Create;
JPEGOriginalBinary.LoadFromFile(OpenPictureDialog.Filename);
ButtonEncrypt.Enabled := TRUE;
EditSeedEncrypt.Enabled := TRUE;
END
end;
With the BMP file, the EncryptImage method looked at each scanline. Here with a JPEG, the whole file is treated as a binary stream of data and encrypted byte-by-byte to form a new binary stream, JPEGEncryptedBinary (a TMemoryStream).
The binary data in a TMemoryStream is processed by obtaining a pointer to the beginning of the stream, such as in
pIn := JPEGOriginalBinary.Memory;
The pointer is incremented, INC(pIn), as each byte is processed by XORing it with a byte from a sequence of "random" bytes.
Encrypt JPEG File
// Encrypt bytes in JPEGOriginalBinary TMemoryStream to give JPEGEncryptedBinary
PROCEDURE TFormCrypt.EncryptImage;
VAR
BitmapDisplay: TBitmap;
i : INTEGER;
pIn : pByte;
pOut : pByte;
RandomValue : BYTE;
BEGIN
TRY
RandSeed := StrToInt(EditSeedEncrypt.Text)
EXCEPT
RandSeed := 67547 // use this prime number if entry is invalid
END;
IF Assigned(JPEGEncryptedBinary)
THEN JPEGEncryptedBinary.Free;
JPEGEncryptedBinary := TMemoryStream.Create;
// Encrypted version same size as the original version
JPEGEncryptedBinary.Size := JPEGOriginalBinary.Size;
pIn := JPEGOriginalBinary.Memory;
pOut := JPEGEncryptedBinary.Memory;
FOR i := 1 TO JPEGOriginalBinary.Size DO
BEGIN
RandomValue := Random(256); // 0..255
pOut^ := pIn^ XOR RandomValue;
INC(pIn);
INC(pOut)
END;
// JPEGEncryptedBinary cannot be displayed as an image. So, let's just
// create a "noise" bitmap to display instead. The "seed" for this noise
// image will be the RandSeed left over from the JPEG encryption, so the
// same noise image will be created for a given JPEG.
BitmapDisplay := CreateNoiseImage(ImageEncrypted.Width,
ImageEncrypted.Height);
TRY
ImageEncrypted.Picture.Graphic := BitmapDisplay;
FINALLY
BitmapDisplay.Free
END;
DecryptImage;
ButtonDecrypt.Enabled := TRUE;
ButtonSaveBIN.Enabled := TRUE
END {EncryptImage};
Unlike the encrypted BMP file, the encrypted JPEG binary stream cannot be displayed as an image. To display something for this encrypted file, a "noise" image was displayed instead, which was created as shown next:
Create Noise Image
// Create pf24bit "noise" image using random numbers
FUNCTION CreateNoiseImage(CONST Width, Height: INTEGER): TBitmap;
VAR
i : INTEGER;
j : INTEGER;
row: pByteArray;
BEGIN
RESULT := TBitmap.Create;
RESULT.Width := Width;
RESULT.Height := Height;
RESULT.PixelFormat := pf24bit;
FOR j := 0 TO Height-1 DO
BEGIN
row := RESULT.Scanline[j];
FOR i := 0 TO 3*Width-1 DO // 3 bytes per pixel
BEGIN
row := Random(256)
END
END
END {CreateNoiseImage};
The DecryptImage method works much like the EncryptImage routine. After the decrypted binary stream is formed, JPEGDecryptedBinary, this stream is loaded into a new TJPEGImage. If there is any exception in this process (e.g., the decrypted file never was a JPEG file, or the decryption did no result in a JPEG file), then a "noise" image is displayed.
Decrypt JPEG File
PROCEDURE TFormCrypt.DecryptImage;
VAR
BitmapDisplay : TBitmap;
i : INTEGER;
JPEGDecrypted : TJPEGImage;
JPEGDecryptedBinary: TMemoryStream;
pIn : pByte;
pOut : pByte;
RandomValue : BYTE;
BEGIN
TRY
RandSeed := StrToInt(EditSeedDecrypt.Text)
EXCEPT
RandSeed := 67547 // use this prime number if entry is invalid
END;
JPEGDecryptedBinary := TMemoryStream.Create;
TRY
// Decrypted version same size as the encrypted version
JPEGDecryptedBinary.Size := JPEGEncryptedBinary.Size;
pIn := JPEGEncryptedBinary.Memory;
pOut := JPEGDecryptedBinary.Memory;
FOR i := 1 TO JPEGEncryptedBinary.Size DO
BEGIN
RandomValue := Random(256); // 0..255
pOut^ := pIn^ XOR RandomValue;
INC(pIn);
INC(pOut)
END;
// At this point the decrypted JPEG binary stream is in the
// JPEGDecryptedBinary stream. Load this memory stream into the
// JPEGDecrypted TJPEGImage.
JPEGDecrypted := TJPEGImage.Create;
TRY
TRY
JPEGDecrypted.LoadFromStream(JPEGDecryptedBinary);
ImageDecrypted.Picture.Graphic := JPEGDecrypted;
EXCEPT
// If any error occurs in the conversion, just show a noise image
BitmapDisplay := CreateNoiseImage(ImageEncrypted.Width,
ImageEncrypted.Height);
TRY
ImageDecrypted.Picture.Graphic := BitmapDisplay;
FINALLY
BitmapDisplay.Free
END;
END
FINALLY
JPEGDecrypted.Free
END
FINALLY
JPEGDecryptedBinary.Free
END;
EditSeedDecrypt.Enabled := TRUE;
END {DecryptImage};
--------------------------------------------------------------------------------
Conclusions
Encryption using a sequence of random bytes and XOR is a fairly simple process. A similar technique can be applied to other types of files.
An encrypted BMP file can still be displayed as a BMP file. An encrypted JPEG file is a binary stream that cannot be treated as a JPEG image until (and unless) it is correctly decrypted.
--------------------------------------------------------------------------------
Thanks to Morten Jacobsen of Norway for asking how an image could be encrypted.
--------------------------------------------------------------------------------
Keywords
XOR, Random, RandSeed, BMP, JPEG, TBitmap, TJEGImage, Scanline, PixelFormat, Palette, CopyPalette, TMemoryStream, Noise Image, pByte, pByteArray, TMemoryStream.Memory, LoadFromFile, LoadFromStream, ShellExecute, EditNumericKeyPress