Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-pascal / 2011-03

TBlowfishDecryptStream flaw

4 messages · 2 people ·

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Jorge Aldo G. de F. Junior

i have a problem with TBlowfishDecryptStream :

			Try
				lBuffer := TMemoryStream.Create;
				lSource := (aObject As TMemoryStream);
				lSource.Seek(0, soFromBeginning);
				lCypher := TBlowfishDecryptStream.Create(fKey, lSource);
				lBuffer.LoadFromStream(lCypher);
			Except
				On E: Exception Do
					WriteLn(E.ClassName, '->', E.Message);
			End;

Spits out the "EBlowFishError" exception with "Seek not allowed on encryption streams"

The problem is basically that TBlowfishDecryptStream does not allow to use the "size" propertie, making it very hard to use it.

There are various ways to do this, for one :

			Try
				lBuffer := TMemoryStream.Create;
				lSource := (aObject As TMemoryStream);
				lSource.Seek(0, soFromBeginning);
				lCypher := TBlowfishDecryptStream.Create(fKey, lSource);
				lBuffer.CopyFrom(lCypher, lCypher.Size);
			Except
				On E: Exception Do
					WriteLn(E.ClassName, '->', E.Message);
			End;

But this gives the exact same error,

the last one is this :

			Try
				lBuffer := TMemoryStream.Create;
				lSource := (aObject As TMemoryStream);
				lSource.Seek(0, soFromBeginning);
				lCypher := TBlowfishDecryptStream.Create(fKey, lSource);
				lBuffer.CopyFrom(lCypher, lSource.Size);
			Except
				On E: Exception Do
					WriteLn(E.ClassName, '->', E.Message);
			End;

The problem now is that it eats chars at the end of the generated lBuffer (tested by taking a tmemorystream thru tblowfishencryptstream, then taking the result to this tblowfishdecryptstream, than back to a tmemorystream and comparing the results, the original tmemorystream shows to be bigger than the resulting tmemorystream)...

Basically i need :

1 - That TBlowfishEncryptStream guarantees that the same ammount of bytes feed to it, ends up at the output of it
2 - That TBlowfishDecryptStream simply allows us to use Size property (Well, sometimes i receive a tmemorystream in an unrelated part of the code so i cannot read the original size the buffer had before encrypting)...

Test case :

===== Test7.pas ======

Uses
Classes,

	BlowFish;
Var
	lSource,
	lIntermediate,
	lDestination : TMemoryStream;
	lEncrypter : TBlowfishEncryptStream;
	lDecrypter : TBlowfishDecryptStream;
Begin
	lSource := TMemoryStream.Create;
	lIntermediate := TMemoryStream.Create;
	lDestination := TMemoryStream.Create;
	lSource.LoadFromFile('test7.pas');
	WriteLn(lSource.Size);
	lEncrypter := TBlowfishEncryptStream.Create('123', lIntermediate);
	lEncrypter.CopyFrom(lSource, lSource.Size);
	WriteLn(lIntermediate.Size);
	lIntermediate.Seek(0, soFromBeginning);
	lDecrypter := TBlowfishDecryptStream.Create('123', lIntermediate);
	lDestination.CopyFrom(lDecrypter, lSource.Size);
	WriteLn(lDestination.Size);
	lDestination.SaveToFile('test7.txt');
	lSource.Free;
	lIntermediate.Free;
	lDestination.Free;
End.
==== output =====

2147
2144
2144

IE : It misses 3 chars...

Michael Van Canneyt
to Jorge Aldo G. de F. Junior
Jorge Aldo G. de F. Junior wrote
i have a problem with TBlowfishDecryptStream :

[snip]

Show quoted text 11 lines
The problem now is that it eats chars at the end of the generated lBuffer (tested by taking a tmemorystream thru tblowfishencryptstream, then taking the result to this tblowfishdecryptstream, than back to a tmemorystream and comparing the results, the original tmemorystream shows to be bigger than the resulting tmemorystream)... Basically i need : 1 - That TBlowfishEncryptStream guarantees that the same ammount of bytes feed to it, ends up at the output of it

That should be the case. See below for an explanation why you get different results.

2 - That TBlowfishDecryptStream simply allows us to use Size property (Well, sometimes i receive a tmemorystream in an unrelated part of the code so i cannot read the original size the buffer had before encrypting)...

This is impossible by definition.

You should always design your algorithms so they don't need size. Compression and encryption streams cannot have size, since they do not know the size of the input data.

There are only 3 streams that have guaranteed size:
- Memory stream
- File stream
- String stream (a variant of memory stream)
All other streams do not have size.

Your second mistake is that the output of the BlowFish encryption stream is only fully known when it is destroyed. You should FIRST destroy the blowfish encryptor/decryptor stream. Only then, all characters that are in the internal encryption buffer are written to the output buffer.

Alternatively, call Flush() to write the remains of the internal buffer to the output buffer. Note that after a call to flush(), no more data can be written to the stream, because the output will then be wrong.

Michael.

Jorge Aldo G. de F. Junior
to Michael Van Canneyt

Check those two programs

======= Program A =====

Program A;

Uses
Classes,

	BlowFish;
Var
	lSource,
	lIntermediate : TMemoryStream;
	lEncrypter : TBlowfishEncryptStream;
Begin
	lSource := TMemoryStream.Create;
	lSource.LoadFromFile('test7.pas');
	WriteLn(lSource.Size);
	lIntermediate := TMemoryStream.Create;
	lEncrypter := TBlowfishEncryptStream.Create('123', lIntermediate);
	lEncrypter.CopyFrom(lSource, lSource.Size);
	lEncrypter.Free;
	WriteLn(lIntermediate.Size);
	// SendStreamViaSocket(lIntermediate); { Lets pretend this function
automatically checks the size of the stream }
	lIntermediate.Free;
	lSource.Free;
End.

====== EOF =====

====== Program B =====

Program B;

Uses
Classes,

	Blowfish;
Var
	lIntermediate,
	lDestination : TMemoryStream;
	lDecrypter : TBlowfishDecryptStream;
Begin
	lIntermediate := TMemoryStream.Create;
	lDestination := TMemoryStream.Create;
	// ReceiveStreamViaSocket(lIntermediate);  { Lets pretend this
function can check the socket for the size of the last UDP packet }
	WriteLn(lIntermediate.Size);
	lIntermediate.Seek(0, soFromBeginning);
	lDecrypter := TBlowfishDecryptStream.Create('123', lIntermediate);
	lDestination.CopyFrom(lDecrypter, lIntermediate.Size); { Read how much ??? }
	lDecrypter.Free;
	WriteLn(lDestination.Size);
	lDestination.SaveToFile('test7.txt');
	lSource.Free;
	lIntermediate.Free;
	lDestination.Free;
End.

===== EOF =====

The problem consists that, if lIntermediate.Size is not equal to lSource.Size, i will not know how much bytes to "copyfrom" from the decrypter.

For one, if a 10 bytes lSource generates a 20 bytes lIntermediate, and no clear rule show what are the mathematical relationship of those sizes,
then asking 20 bytes (as lIntermediate.Size will show) will yield wrong results (Worse if the result at lIntermediate is smaller than the original lSource.Size).

But, if TBlowfishDecryptStream worked backwards (the create parameters being the key and the DESTINATION stream, not the source) then this would makes sense.

The problem ends up to be that by having a source parameter instead of a destination paramater this makes you have to read out bytes from the decrypter (you push out instead of pushing in), while at the same time you cannot know how many bytes you really have to read by just looking at the decrypter current state.

Thats a set of mutually exclusive assumptions.

Now this :

===== Program C ====

Program B;

Uses
Classes,

	Blowfish;
Var
	lIntermediate,
	lDestination : TMemoryStream;
	lDecrypter : TBlowfishDecryptStream;
Begin
	lIntermediate := TMemoryStream.Create;
	lDestination := TMemoryStream.Create;
	// ReceiveStreamViaSocket(lIntermediate);  { Lets pretend this
function can check the socket for the size of the last UDP packet }
	WriteLn(lIntermediate.Size);
	lIntermediate.Seek(0, soFromBeginning);
	lDecrypter := TBlowfishDecryptStream.Create('123', lDestination);
	lDecrypter.CopyFrom(lIntermediate, lIntermediate.Size);
	lDecrypter.Free;
	WriteLn(lDestination.Size);
	lDestination.SaveToFile('test7.txt');
	lSource.Free;
	lIntermediate.Free;
	lDestination.Free;
End.

=== EOF ===

This would work better, cause now you just need to know how much bytes to push into the decrypter, something that is readily available at the lIntermediate stream.

While i bet that the blowfish algorithm has an input to output size relationship of one to one (as most stream cyphers), this is not a good choice for other algorithms wich could yield different output sizes for a given input...

2011/3/6 Michael Van Canneyt :

Show quoted text 53 lines
On Sat, 5 Mar 2011, Jorge Aldo G. de F. Junior wrote: > i have a problem with TBlowfishDecryptStream : [snip] > > The problem now is that it eats chars at the end of the generated > lBuffer (tested by taking a tmemorystream thru tblowfishencryptstream, > then taking the result to this tblowfishdecryptstream, than back to a > tmemorystream and comparing the results, the original tmemorystream > shows to be bigger than the resulting tmemorystream)... > > Basically i need : > > 1 - That TBlowfishEncryptStream guarantees that the same ammount of > bytes feed to it, ends up at the output of it That should be the case. See below for an explanation why you get different results. > 2 - That TBlowfishDecryptStream simply allows us to use Size property > (Well, sometimes i receive a tmemorystream in an unrelated part of the > code so i cannot read the original size the buffer had before > encrypting)... This is impossible by definition. You should always design your algorithms so they don't need size. Compression and encryption streams cannot have size, since they do not know the size of the input data. There are only 3 streams that have guaranteed size: - Memory stream - File stream - String stream (a variant of memory stream) All other streams do not have size. Your second mistake is that the output of the BlowFish encryption stream is only fully known when it is destroyed. You should FIRST destroy the blowfish encryptor/decryptor stream. Only then, all characters that are in the internal encryption buffer are written to the output buffer. Alternatively, call Flush() to write the remains of the internal buffer to the output buffer. Note that after a call to flush(), no more data can be written to the stream, because the output will then be wrong. Michael. _______________________________________________ fpc-pascal maillist  -   http://lists.freepascal.org/mailman/listinfo/fpc-pascal
Michael Van Canneyt
to Jorge Aldo G. de F. Junior
Jorge Aldo G. de F. Junior wrote
Check those two programs ======= Program A =====

[snip]

Show quoted text 5 lines
===== EOF ===== The problem consists that, if lIntermediate.Size is not equal to lSource.Size, i will not know how much bytes to "copyfrom" from the decrypter.

Correct. You may never know. Imagine your data was compressed. You would need the ORIGINAL uncompressed size, not the compressed size.

Please Re-Read my first reply:

"You should always design your algorithms so they don't need size. Compression and encryption streams cannot have size, since they do not know the size of the input data."

You'll never know in advance how many bytes go through a socket, unless the size was sent first. so the comment in your code:

   // ReceiveStreamViaSocket(lIntermediate);  { Lets pretend this function can check the socket for the size of the last UDP packet }

is in general false. Size is not available on a socket, unless it was pushed. Or if the data comes from standard input, as with piping.

Your idea of a decryptor stream in which you push data is good, but will not help in case of a socket or standard input.

Anyway, I don't see what is wrong with

   D:=TDecryptor.Create(SomeStreamWithEncryptedData);
   try
     repeat
       b:=D.Read(ABuffer,BufferSize);
       if b0 then
         processbuffer(abuffer,b);
     until (b=0);
   finally
     D.Free;
   end;

If you look at the standard algorithms in the RTL, you'll see that they never use Size, except in the case of CopyFrom(Somestream,0).

Borland made a design mistake when they implemented Size and Seek as standard methods/properties of TStream. These 2 properties are available only in a a minority of the streams. (Where they are useful, I'm not arguing that).

Michael.