I've been fighting for hours on a very strange phenomenon. First of all, I'm using the TurboPower LockBox v10 encryption library (using TLbRijndael
), but I'm not convinced that's what's at fault. I need to pass a key into the encrypt/decrypt functions, so naturally I created functions like this:
function EncryptText(const S: String; const Key: String): String;
function DecryptText(const S: String; const Key: String): String;
Oddly, while the encryption seems to work, when I try to decrypt, it returns complete garbage. So, on my hours-long journey digging to find the reason, what I found absolutely baffles me. When I use an encryption key defined as a constant, everything works. But passing the exact same value (also system.string
) as a parameter in the function, it does not.
So to summarize that:
system.string
), everything works fine.system.string
), results are random and incorrect (and fails to decrypt).Here's a demo I put together to show what's happening (my third test application trying to figure this out):
program EncDecDemo;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.Classes, System.SysUtils,
LbCipher, LbClass, LbAsym, LbRSA;
const
ENC_KEY = 'abcdefghijklmnopqrstubwxyz123456';
var
EncComp: TLbRijndael;
function EncAsConst(const S: String): String;
begin
EncComp.SetKey(ENC_KEY); //<-- works as expected
Result:= EncComp.EncryptString(S);
end;
function DecAsConst(const S: String): String;
begin
EncComp.SetKey(ENC_KEY); //<-- works as expected
Result:= EncComp.DecryptString(S);
end;
function EncAsParam(const S: String; const Key: String): String;
begin
EncComp.SetKey(Key); //<-- produces random results
Result:= EncComp.EncryptString(S);
end;
function DecAsParam(const S: String; const Key: String): String;
begin
EncComp.SetKey(Key); //<-- produces random results
Result:= EncComp.DecryptString(S);
end;
var
InputStr: String;
EncrStr: String;
DecrStr: String;
Ctr: Integer;
begin
EncComp:= TLbRijndael.Create(nil);
try
EncComp.Encoding:= TEncoding.UTF8;
EncComp.KeySize:= ks256;
WriteLn('Enter a string to encrypt: ');
ReadLn(Input, InputStr);
WriteLn;
WriteLn('Encrypting as constants...');
EncrStr:= EncAsConst(InputStr);
DecrStr:= DecAsConst(EncrStr);
WriteLn('Pass 1: Enc = '+EncrStr+' Dec = '+DecrStr);
EncrStr:= EncAsConst(InputStr);
DecrStr:= DecAsConst(EncrStr);
WriteLn('Pass 2: Enc = '+EncrStr+' Dec = '+DecrStr);
EncrStr:= EncAsConst(InputStr);
DecrStr:= DecAsConst(EncrStr);
WriteLn('Pass 3: Enc = '+EncrStr+' Dec = '+DecrStr);
WriteLn;
WriteLn('Encrypting as parameters...');
EncrStr:= EncAsParam(InputStr, ENC_KEY);
WriteLn('Pass 1: Enc = '+EncrStr);
EncrStr:= EncAsParam(InputStr, ENC_KEY);
WriteLn('Pass 2: Enc = '+EncrStr);
EncrStr:= EncAsParam(InputStr, ENC_KEY);
WriteLn('Pass 3: Enc = '+EncrStr);
EncrStr:= EncAsParam(InputStr, ENC_KEY);
WriteLn('Pass 4: Enc = '+EncrStr);
EncrStr:= EncAsParam(InputStr, ENC_KEY);
WriteLn('Pass 5: Enc = '+EncrStr);
EncrStr:= EncAsParam(InputStr, ENC_KEY);
WriteLn('Pass 6: Enc = '+EncrStr);
WriteLn;
WriteLn('Press enter to exit');
ReadLn;
finally
EncComp.Free;
end;
end.
As you can see, when passing the exact same value as a parameter in the function, it produces a different result each time. Let me reiterate that, the exact same value.
My only conclusion is that system.string
when used as a constant (const ENC_KEY = 'value';
) is somehow a different size or type behind the scenes than when used as a variable.
When I look inside the SetKey
procedure, it's a simple Move
call...
procedure TLbRijndael.SetKey(const Key);
begin
Move(Key, FKey, FKeySizeBytes);
end;
(where FKey
is array [0..31] of Byte;
)
What's going wrong here and how to resolve it?
procedure TLbRijndael.SetKey(const Key);
begin
Move(Key, FKey, FKeySizeBytes);
end;
What you pass to this untyped parameter is the problem. When you pass a true constant, the text is passed. When you pass a string variable, the address of the text is passed, rather than the text itself. That's because a string variable is actually a pointer to the text.
I believe you will need to stop using a string variable as the key and provide an binary buffer.
Consider this program that illustrates the issue:
{$APPTYPE CONSOLE}
uses
SysUtils;
procedure Foo(const Key);
var
buff: Pointer;
begin
Move(Key, buff, SizeOf(buff));
Writeln(Format('%p', [buff]));
end;
const
ENC_KEY = 'abcdefghijklmnopqrstubwxyz123456';
var
str: string = ENC_KEY;
begin
Foo(ENC_KEY);
Foo(str);
Writeln(Format('%p', [Pointer(str)]));
end.
On my machine the output is:
00620061 00419D48 00419D48
The first line is the text 'ab'
encoded as UTF-16LE. The second line is the address of str
, as confirmed by the third line.
I'd like to stress the importance of the distinction between text and binary.