Reputation: 631
I designed a system that stores blocks of data, sent compressed and encrypted by multiple clients.
When trying to fetch stored data blocks (that I need to decompress and decrypt) on a specific computer, a ZLib "data error" exception is raised during the data decompression. It seems clear that the stream has not been properly decrypted so the input stream is not a valid ZLib stream, leading to a such issue.
After some research I discovered that for an identical block of source data that is also stored, this problematic block contained totaly different data, so it's quite clear that there is an issue in the encryption or the compression algorithm.
However, the fact that I cannot easily reproduce the problem means that if there is an error it's something far from being obvious. So I'm posting the code here, hoping someone will spot something I didn't see.
First, the data block AStream
is compressed :
function CompressStream(AStream: TMemoryStream;
ACompressionLevel:
TCompressionLevel): Boolean;
var
LTempStream: TMemoryStream;
LCompressedStream: TCompressionStream;
begin
LTempStream := TMemoryStream.create;
try
try
AStream.Seek(0, soBeginning);
LCompressedStream := TCompressionStream.Create(ACompressionLevel, LTempStream);
try
try
LCompressedStream.CopyFrom(AStream, AStream.Size);
finally
LCompressedStream.free;
end;
finally
AStream.Clear;
AStream.CopyFrom(LTempStream, 0);
AStream.Seek(0, soBeginning);
Result := True;
end;
finally
LTempStream.free;
end;
except
Result := False;
end;
end;
Then, it's encrypted :
function EncryptStream(AStream: TMemoryStream; const AParameters: AnsiString): Boolean;
var
LModifiedStream: TMemoryStream;
LRijndaelCipher: TDCP_rijndael;
begin
try
LRijndaelCipher := TDCP_rijndael.Create(nil);
LModifiedStream := TMemoryStream.Create;
InitCipherWithKey(LRijndaelCipher, AParameters);
try
AStream.Seek(0, soBeginning);
LRijndaelCipher.EncryptStream(AStream, LModifiedStream, AStream.Size);
TMemoryStream(AStream).Clear;
AStream.CopyFrom(LModifiedStream, 0);
Result := True;
finally
LRijndaelCipher.Burn;
LRijndaelCipher.Free;
LModifiedStream.Free;
end;
except
Result := False;
end;
end;
...and stored somewhere.
When fetching a data block, it's first decrypted :
function DecryptStream(AStream: TMemoryStream; const AParameters: AnsiString): Boolean;
var
LModifiedStream: TMemoryStream;
LRijndaelCipher: TDCP_rijndael;
begin
try
LRijndaelCipher := TDCP_rijndael.Create(nil);
LModifiedStream := TMemoryStream.Create;
InitCipherWithKey(LRijndaelCipher, AParameters);
try
AStream.Seek(0, soBeginning);
LRijndaelCipher.DecryptStream(AStream, LModifiedStream, AStream.Size);
TMemoryStream(AStream).Clear;
AStream.CopyFrom(LModifiedStream, 0);
Result := True;
finally
LRijndaelCipher.Burn;
LRijndaelCipher.Free;
LModifiedStream.Free;
end;
except
Result := False;
end;
end;
Then uncompressed :
function DecompressStream(AStream: TMemoryStream): Boolean;
var
LTempStream: TMemoryStream;
LCompressedStream: TDecompressionStream;
begin
LTempStream := TMemoryStream.create;
try
try
AStream.Seek(0, soBeginning);
LCompressedStream := TDecompressionStream.Create(AStream);
try
try
LTempStream.CopyFrom(LCompressedStream, LCompressedStream.size);
finally
LCompressedStream.Free;
end;
finally
AStream.Clear;
AStream.CopyFrom(LTempStream, 0);
AStream.Seek(0, soBeginning);
Result := True;
end;
finally
LTempStream.free;
end;
except
Result := False;
end;
end;
The cipher is initialized using the InitCipherWithKey()
method. It is designed to convert a MD5 hash into it's binary representation, contained in the LMD5Hash
variable (yes, the array is 64 bytes long but only the first 16 will be used by the cipher, since I call Init()
with the 128 value (which means a 128-bit/16 bytes key length) :
procedure InitCipherWithKey(ACipher: TDCP_cipher; const AKey: AnsiString);
var
LMD5Hash: array [0..63] of Byte;
S: AnsiString;
begin
//We use a 128 bit key
ZeroMemory(@LMD5Hash, SizeOf(LMD5Hash));
S := AKey;
HexToBin(PAnsiChar(S), LMD5Hash, Length(LMD5Hash) -1);
ACipher.Init(LMD5Hash[0], 128, nil);
ZeroMemory(@LMD5Hash, SizeOf(LMD5Hash));
end;
Thanks in advance.
Upvotes: 1
Views: 1783
Reputation: 93968
There is something going wrong when calculating/transporting the key. First you calculate the key from a password (incorrectly named AKey
) using MD5. For some reason, you are using a buffer of 64 bytes for this, even though MD5 will always output exactly 16 bytes. This is already not secure, you should use a password based key derivation function such as PBKDF2 to derive keys from passwords.
Then however you seem to treat the binary output of MD5 as characters. I presume you take the output of InitCipherWithKey
and put it in AParameters
. Now suddenly the binary output of MD5 is treated as a string. Unfortunately, this means that bytes are interpreted as characters such as control characters (including the null-termination character value if the byte has value 00
).
So it is most likely that the decryption does not succeed because of loss of data from the key itself. This of course depends on the key value, making this scheme fail now and then. Please check your code base to see if you make errors regarding character-encoding and encoding.
Upvotes: 1