86153
86153

Reputation: 1

Delphi Firemonkey LockBox3 AES-CBC, PC and Android result are different?

I need a AES library for devolop Firemonkey Moblie App. I tested ElAES and LockBox3, everything works fine complie to PC, But on FMX Android both library return wrong ciphertext.

Test Data (AES128CBC PKCS5Padding):

plainText: 'plainText'  - edtPlaintext.Text
key: '0000000000000000' - edtKey.Text
IV: '0000000000000000' - edtIV.Text
cipherText:  hex - 'DD0A2A20616162697B8B4DF53483F1D2',
             base64 - '3QoqIGFhYml7i031NIPx0g==' 

Test Code:

This is test code reley on LockBox3, related: https://github.com/TurboPack/LockBox3, function 'EncryptMemory' return unfixed ciphertext each time on Android, something need to notice?

uses uTPLb_Codec, uTPLb_CryptographicLibrary, uTPLb_Constants, uTPLb_StreamUtils;

type
  TCustomPadder = class(TObject)
  private
    FIV: TBytes;
  public
    constructor Create(const AIV: TBytes);
    procedure OnSetIV(Value: TMemoryStream);
  end;

constructor TCustomPadder.Create(const AIV: TBytes);
begin
  FIV := AIV
end;

procedure TCustomPadder.OnSetIV(Value: TMemoryStream);
begin
  Value.Size := Length(FIV);
  Value.Position := 0;
  Value.WriteBuffer(FIV, Length(FIV))
end;

function NewCodec(key: TBytes): TCodec;
var
  codec: TCodec;
  cryptographicLibrary: TCryptographicLibrary;
  keyStream: TStream;
  padder: TCustomPadder;
begin
  cryptographicLibrary := TCryptographicLibrary.Create(nil);
  // basic
  codec := TCodec.Create(nil);
  codec.BlockCipherId := Format(AES_ProgId, [128]);
  codec.ChainModeId := CBC_ProgId;
  codec.CryptoLibrary := cryptographicLibrary;
  codec.StreamCipherId := BlockCipher_ProgId;
  // extend
  padder := TCustomPadder.Create(bytesof('0000000000000000'));
  keyStream := TMemoryStream.Create;
  keyStream.WriteBuffer(key, Length(key));
  keyStream.Position := 0;
  codec.OnSetIV := padder.OnSetIV;
  codec.InitFromStream(keyStream);
  result := codec;
end;

function PKCS5Padding(ciphertext: string; blocksize: integer): string;
var
  builder: TStringBuilder;
  padding: integer;
  i: integer;
begin
  builder := TStringBuilder.Create(ciphertext);
  padding := blocksize - (builder.Length mod blocksize);
  for i := 1 to padding do
  begin
    builder.Append(Char(padding));
  end;
  result := builder.ToString;
  builder.DisposeOf;
end;

function BytesToHexStr(bytes: TBytes): string;
var
  i: integer;
begin
  result := '';
  for i := 0 to Length(bytes) - 1 do
    result := result + bytes[i].ToHexString(2);
end;

procedure TformAEST.btnEncryptClick(Sender: TObject);
var
  codec: TCodec;
  plainBytes, cipherBytes: TBytes;
  cipherMemory: TStream;
  cipherBytesLen: integer;
begin

  cipherMemory := TMemoryStream.Create;

  plainBytes := bytesof(PKCS5Padding(edtPlaintext.Text, 16));

  codec := NewCodec(bytesof(edtKey.Text));
  codec.Begin_EncryptMemory(cipherMemory);
  codec.EncryptMemory(plainBytes, Length(plainBytes));
  codec.End_EncryptMemory;

  cipherMemory.Position := 8;
  cipherBytesLen := cipherMemory.Size - 8;
  SetLength(cipherBytes, cipherBytesLen);
  cipherMemory.ReadBuffer(cipherBytes, cipherBytesLen);
  edtCiphertext.Text := BytesToHexStr(cipherBytes);
end;

Upvotes: -1

Views: 1192

Answers (2)

Dennies Chang
Dennies Chang

Reputation: 582

As your requirement, I need to create a mobile app with Delphi in recent weeks, and try to figure out how to encrypt in Delphi, and decrypt in server side application.

I choose Laravel 8 as my server side application framework, Delphi Alaxandria as client RAD tool.

With some tests, I found that Laravel used openssl_decrypt function to decrypt cipher, and padding byte is under special rule.

Hence, I use DEC (Delphi Encryption Compendium, you can download it free with Delphi GitIt package manager or from GitHub, the link I attached) to generate the cipher, with the APP_Key generated in Laravel (or you can generate 32 bytes key by yourself), the generated cipher can be decrypted by Laravel successfully.

I also upload my sample project to my GitHub repository, if you need to use AES-256-CBC in your Delphi FireMonkey project, please enjoy it.

Upvotes: 0

Remy Lebeau
Remy Lebeau

Reputation: 596527

Encryption and decryption operate on raw bytes, not on characters.

When encrypting Unicode strings, especially across platforms, you have to encode the characters to bytes using a consistent byte encoding before then encrypting those bytes.

And when decrypting Unicode strings, make sure to use that same byte encoding when converting the decrypted bytes back into characters.

In your code, you are using BytesOf() to encode Unicode characters to bytes. Internally, BytesOf() uses TEncoding.Default as the encoding, which is TEncoding.ANSI on Windows PCs but is TEncoding.UTF8 on other platforms. So, if your input strings contain any non-ASCII characters, you will end up with different results.

I suggest replacing BytesOf() with TEncoding.UTF8.GetBytes() on all platforms:

plainBytes := TEncoding.UTF8.GetBytes(PKCS5Padding(edtPlaintext.Text, 16));

codec := NewCodec(TEncoding.UTF8.GetBytes(edtKey.Text));

Upvotes: 2

Related Questions