![]() |
|||||||||||
|
|
||||||||||
IntroductionThis article enhances part 1 by adding three important features to the application:
Multiple KeysSymmetric keys contain a central problem: How do you transfer the key?
Anyone who gets his hands on the key is able to decrypt every message you send or receive.
So we have to make it difficult to steal the key. One idea might be keeping the keys short and don’t save them at all.
But a short key always leaves a footprint in the encrypted file, in our case it wouldt be visible as a regular noise pattern. A password always belongs to its key file, just like that:
public struct FilePasswordPair{
public String fileName;
public String password;
public FilePasswordPair(String fileName, String password){
this.fileName = fileName;
this.password = password;
}
}
Before we can use the keys, the file/password pairs have to be combined.
Here is the same thing in C#
public static MemoryStream CreateKeyStream(FilePasswordPair key){
FileStream fileStream = new FileStream(key.fileName, FileMode.Open);
MemoryStream resultStream = new MemoryStream();
int passwordIndex = 0;
int currentByte = 0;
while( (currentByte = fileStream.ReadByte()) >= 0 ){
//combine the key-byte with the corresponding password-byte
currentByte = currentByte ^ key.password[passwordIndex];
//add the result to the key stream
resultStream.WriteByte((byte)currentByte);
//proceed to the next letter or repeat the password
passwordIndex++;
if(passwordIndex == key.password.Length){
passwordIndex = 0;
}
}
fileStream.Close();
resultStream.Seek(0, SeekOrigin.Begin);
return resultStream;
}
The resulting stream is as long as your key file, but never stored anywhere. Of course this encryption is not really save,
so we’ll use more keys and more passwords. Before the recipient is able to extract the message, he must have
all the keys and know all the passwords. If somebody manages to copy one key or guess two passwords, there's no need
to panic as long as the other key files are still save.
private static MemoryStream GetKeyStream(FilePasswordPair[] keys){
//Xor the keys an their passwords
MemoryStream[] keyStreams = new MemoryStream[keys.Length];
for(int n=0; n<keys.Length; n++){
keyStreams[n] = CreateKeyStream(keys[n]);
}
//Buffer for the resulting stream
MemoryStream resultKeyStream = new MemoryStream();
//Find length of longest stream
long maxLength = 0;
foreach(MemoryStream stream in keyStreams){
if( stream.Length > maxLength ){ maxLength = stream.Length; }
}
int readByte = 0;
for(long n=0; n<=maxLength; n++){
for(int streamIndex=0; streamIndex<keyStreams.Length; streamIndex++){
if(keyStreams[streamIndex] != null){
readByte = keyStreams[streamIndex].ReadByte();
if(readByte < 0){
//end of stream - close the file
//the last loop (n==maxLength) will close the last stream
keyStreams[streamIndex].Close();
keyStreams[streamIndex] = null;
}else{
//copy a byte into the result key
resultKeyStream.WriteByte( (byte)readByte );
}
}
}
}
return resultKeyStream;
}
As you can see, we don’t have to change the algorithm at all. We just call Spread the information over the imagesIf you're really paranoid, you won’t trust your mailbox. Of course you won’t trust the postman either.
That means, you don’t dare sending the carrier bitmap in one piece.
You can send each image in a separate e-mail, post them in different mailboxes, or store them on different discs.
The GUI allows selecting carrier bitmaps the same way as selecting key files. The selection is stored as an array of
public struct CarrierImage{
//file name of the clean image
public String sourceFileName;
//file name to save the new image
public String resultFileName;
//width * height
public long countPixels;
//produce colorful (false) or grayscale noise (true) for this picture
public bool useGrayscale;
//how many bytes will be hidden in this image - this field is set by CryptUtility.HideOrExtract()
public long messageBytesToHide;
public CarrierImage(String sourceFileName, String resultFileName, long countPixels, bool useGrayscale){
this.sourceFileName = sourceFileName;
this.resultFileName = resultFileName;
this.countPixels = countPixels;
this.useGrayscale = useGrayscale;
this.messageBytesToHide = 0;
}
}
Larger images can hide more bytes (more pixels) than smaller images. This application uses the most simple distribution:
//calculate count of message-bytes to hide in (or extract from) each image
for(int n=0; n<imageFiles.Length; n++){
//pixels = count of pixel in this image / count of all available pixels
float pixels = (float)imageFiles[n].countPixels / (float)countPixels;
//this image will hide (length of the message * pixels) bytes
imageFiles[n].messageBytesToHide = (long)Math.Ceiling( (float)messageLength * pixels );
}
Now we start with the first carrier bitmap, loop over the message, hide a number of bytes, switch to the second carrier bitmap, and so on.
//Current position in the carrier bitmap
//Start with 1, because (0,0) contains the message length
Point pixelPosition = new Point(1,0);
//Count of bytes already hidden in the current image
int countBytesInCurrentImage = 0;
//Index of the currently used image
int indexBitmaps = 0;
//Loop over the message and hide each byte
for(int messageIndex=0; messageIndex<messageLength; messageIndex++){
//Calculate the position of the next pixel
//...
//Proceed to the next bitmap
if(countBytesInCurrentImage == imageFiles[indexBitmaps].messageBytesToHide){
indexBitmaps++;
pixelPosition.Y = 0;
countBytesInCurrentImage = 0;
bitmapWidth = bitmaps[indexBitmaps].Width-1;
bitmapHeight = bitmaps[indexBitmaps].Height-1;
if(pixelPosition.X > bitmapWidth){ pixelPosition.X = 0; }
}
//hide or extract a byte
//...
countBytesInCurrentImage++;
}
In the end we must save the new images. Each image can be saved using a different format (bmp, tif or png). The new format has nothing to do with the format of the original image file. That means, you can select a BMP-, two PNG- and a TIFF-file as carrier images, and save the results into three TIFF- and one PNG-file.
//...
for(indexBitmaps=0; indexBitmaps<bitmaps.Length; indexBitmaps++){
if( ! extract ){ //Extract mode does not change any images
//Save the resulting image and close the source file
SaveBitmap( bitmaps[indexBitmaps], imageFiles[indexBitmaps].resultFileName );
}
}
//...
private static void SaveBitmap(Bitmap bitmap, String fileName){
String fileNameLower = fileName.ToLower();
//Get the format from the file extension
System.Drawing.Imaging.ImageFormat format = System.Drawing.Imaging.ImageFormat.Bmp;
if((fileNameLower.EndsWith("tif"))||(fileNameLower.EndsWith("tiff"))){
format = System.Drawing.Imaging.ImageFormat.Tiff;
}else if(fileNameLower.EndsWith("png")){
format = System.Drawing.Imaging.ImageFormat.Png;
}
//copy the bitmap
Image img = new Bitmap(bitmap);
//close bitmap file
bitmap.Dispose();
//save new bitmap
img.Save(fileName, format);
img.Dispose();
}
|