I have problems with decrypting strings sent from PHP to Delphi using the rijndael cipher. I'm using mcrypt on the PHP side and DCP_rijndael on the Delphi side.
At the moment I have the below code.
PHP:
function encRJ($key, $iv, $data)
{
$r = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $data, MCRYPT_MODE_CBC, $iv);
$r = base64_encode($r);
return $r;
}
And in Delphi:
function decRJ(Data: string; Key: string; IV: string): string;
var ciph: TDCP_rijndael;
begin
Data := Base64DecodeStr(Data);
ciph:= TDCP_rijndael.Create(Self);
ciph.Init(Key[1], 256, @IV[1]);
ciph.DecryptCBC(Data[1], Data[1], Length(Data));
ciph.Free;
Result := Data;
end;
I have tried using several Units on the Internet implementing the cipher, and found out most people are saying about the DCP components. Even so, I haven't managed to make it correctly decrypt. I've tried using Byte arrays for the parameters, AnsiStrings, WideStrings, etc, but unfortunately no luck.
Excuse me if I'm missing something really obvious here, as my mind isn't in good shape atm, after hours of searching for the matter.
I seem to have spent too long on this but...
Your problem is the block size. TDCP_rijndael is equivalent to MCRYPT_RIJNDAEL_128 (not _256). The '256' value in ciph.Init(...) call is still correct though. Other than that it looks pretty much ok. That is, assuming you're using ansistrings for key/iv or you're using non-unicode Delphi.
For unicode Delphi versions I'd be inclined to use TBytes and key[0] / iv[0].
Padding may still be an issue. If so, then here's what I've mangled up based on the PHP manual pages and some trial and error.
PHP:
function Encrypt($src, $key, $iv)
{
$block = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, 'cbc');
//echo "Block size: " . $block . "\r\n";
$pad = $block - (strlen($src) % $block);
$src .= str_repeat(chr($pad), $pad);
$enc = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $src, MCRYPT_MODE_CBC, $iv);
$r = base64_encode($enc);
return $r;
}
function Decrypt($src, $key, $iv)
{
$enc = base64_decode($src);
$dec = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $enc, MCRYPT_MODE_CBC, $iv);
$block = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, 'cbc');
$pad = ord($dec[($len = strlen($dec)) - 1]);
return substr($dec, 0, strlen($dec) - $pad);
}
Delphi:
function DecryptData(Data: string; AKey: AnsiString; AIv: AnsiString): string;
var
key, iv, src, dest: TBytes;
cipher: TDCP_rijndael;
slen, pad: integer;
begin
//key := Base64DecodeBytes(TEncoding.UTF8.GetBytes(AKey));
//iv := Base64DecodeBytes(TEncoding.UTF8.GetBytes(AIv));
key := TEncoding.ASCII.GetBytes(AKey);
iv := TEncoding.ASCII.GetBytes(AIv);
src := Base64DecodeBytes(TEncoding.UTF8.GetBytes(Data));
cipher := TDCP_rijndael.Create(nil);
try
cipher.CipherMode := cmCBC;
slen := Length(src);
SetLength(dest, slen);
cipher.Init(key[0], 256, @iv[0]); // DCP uses key size in BITS not BYTES
cipher.Decrypt(src[0], dest[0], slen);
// Remove the padding. Get the numerical value of the last byte and remove
// that number of bytes
pad := dest[slen - 1];
SetLength(dest, slen - pad);
// Base64 encode it
result := TEncoding.Default.GetString(dest);
finally
cipher.Free;
end;
end;
function EncryptData(Data: string; AKey: AnsiString; AIv: AnsiString): string;
var
cipher: TDCP_rijndael;
key, iv, src, dest, b64: TBytes;
index, slen, bsize, pad: integer;
begin
//key := Base64DecodeBytes(TEncoding.UTF8.GetBytes(AKey));
//iv := Base64DecodeBytes(TEncoding.UTF8.GetBytes(AIv));
key := TEncoding.ASCII.GetBytes(AKey);
iv := TEncoding.ASCII.GetBytes(AIv);
src := TEncoding.UTF8.GetBytes(Data);
cipher := TDCP_rijndael.Create(nil);
try
cipher.CipherMode := cmCBC;
// Add padding.
// Resize the Value array to make it a multiple of the block length.
// If it's already an exact multiple then add a full block of padding.
slen := Length(src);
bsize := (cipher.BlockSize div 8);
pad := bsize - (slen mod bsize);
Inc(slen, pad);
SetLength(src, slen);
for index := pad downto 1 do
begin
src[slen - index] := pad;
end;
SetLength(dest, slen);
cipher.Init(key[0], 256, @iv[0]); // DCP uses key size in BITS not BYTES
cipher.Encrypt(src[0], dest[0], slen);
b64 := Base64EncodeBytes(dest);
result := TEncoding.Default.GetString(b64);
finally
cipher.Free;
end;
end;
The PHP and Delphi functions now give me the same answer.
EDIT
Base64DecodeBytes was a bit of code I added to the DCP Base64 unit:
function Base64DecodeBytes(Input: TBytes): TBytes;
var
ilen, rlen: integer;
begin
ilen := Length(Input);
SetLength(result, (ilen div 4) * 3);
rlen := Base64Decode(@Input[0], @result[0], ilen);
// Adjust the length of the output buffer according to the number of valid
// b64 characters
SetLength(result, rlen);
end;
EDIT 2018 (Raising the dead...):
As requested, here is the encoding method, unchecked and pulled straight from an old source file I found.
DISCLAIMER: It is many years old and untested in recent memory and not used since Delphi 2010. There are probably many better alternatives now. Use at your own risk.
function Base64EncodeBytes(Input: TBytes): TBytes;
var
ilen: integer;
begin
ilen := Length(Input);
SetLength(result, ((ilen + 2) div 3) * 4);
Base64Encode(@Input[0], @result[0], ilen);
end;