1
votes

I am using Delphi 10.3.2 I am trying to use the DCPCrypt units with the firemonkey framework to encrypt a string. It works 100% on Win32, Win64 and macOS 32 targets, the result is always the same. But when I compile for macOS 64, the result is different.

This is the code used:

function EncodeAES(code:ansistring; key:ansistring):string;
var
    s,u:ansistring;
  enc: TEncoding;
  k,iv, Data, Crypt: TBytes;
  Cipher: TDCP_rijndael;
begin
    u:='';
    enc:=TEncoding.ANSI;
    Data := enc.GetBytes(code); // VMpuXJGbUNOv
    k:=enc.GetBytes(key);   // kj3214ed)k32nre2
    iv:=enc.GetBytes(u);
    Cipher:=TDCP_rijndael.Create(nil);
    Cipher.Init(K[0], 128, @iv[0]);
    Crypt:=Copy(Data, 0, Length(Data));
    BytePadding(Crypt, Cipher.BlockSize, pmPKCS7);
    Cipher.EncryptECB(Crypt[0], Crypt[0]);
    Cipher.Free;
    s:=tencoding.ANSI.GetString(crypt);
    result:=StringToHex(s);
end;

and this is the BytePadding function:

procedure BytePadding(var Data: TBytes; BlockSize: integer; PaddingMode: TPaddingMode);
var
    I, DataBlocks, DataLength, PaddingStart, PaddingCount: integer;
begin
    BlockSize := BlockSize div 8;
    if PaddingMode in [pmZeroPadding, pmRandomPadding] then
        if Length(Data) mod BlockSize = 0 then Exit;
    DataBlocks := (Length(Data) div BlockSize) + 1;
    DataLength := DataBlocks * BlockSize;
    PaddingCount := DataLength - Length(Data);
    if PaddingMode in [pmANSIX923, pmISO10126, pmPKCS7] then
        if PaddingCount > $FF then Exit;
    PaddingStart := Length(Data);
    SetLength(Data, DataLength);
    case PaddingMode of
        pmZeroPadding, pmANSIX923, pmISO7816: // fill with $00 bytes
            FillChar(Data[PaddingStart], PaddingCount, 0);
        pmPKCS7: // fill with PaddingCount bytes
            FillChar(Data[PaddingStart], PaddingCount, PaddingCount);
        pmRandomPadding, pmISO10126: // fill with random bytes
            for I := PaddingStart to DataLength-1 do Data[I] := Random($FF);
    end;
    case PaddingMode of
        pmANSIX923, pmISO10126:
            Data[DataLength-1] := PaddingCount; // set end-marker with number of bytes added
        pmISO7816:
            Data[PaddingStart] := $80; // set fixed end-markder $80
    end;
end;

I call the function like:

procedure TForm1.BtnClick(Sender: TObject);
var
    s:string;
begin
    s:=EncodeAES('VMpuXJGbUNOv','kj3214ed)k32nre2');
end;

Result with Win32/Win64/macOS 32 (correct): E32A9DE47CC60BDB70CA27885128D17A

Result with macOS 64 (wrong): CF622155545E485AC3A083E8A0478493

What am I doing wrong?

1
Argh!!!! Not again. So many questions which treat binary and text as interchangeable. Please make it stop! Seriously though, encryption algorithms operate on binary. Your encryption functions need to use byte arrays as input and output.David Heffernan
I assume when the data, crypt, iv and k variables are declared as TBytes, they ARE byte arrays.user7415109
@Softtouch yes they are, but you are responsible for populating them with valid bytes correctly and consistently across machines/platforms, and your EncodeAES() code is simply not doing that.Remy Lebeau
DCPCrypt2 has an incorrect definition in DCPcrypt2.pas. dword = longword should be: dword = Cardinal. Longword is 8 bytes on 64-bit Posix systemsDave Nottage
@DaveNottage I would opt for UInt32 instead of CardinalRemy Lebeau

1 Answers

4
votes

Encryption operates on binary data, not textual data. When dealing with text, you have to encode characters to bytes before encryption, and then decode bytes to characters after decryption. Which means using the same character encoding before encryption and after decryption. You are attempting to do that encoding, but you are not accounting for the fact that TEncoding.ANSI is NOT portable across OS platforms, or even across different machines using the same OS platform. For better portability, you need to use a consistent encoding, such as TEncoding.UTF8.

Also, TEncoding operates only with UnicodeString, so using TEncoding.GetBytes() and TEncoding.GetString() with AnsiString will perform implicit conversions between ANSI and Unicode using the RTL's definition of ANSI, not yours, producing bytes you are likely not expecting if your strings contain any non-ASCII characters in them.

Your EncodeAES() function is best off using (Unicode)String for all of its string handling and forget that AnsiString even exists. Although platforms like Linux are largely UTF-8 systems, Delphi's default (Unicode)string uses UTF-16 on all platforms. If you want to encode ANSI strings, use RawByteString to avoid any implicit conversions if calling code wants to use UTF8String, AnsiString(N), etc. Encrypt the 8bit characters as-is without using TEncoding at all. Use TEncoding only for UnicodeString data.

Lastly, your EncodeAES() function should not be decoding encrypted bytes to a UnicodeString just to convert that to a hex string. You should be hex-encoding the encrypted bytes as-is.

Try this instead:

function EncodeAES(const Data: TBytes; const Key: string): string; overload;
const
  Hex: array[0..15] of Char = ('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F');
var
  enc: TEncoding;
  iv, k, Crypt: TBytes;
  Cipher: TDCP_rijndael;
  B: Byte;
  I: Integer;
begin
  enc := TEncoding.UTF8;
  iv := enc.GetBytes('');
  k := enc.GetBytes(Key);

  Cipher := TDCP_rijndael.Create(nil);
  try
    Cipher.Init(k[0], 128, @iv[0]);
    Crypt := Copy(Data, 0, Length(Data));
    BytePadding(Crypt, Cipher.BlockSize, pmPKCS7);
    Cipher.EncryptECB(Crypt[0], Crypt[0]);
  finally
    Cipher.Free;
  end;

  SetLength(Result, Length(Crypt)*2);
  I := Low(Result);
  for B in Crypt do
  begin
    Result[ I ] := Hex[(B shr 4) and $F];
    Result[I+1] := Hex[B and $F];
    Inc(I, 2);
  end;
end;

function EncodeAES(const S, Key: string): string; overload;
begin
  Result := EncodeAES(TEncoding.UTF8.GetBytes(S), Key);
end;

function EncodeAES(const S: RawByteString; const Key: string): string; overload;
begin
  Result := EncodeAES(BytesOf(S), Key);
end;