Delphi DEC library (Rijndael) encryption
Asked Answered
C

3

8

I am trying to use the DEC 3.0 library (Delphi Encryption Compedium Part I) to encrypt data in Delphi 7 and send it to a PHP script through POST, where I am decrypting it with mcrypt (RIJNDAEL_256, ECB mode).

Delphi part:

uses Windows, DECUtil, Cipher, Cipher1;

function EncryptMsgData(MsgData, Key: string): string;
var RCipher: TCipher_Rijndael;
begin
  RCipher:= TCipher_Rijndael.Create(KeyStr, nil);
  RCipher.Mode:= cmECB;
  Result:= RCipher.CodeString(MsgData, paEncode, fmtMIME64);
  RCipher.Free;
end;

PHP part:

function decryptMsgContent($msgContent, $sKey) {
    return mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $sKey, base64_decode($msgContent), MCRYPT_MODE_ECB, mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND));
}

The problem is that the decryption from PHP doesn't work and the output is gibberish, differing from the actual data.

Of course, Delphi Key and PHP $Key is the same 24 characters string.

Now I know DEC 3.0 is old and outdated, and I'm not an expert in encryption and can't tell if the inplementation is actually Rijndael 256. Maybe someone can tell me how this implementation differs from PHP's mcrypt w/ RIJNDAEL_256. Maybe the keysize is different, or the block size, but can't tell this from the code. Here's an excerpt from Cipher1.pas:

const
{ don’t change this }
  Rijndael_Blocks =  4;
  Rijndael_Rounds = 14;

class procedure TCipher_Rijndael.GetContext(var ABufSize, AKeySize, AUserSize: Integer);
begin
  ABufSize := Rijndael_Blocks * 4;
  AKeySize := 32;
  AUserSize := (Rijndael_Rounds + 1) * Rijndael_Blocks * SizeOf(Integer) * 2;
end;

Side question:

I know ECB mode isn't recommended and I'll use CBC as soon as I get ECB working. The question is, do I have to transmit the generated IV in Delphi to the PHP script also? Or knowing the key is sufficient, like for ECB?

Cursorial answered 10/2, 2012 at 10:43 Comment(7)
This may be a very stupid question. But using delphi are you able to decrypt your encrypted data? Oh, and does the answer to this question help: https://mcmap.net/q/1469118/-dcpcrypt-delphi-not-properly-encoding-rijndael/41338Cabotage
You call mcrypt_create_iv(). What's the IV you used in Delphi?Petes
@ldsandon: talereader is using ECB mode. There is no IV.Guenna
Hope PHP knows that - don't know what happens with the call mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND)); maybe it is simply ignored (I hope), maybe it triggers something bad. If it returns False it could pass a bad parameter to mcrypt_decrypt.Petes
@Isandon Thought of that, tested encryption/decryption in PHP by generating the IV both in encryption and decryption, and the output is ok. So looks like in ECB, mcrypt ignores the passed IV.Cursorial
@Cabotage If I can't solve this with the current implementation (DEC) I'll probably resolve to another implementation (DCPCrypt, Lockbox looks good, or even latest DEC update), but still this puzzles me. I've used this DEC library for so long and never had problems if I used it in Delphi application only.Cursorial
@Cabotage and no, it's not a stupid question actually. It's basic debugging, amd didn't think to test it on this setup until now, because I've used it before in other applications. The algorithm in Delphi works (crypt then decrypt). Henrick Hellström pointed to the problem.Cursorial
D
6

You are calling the TCipher.Create(const Password: String; AProtection: TProtection); constructor, which will compute a hash of the password before passing it to the Init method, which performs the standard key schedule of the implemented algorithm. To override this key derivation, use:

function EncryptMsgData(MsgData, Key: string): string;
var RCipher: TCipher_Rijndael;
begin
  RCipher:= TCipher_Rijndael.Create('', nil);
  RCipher.Init(Pointer(Key)^,Length(Key),nil);
  RCipher.Mode:= cmECB;
  Result:= RCipher.CodeString(MsgData, paEncode, fmtMIME64);
  RCipher.Free;

end;

Dukie answered 10/2, 2012 at 11:36 Comment(7)
Thank you, this put me on the right track. I am actually using my own password transformation (omitted from the code posted) that takes only the first 24 characters of the sha1 of PasswordSecret + Timestamp (that goes along in the POST data). I was obliged to do this when I got an error in PHP that the mcrypt accepted a key of maximum 24 chars.Cursorial
The other part of the problem was that the DEC implementation is actually 128 bits Rijndael. Found that by changing the PHP decrypt function to use RIJNDAEL_128. Now the output of the PHP decryption function has the original data + some gibberish at the end. From what I've read previously in other posts on stackoverflow, there's a padding problem around here somewhere that I have to figure out.Cursorial
Also, by looking at the DEC source, would it be possible to make some modifications to transform it into a variant of block size of 128 bits and a key size of 256 (AES 256), so it would couple with mcrypt w/ RIJNDAEL_256 ?Cursorial
Right, DEC will not pad your plain text in ECB mode, which means the plain text you pass to your function has to have a length that is a multiple of the block size of the cipher (16 for Rijndael/AES). PHP uses zero padding by default, so you should pad your MsgData with StringOfChar(#0,16-(Length(MsgData) mod 16)), presuming you use a non-unicode version of Delphi.Guenna
DEC implements Rijndael with a block size of 128 bits and key sizes 128, 192 and 256 bits. If the key you pass to the Init method has length <= 16, AES-128 will be used, 16 < length <= 24 AES-192 will be used, and AES-256 otherwise. However, to guarantee interoperatibility with mcrypt (or any other implementation), you should make sure the length of the key is exactly 32 if you want AES-256.Guenna
According to the available documentation MCRYPT_RIJNDAEL_128 corresponds to AES (Rijndael with a 128 bit block size), regardless of the key size. MCRYPT_RIJNDAEL_256 is not AES and is not supported by DEC.Guenna
Ok, so 1). I was mistaking MCRYPT_RIJNDAEL_128/256 as refering to the key size, when in actuality 128 stands for the block size. 2). Done what you said with the padding and it works like a charm. 3). That's why many examples of mcrypt out there use trim() on the resulted (decoded) text, because of the padding. Hmm. Learning is so great. Thank you very much!Cursorial
C
2

OK, so to sum this up, there were 3 problems with my code:

  1. Due to my poor understanding of mcrypt and ciphers in general, MCRYPT_RIJNDAEL_256 refers to 128 bits block and doesn't refer to the keysize. My correct choice should have been MCRYPT_RIJNDAEL_128, which is the AES standard and is also supported by DEC 3.0.

  2. DEC has it's own default key derivation, so I needed to bypass it so I wouldn't have to implement it in PHP also. In actuality, I am using my own key derivation algorithm that was easy to reproduce in PHP (first 32 characters of sha1(key)).

  3. DEC doesn't pad plaintext to a multiple of the block size of the cipher, as mcrypt expects, so I had to do it manually.

Providing working code below:

Delphi:

uses Windows, DECUtil, Cipher, Cipher1, CryptoAPI;

function EncryptMsgData(MsgData, Key: string): string;
var RCipher: TCipher_Rijndael;
    KeyStr: string;
begin
  Result:= '';
  try
    // key derivation; just making sure to feed the cipher a 24 chars key
    HashStr(HASH_SHA1, Key, KeyStr);
    KeyStr:= Copy(KeyStr, 1, 24);
    RCipher:= TCipher_Rijndael.Create('', nil);
    RCipher.Init(Pointer(KeyStr)^, Length(KeyStr), nil);
    RCipher.Mode:= cmECB;
    Result:= RCipher.CodeString(MsgData + StringOfChar(#0,16-(Length(MsgData) mod 16)), paEncode, fmtMIME64);
    RCipher.Free;
  except
  end;
end;

PHP:

function decryptMsgContent($msgContent, $sKey) {
    $sKey = substr(sha1(sKey), 0, 24);
    return trim(mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $sKey, base64_decode($msgContent), MCRYPT_MODE_ECB, mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB), MCRYPT_RAND)));
}
Cursorial answered 10/2, 2012 at 14:35 Comment(3)
Uhm. SHA-1 will only output 20 bytes. The easiest solution is probably to reduce the key size to 128 bits/16 bytes. Another alternative is to use SHA-256 and switch from DEC 3.0 to another library that supports SHA-256, such as <shameless plug>OpenStrSecII or StreamSec Tools</shameless plug> or DCP Crypt. A third alternative is to use SHA-1 with key stretching, but that might easily get complicated to implement in PHP, and DEC 3.0 doesn't have any of the PKCS#5 v2.0 functions implemented.Guenna
Am I correct to say that the code above actually uses AES-128, and not 192, even though the key is 24 chars in length? Should I decrease de key length to 16? Because I'll stick to AES-128 for the moment, It's a small project. Also, I switched to CBC, let DEC generate the IV internally and I just pass the IV base64encoded along with the data.Cursorial
It uses AES-192, but there are at most 160 bits of entropy in the key (bounded by the output length of SHA-1), and the last 4 bytes of the key (#21 to #24) are undefined (although probably 0 since you got it working).Guenna
P
0

A 256 bit key I found is 32 charachters, or 32 bytes. Not 24. This may be the issue.

[EDIT]

I combined everyone's ideas (ansistring, etc) into one single idea with a fix.

Also, you are using codestring( -- it should be Encodestring(

I pasted working Encrypt and Decrypt source below:


function EncryptMsgData(MsgData, Key: AnsiString): AnsiString;
var RCipher: TCipher_Rijndael;
begin
  RCipher:= TCipher_Rijndael.Create('', nil);
  RCipher.Init(Pointer(Key)^,Length(Key),nil);
  RCipher.Mode:= cmCBC;
  Result:= RCipher.EncodeString(MsgData);
  RCipher.Free;
end;

function DecryptMsgData(MsgData, Key: AnsiString): AnsiString;
var RCipher: TCipher_Rijndael;
begin
  RCipher:= TCipher_Rijndael.Create('',nil);
  RCipher.Init(Pointer(Key)^,Length(Key),nil);
  RCipher.Mode:= cmCBC;
  Result:= RCipher.DecodeString(MsgData);
  RCipher.Free;
end;

Use that with a 32 charachter key and you get proper encryption and decryption.

In order to store and use the encrypted data as a string you may want to use Base64Encode(

But do not forget to Base64Decode prior to decrypting.

This is the same technique needed for Blowfish. Sometimes the charachters actually are like a backspace, and perform the function rather than showing on screen. Base64Encode basically converts the charachters to something you can display in text.

Prior to transferring the encoded data across the internet or to another application in the same or another language, you MUST base64encode and decode in order to not loose data. Don't forget it in PHP too!

Planospore answered 19/10, 2014 at 0:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.