Search code examples
stringdelphidelphi-xe7lockbox

Different results passing system.string as either function parameter or constant


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:

  • When passing key as constant (system.string), everything works fine.
  • When passing key as parameter (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.

enter image description here

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?


Solution

  • 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.