5
votes

I have an application developed in Delphi 2007 in which a value is encrypted by PHP and decrypted in the application. Encryption algorithm is RIJNDAEL 128. When I moved the XE2 and installed the latest version of DCPcrypt the application run but is no longer able to decrypt the encrypted string from PHP. The result appears like Chinese characters so I am wondering if I need to modify my handling of the encryption key, vector, or encrypted string to account for the fact that XE2 uses Unicode characters.

The PHP encryption is performed by the following: (mcrypt_cbc(MCRYPT_RIJNDAEL_128,$key,$date_str,MCRYPT_ENCRYPT,$iv))

And the two relevant Delphi functions are here:

function PadWithZeros(const str : string; size : integer) : string;
var
  origsize, i : integer;
begin
  Result := str;
  origsize := Length(Result);
  if ((origsize mod size) <> 0) or (origsize = 0) then
  begin
    SetLength(Result,((origsize div size)+1)*size);
    for i := origsize+1 to Length(Result) do
      Result[i] := #0;
  end;
end;

procedure TfrmMain.btnDecryptClick(Sender: TObject);
var
  Cipher : TDCP_rijndael;
  Data, Key, IV : string;
begin
  // Pad Key and IV with zeros as appropriate
  Key := PadWithZeros(boxKey.Text,KeySize);
  IV := PadWithZeros(boxIV.Text,BlockSize);
  // Decode the Base64 encoded string
  Data := Base64DecodeStr(boxCipherTextIn.Text);
  // Create the cipher and initialise according to the key length
  Cipher := TDCP_rijndael.Create(Self);
  if Length(boxKey.Text) <= 16 then
    Cipher.Init(Key[1],128,@IV[1])
  else if Length(boxKey.Text) <= 24 then
    Cipher.Init(Key[1],192,@IV[1])
  else
    Cipher.Init(Key[1],256,@IV[1]);
  // Decrypt the data
  Cipher.DecryptCBC(Data[1],Data[1],Length(Data));
  // Free the cipher and clear sensitive information
  Cipher.Free;
  FillChar(Key[1],Length(Key),0);
  // Display the result
  boxPlainTextOut.Text := Data;
end;
2
If it worked in D2007, and stopped with the upgrade to XE2, the problem is pretty obviously related to Ansi/Unicode. Change the references from string to AnsiString in your Delphi code and see if that solves the problem.Ken White
Not necessarily Ken. In this case quite possibly, but it is a mistake to automatically reach for the "Unicode Problem" just because an issue starts arising in D2009. I had a problem that reared it's head in D2009 that was seemingly caused by a change in the way the compiler handled the left-over bytes in a parameter declared of the wrong size in an external API function decl. stackoverflow.com/a/3527955/123487Deltics

2 Answers

10
votes

The issue is related to character encoding but it is not that DCPCrypt is incapable of handling UTF-16.

PHP strings are UTF-8. So you are passing the password ('Key') from PHP to Dephi as a base64 encoded string. Bizarrely you are storing the decoded key in a UTF-16LE string. More appropriate would have been a rawbytestring or a TBytes or a TMemoryStream. The binary layout of the payload of Key is now different to what it would be on the encoding side because it is typed as a UTF16-LE (In incorrect Delphi terminology, 'unicode string' - A Microsoft and Embarcadero malapropism).

Also this line of code is wrong in a 'unicode' (sic) compiler for obvious reasons...

FillChar(Key[1],Length(Key),0);

Side note

I am the author of TurboPower LockBox 3, and the number one issue I get in forums springs from confusion between ansistrings, utf-8 strings and utf-16le strings. A lot of people think that the password 'abc' as PHP understands it, is the same password as 'abc' in Delphi 2010. What cryptographic libraries use from password strings is the binary payload resulting from the encoding of the string, not its semantic meaning as a string.

5
votes

The application now compiles properly in XE2 and is able to decrypt records from the version compiled with Delphi 2007. Thanks to all for your comments. The different perspectives help to make clear when variables needed to be defined as ANSI string.

In case anyone else is trying a similar update and needs to decrypt cipher text generated in PHP, my decrypt function and the supporting padWithZeros() function came from the DCPcrypt site so I posting my modified code here. I should also not that I installed the 7/21/2012 update to DCPcrypt for XE2 made available at the following location: http://www.pepak.net/files/tools/dcpcrypt.zip.

function TForm1.AESDecrypt(const DecKeyStr: AnsiString;
                              const DecIVStr: AnsiString;
                              const CypherTextIn: AnsiString): String;
var
  Cipher : TDCP_rijndael;
  Data, Key, IV : AnsiString;
begin
  // Pad Key and IV with zeros as appropriate
  Key := PadWithZeros(DecKeyStr,KeySize);
  IV := PadWithZeros(DecIVStr,BlockSize);
  // Decode the Base64 encoded string
  Data := Base64DecodeStr(CypherTextIn);
  // Create the cipher and initialise according to the key length
  Cipher := TDCP_rijndael.Create(nil);
  if Length(DecKeyStr) <= 16 then
    Cipher.Init(Key[1],128,@IV[1])
  else if Length(DecKeyStr) <= 24 then
    Cipher.Init(Key[1],192,@IV[1])
  else
    Cipher.Init(Key[1],256,@IV[1]);
  // Decrypt the data
  Cipher.DecryptCBC(Data[1],Data[1],Length(Data));
  // Free the cipher and clear sensitive information
  Cipher.Free;
  FillChar(Key[1],Length(Key),0);
  // Display the result
  Result := String(Data);
(* *)
end;

function TForm1.PadWithZeros(const str:AnsiString; size:integer):AnsiString;
var
  origsize, i : integer;
begin
  Result := str;
  origsize := Length(Result);
  if ((origsize mod size) <> 0) or (origsize = 0) then
  begin
    SetLength(Result,((origsize div size)+1)*size);
    for i := origsize+1 to Length(Result) do
      Result[i] := #0;
  end;
end;