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?