-1

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;
86153
  • 1
  • Consider accepting answers that are helpful and reviewing previous answers an accepting is appropriate. 1, See [reputation faq](http://stackoverflow.com/faq#reputation) Voting up a question or answer signals to the rest of the community that a post is interesting, well-researched, and useful. – zaph Jan 01 '19 at 18:04

2 Answers2

2

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));
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thanks answer, from Debugger/Output: key/IV:30303030303030303030303030303030 plainBytes: 706C61696E5465787407070707070707, the same two platform, seems not this reason. – 86153 Jan 02 '19 at 01:16
  • @86153 it is very unlikely that 2 different encryption libraries would both fail on mobile, it is more likely that you are doing sonething differently in how you use the libraries across platforms. You will have to continue debugging to see where differences between platforms actually occur at each step. – Remy Lebeau Jan 02 '19 at 07:31
  • @86153 And FYI, on a side note, you should NOT be applying PKCS5 padding to the `string` you are encrypting, you should be applying the padding to `plainBytes` before giving it to the library. Again, encryption operates on bytes, and the `blocksize` applies to the bytes being encrypted, not to the characters used to produce those bytes, so the padding needs to be added to the end of the actual bytes, not the `string`. This is a subtle but important thing to get right. – Remy Lebeau Jan 02 '19 at 07:32
  • Thanks Remy, finally I use DCPcrypt2, it's work on android. Related Quetion: https://stackoverflow.com/questions/43523262/delphi-fmx-dcpcrypt2-in-windows-produces-different-result-in-android-ios – 86153 Jan 02 '19 at 08:18
  • @86153 that other question is not the same issue as this one. The other question used string buffers for the bytes, and suffered from indexing issues across platforms. This question does not do that. – Remy Lebeau Jan 02 '19 at 16:37
0

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.

Dennies Chang
  • 564
  • 5
  • 15