LockBox 3 Encrypting not matching online tool, suspect padding or key problem

1.3k Views Asked by At

I have a client providing an API that dictates the data to send to them must be encrypted with AES, 128-bit key, ECB mode, and PKCS5Padding. I'm trying to use LockBox 3 in Delphi 10.3 Rio and am not getting the same encrypted string as an online test tool they pointed to for verification. It is close, but not quite there.

With lots of reading here about Unicode, PKCS5Padding, and related questions, I've come to the end of what to try. I must admit I don't do very much with encryption and have been reading as much as I can to get my head around this before I ask questions.

There are a couple things I'd like confirmation on:

  • The difference between a password and a key. I've read that LB3 uses a password to generate a key, but I have specific instructions from the client on how the key is to be generated so I've made my own Base64-encoded key and am calling InitFromStream to initialize it. I believe this takes the place of setting a password, is that right? Or maybe passwords are only used by Asymetric cipers (not Symetric ones, like AES)?
  • PKCS5Padding: I was worried by something I read on the LB3 Help site that said padding is done intelligently depending on the choice of cipher, chaining mode, etc. So does that mean there's no way to force it to use a specific padding method? I've converted the data to a byte array and implemented by own PKCS5Padding but I think LB3 may still be padding beyond that. (I've tried looking through the code and have not found any evidence this is what it's doing.)

Should I use a different encryption library in Delphi to accomplish this? I've checked out DelphiEncryptionCompendium and DcPCryptV2 but I found LB3 seems to have the most support and I felt it was the easiest to work with, especially in my Unicode version of Delphi. Plus I have used LockBox 2 quite a bit in years past, so I figured it would be more familiar (this turned out not to be the case).

To illustrate what I've tried, I extracted my code from the project to a console application. Perhaps my assumptions above are correct and there's a glaring error in my code or a LB3 parameter I don't understand that someone will point out:

program LB3ConsoleTest;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils, System.Classes, System.NetEncoding,
  uTPLb_Codec, uTPLb_CryptographicLibrary,
  uTPLb_StreamUtils, uTPLb_Constants;

var
  Codec: TCodec;
  CryptographicLibrary: TCryptographicLibrary;

function PKCS5PadStringToBytes(RawData: string; const PadSize: Integer): TBytes;
{ implement our own block padding }
var
  DataLen: Integer;
  PKCS5PaddingCount: ShortInt;
begin
  Result := TEncoding.UTF8.GetBytes(RawData);
  DataLen := Length(RawData);

  PKCS5PaddingCount := PadSize - DataLen mod PadSize;
  if PKCS5PaddingCount = 0 then
    PKCS5PaddingCount := PadSize;
  Inc(DataLen, PKCS5PaddingCount);

  SetLength(Result, DataLen);
  FillChar(Result[DataLen - PKCS5PaddingCount], PKCS5PaddingCount, PKCS5PaddingCount);
end;

procedure InitializeAESKey(const AESKey: string);
{ convert the string to a byte array,
  use that to initialize a ByteStream,
  and call LB3's InitFromStream }
var
  AESKeyBytes: TBytes;
  AESKeyStream: TBytesStream;
begin
  AESKeyBytes := TEncoding.UTF8.GetBytes(AESKey);
  AESKeyStream := TBytesStream.Create(AESKeyBytes);
  Codec.InitFromStream(AESKeyStream);
end;

const
  RawData = '{"invoice_id":"456456000018047","clerk_id":"0023000130234234","trans_amount":1150034534,"cust_code":"19455605000987890641","trans_type":"TYPE1"}';
  AESKeyStr = 'CEAA31AD1EE4BDC8';
var
  DataBytes: TBytes;
  DataStream: TBytesStream;
  ResultStream: TBytesStream;
  ResultBytes: TBytes;
  Base64Encoder: TBase64Encoding;
begin
  // create the LockBox3 objects
  Codec := TCodec.Create(nil);
  CryptographicLibrary := TCryptographicLibrary.Create(nil);
  try
    // setup LB3 for AES, 128-bit key, ECB
    Codec.CryptoLibrary := CryptographicLibrary;
    Codec.StreamCipherId := uTPLb_Constants.BlockCipher_ProgId;
    Codec.BlockCipherId  := Format(uTPLb_Constants.AES_ProgId, [128]);
    Codec.ChainModeId    := uTPLb_Constants.ECB_ProgId;

    // prep the data, the key, and the resulting stream
    DataBytes := PKCS5PadStringToBytes(RawData, 8);
    DataStream := TBytesStream.Create(DataBytes);
    InitializeAESKey(AESKeyStr);
    ResultStream := TBytesStream.Create;

    // ENCRYPT!
    Codec.EncryptStream(DataStream, ResultStream);

    // take the result stream, convert it to a byte array
    ResultStream.Seek(0, soFromBeginning);
    ResultBytes := Stream_to_Bytes(ResultStream);

    // convert the byte array to a Base64-encoded string and display
    Base64Encoder := TBase64Encoding.Create(0);
    Writeln(Base64Encoder.EncodeBytesToString(ResultBytes));

    Readln;
  finally
    Codec.Free;
    CryptographicLibrary.Free;
  end;
end.

This program generates an encrypted string 216 characters long with only the last 25 different than what the online tool produces.

Why?

1

There are 1 best solutions below

5
Arnaud Bouchez On

AES uses 16-bytes blocks, not 8-bytes.

So you need PKCS7 with 16 bytes padding, not PKCS5 which is fixed to 8 bytes.

Please try

DataBytes := PKCS5PadStringToBytes(RawData, 16);

Also consider changing the chaining mode away from ECB, which is pretty weak, so is to be avoided for any serious work.