Different results when encrypting with "same" method in Delphi and PHP - php

I am trying to exchange encrypted messages between Delphi and PHP.
From the Delphi side I downloaded DCPcrypt v2 Beta 3 from here:
http://www.cityinthesky.co.uk/opensource/dcpcrypt/
For encrypting I use this function:
function TForm1.Encrypt3DES(psData, psKey: string): string;
var
Cipher: TDCP_3des;
begin
Cipher:= TDCP_3des.Create(nil);
Cipher.InitStr(psKey,TDCP_sha256);
result:=Cipher.EncryptString(psData);
Cipher.Burn;
Cipher.Free;
end;
And I am testing it like this:
ShowMessage(Encrypt3DES('test','SecretKeySecretKeySecret'));
The result that I get is Z74E0Q== and I can successfully decrypt it with another similar delphi function:
function TForm1.Decrypt3DES(psData, psKey: string): string;
var
Cipher: TDCP_3des;
begin
Cipher:= TDCP_3des.Create(nil);
Cipher.InitStr(psKey, TDCP_sha256);
result:=Cipher.DecryptString(psData);
Cipher.Burn;
Cipher.Free;
end;
From PHP side I tried several function to encrypt the same string ('test') with the same key ('SecretKeySecretKeySecret') but the result is different from what I get in Delphi. Again I can successfully decrypt the messages in PHP with similar functions but I need to decrypt messages in Delphi.
This is what I do in PHP, I even tried to hash the key as I see Delphi function is using TDCP_sha256 but still results are diferent.
$key = "SecretKeySecretKeySecret";
echo base64_encode(mcrypt_encrypt(MCRYPT_3DES, $key, 'test', 'ecb')).'<BR><BR>';
echo openssl_encrypt('test', 'des-ede3', $key).'<BR><BR>';
$key = hash('sha256', $key);
echo openssl_encrypt('test', 'des-ede3', $key).'<BR><BR>';
This is the result:
Z05z5Bp4/vY=
L5qmk5nJOzs=
bm7yRdrMs5g=
What am I doing wrong? BTW I am using Delphi 7 and DCPcrypt is the only library for now that I managed to make it run.

I think that this will help you. TDCP_3des is blockcipher and EncryptString method uses EncryptCFB8bit method (Encrypt size bytes of data using the CFB (8 bit) method of encryption).
Two things are important:
use the same initialisation vector
hash the key in PHP part.
Delphi part:
function TForm1.Encrypt3DES(psData, psKey: string): string;
var
Cipher: TDCP_3des;
i: integer;
begin
Cipher := TDCP_3des.Create(nil);
try
Cipher.InitStr(psKey, TDCP_sha256);
Cipher.SetIV('00000000');
Result := Cipher.EncryptString(psData);
Cipher.Burn;
finally
Cipher.Free;
end{try};
end;
procedure TForm1.btnEncryptClick(Sender: TObject);
var
input, key: string;
begin
input := 'Some words in English';
key := 'SecretKeySecretKeySecret';
ShowMessage(Encrypt3DES(input, key));
end;
PHP part:
<?
$key = "SecretKeySecretKeySecret";
$key = hash('sha256', $key, true);
$key = substr($key, 0, 24);
$iv = '00000000';
$message = 'Some words in English';
$result = mcrypt_encrypt(MCRYPT_3DES, $key, $message, MCRYPT_MODE_CFB, $iv);
$result = base64_encode($result);
echo 'Input text: '.$message.'</br>';
echo 'Encrypted text: '.$result.'</br>';
?>
Output:
Input: Some words in English
Encrypted text: hTpdn+USolFTgv/4HnBEvo4scgmp
Input: This will test Delphi7 and PHP encryption.
Encrypted text: gik2Iw/m2rtMA9gdKqvFqDg3kuUSb4rnAieyZ8unIvt510Rbt1jLPO+/
Input: I hope this will work.
Encrypted text: n/JxW12zORaI7TSCAF4/6cBxqC3mZg==
Notes:
Tested with Delphi 7, DCPcrypt v2, PHP 5.2.10, mcrypt 2.5.7.

The length of your base64 ciphertext shows that DCPCrypt does not use ECB. With a minimal program I can reproduce your result and stepping through the code indeed shows that ECB is not used. The relevant parts are
function TDCP_blockcipher.EncryptString(const Str: string): string;
begin
SetLength(Result,Length(Str));
EncryptCFB8bit(Str[1],Result[1],Length(Str));
Result:= Base64EncodeStr(Result);
end;
function TDCP_blockcipher.DecryptString(const Str: string): string;
begin
Result:= Base64DecodeStr(Str);
DecryptCFB8bit(Result[1],Result[1],Length(Result));
end;

Related

How to create openssl encryption and decryption equivalent of php code in nodejs application

I have an application running on php which have some values encrypted using openssl encrption by using the code below
<?php
define('OSSLENCKEY','14E2E2D1582A36172AE401CB826003C1');
define('OSSLIVKEY', '747E314D23DBC624E971EE59A0BA6D28');
function encryptString($data) {
$encrypt_method = "AES-256-CBC";
$key = hash('sha256', OSSLENCKEY);
$iv = substr(hash('sha256', OSSLIVKEY), 0, 16);
$output = openssl_encrypt($data, $encrypt_method, $key, 0, $iv);
$output = base64_encode($output);
return $output;
}
function decryptString($data){
$encrypt_method = "AES-256-CBC";
$key = hash('sha256', OSSLENCKEY);
$iv = substr(hash('sha256', OSSLIVKEY), 0, 16);
$output = openssl_decrypt(base64_decode($data), $encrypt_method, $key, 0, $iv);
return $output;
}
echo encryptString("Hello World");
echo "<br>";
echo decryptString("MTZHaEoxb0JYV0dzNnptbEI2UXlPUT09");
?>
I have another endpoint which runs on nodejs where I need to decrypt and encrypt values based on the above php encrypt/decrypt rule.
I have searched but could'nt find a solution for this.
I tried with the library crypto But ends up with errors Reference
My nodejs code which I have tried is given below
message = 'MTZHaEoxb0JYV0dzNnptbEI2UXlPUT09';
const cypher = Buffer.from(message, "base64");
const key = crypto.createHash('sha256').update('14E2E2D1582A36172AE401CB826003C1');//.digest('hex');
// $iv = substr(hash('sha256', '747E314D23DBC624E971EE59A0BA6D28'), 0, 16); from php returns '0ed9c2aa27a31693' need nodejs equivalent
const iv = '0ed9c2aa27a31693';
const decipher = crypto.createDecipheriv("aes-256-cbc", key, iv);
console.log( decipher.update(contents) + decipher.final());
Someone please help me to find a nodejs code for openssl encryption and decyption
Thanks in advance
There are the following problems in the code:
The key is returned hex encoded in the PHP code, so in the NodeJS code for AES-256 only the first 32 bytes must be considered for the key (PHP does this automatically).
The PHP code Base64 encodes the ciphertext implicitly, so because of the explicit Base64 encoding the ciphertext is Base64 encoded twice (which is unnecessary). Therefore, a double Base64 encoding is necessary in the NodeJS code as well.
Also, note that using a static IV is insecure (but you are probably only doing this for testing purposes).
The following NodeJS code produces the same ciphertext as the PHP code:
const crypto = require('crypto');
const plain = 'Hello World';
const hashKey = crypto.createHash('sha256');
hashKey.update('14E2E2D1582A36172AE401CB826003C1');
const key = hashKey.digest('hex').substring(0, 32);
const hashIv = crypto.createHash('sha256');
hashIv.update('747E314D23DBC624E971EE59A0BA6D28');
const iv = hashIv.digest('hex').substring(0, 16);
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
var encrypted = cipher.update(plain, 'utf-8', 'base64');
encrypted += cipher.final('base64');
encrypted = Buffer.from(encrypted, 'utf-8').toString('base64');
console.log(encrypted); // MTZHaEoxb0JYV0dzNnptbEI2UXlPUT09
encrypted = Buffer.from(encrypted, 'base64').toString('utf-8');
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
var decrypted = decipher.update(encrypted, 'base64', 'utf-8');
decrypted += decipher.final('utf-8');
console.log(decrypted); // Hello World
EDIT:
As mentioned in the comments, PHP's hash() method returns the hash as a hexadecimal string by default (unless the third parameter is explicitly set to true, which is not the case in the reference code). This doubles the length, because in this encoding each byte of the hash is represented by two hex digits (hexits), i.e. 2 bytes.
Therefore it is necessary to shorten the key in the NodeJS code (see the first point of my original answer). This shortening is not necessary in the PHP code, since PHP does this implicitly (which is actually a design flaw, since this way the user does not notice a possible issue with the key).
The use of the hex string has two disadvantages:
With a hex encoded string, each byte consists of 16 possible values (0-15), as opposed to 256 possible values of a byte (0-255). This reduces the security from 256 bit to 128 bit (which is arithmetically equivalent to AES-128), see here.
Depending on the platform, the hexits a-f can be represented as lowercase or uppercase letters, which can result in different keys and IVs (without explicit agreement on one of the two cases).
For these reasons it is more secure and robust to use the raw binary data of the hash instead of the hex encoded strings. If you want to do this, then the following changes are necessary.
In the PHP code:
$key = hash('sha256', OSSLENCKEY, true);
$iv = substr(hash('sha256', OSSLIVKEY, true), 0, 16);
in the NodeJS code:
const key = hashKey.digest();
const iv = hashIv.digest().slice(0, 16)
Note, however, that this version is not compatible with the old one, i.e. encryptions before this change cannot be decrypted after the change. So the old data would have to be migrated.

decrypt string in AES using php

I am currently working on encryption and decryption. I have encrypted my api key using https://medium.com/#amitasaurus/encrypting-decrypting-a-string-with-aes-js-1d9efa4d66d7 like below
var api_key = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
var d = new Date();
var n = d.getTime();
var final_key = api_key+'/'+n;
var encrypted = CryptoJS.AES.encrypt('encryption', final_key);
var encrypted_key = encrypted.toString();
and passed the encrypted key to the server side. I used
<?php
$key = pack("H*", "0123456789abcdef0123456789abcdef");
$iv = pack("H*", "abcdef9876543210abcdef9876543210");
$encrypted = base64_decode('U2FsdGVkX19gHSzwsrc5H9K6rqDYr2E8oYoVNSp8INU=');
$decrypt_string = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $encrypted, MCRYPT_MODE_CBC, $iv);
echo $decrypt_string;
?>
for decrypting the encrypted string. When i print decrypted string , it is like this ���9Һ��دa<��5*| աT�;��놻��V�[�}��ID-�}��硵�
Any suggestions to print as decoded string?
mcryptdefaults to zero padding. That means that, no matter what kind of ciphertext and key combination you are using, that the unpadding will not fail. Instead, it just returns invalid, randomized plaintext.
CryptoJS by default uses OpenSSL key derivation from a given password. Your decryption will return randomized plaintext as long as you cannot mimic the final AES key value that is generated by CryptoJS.
Modern modes such as GCM include an authentication tag with the ciphertext so that the validity of the ciphertext / key combination is ensured, or a verification error will be generated. Note that CBC mode is absolutely not secure when directly used for transport mode security.

Delphi: AES CBC encryption compatible with PHP mcrypt_encrypt

I'm trying to encrypt data in Delphi then decrypt it in PHP, but the Delphi output differs from what I get with PHP, so I am first testing whether both the PHP and Delphi output from encrypting a string is the same:
Delphi encryption, using DCPcrypt:
Uses DCPcrypt2, DCPblockciphers, DCPrijndael;
var Cipher : TDCP_rijndael;
Data, Key, IV : Ansistring;
begin
key := '12345678901234567890123456789012';
iv := '1234567890123456';
Data := 'thisis128bitstxt';
Cipher := TDCP_rijndael.Create(nil);
Cipher.Init(Key[1],128,#IV[1]);
Cipher.EncryptCBC(Data[1],Data[1],Length(Data));
Cipher.Free;
With TMemoryStream.Create do begin
Write(Data[1],Length(Data));
SaveToFile('z:\delphi');
Free;
end;
Now the code in PHP:
<?php
$source = "thisis128bitstxt"; //128-bits block
$key = "12345678901234567890123456789012"; // 32
$iv = "1234567890123456"; // 16
$source = mcrypt_encrypt(MCRYPT_RIJNDAEL_128,$key,$source,MCRYPT_MODE_CBC,$iv);
$out = fopen ('php', 'w');
fwrite ($out, $source);
fclose ($out);
?>
Results:
What's wrong?
Change:
$source = mcrypt_encrypt(MCRYPT_RIJNDAEL_128,$key,$source,MCRYPT_MODE_CBC,$iv);
to:
$source = mcrypt_cbc(MCRYPT_RIJNDAEL_128,$KEYkey$source,MCRYPT_ENCRYPT,$iv);

Ruby to PHP AES

Im trying to convert some ruby code that encrypts data with AES 265 in CBC mode to php but its not working, the converted php code returns a null string. Here is what i have:
Ruby:
require 'openssl'
module AESCrypt
def self.encrypt(message, password)
Base64.encode64(self.encrypt_data(message.to_s.strip, self.key_digest(password), nil, "AES-256-CBC"))
end
def self.decrypt(message, password)
base64_decoded = Base64.decode64(message.to_s.strip)
self.decrypt_data(base64_decoded, self.key_digest(password), nil, "AES-256-CBC")
end
def self.key_digest(password)
OpenSSL::Digest::SHA256.new(password).digest
end
def self.decrypt_data(encrypted_data, key, iv, cipher_type)
aes = OpenSSL::Cipher::Cipher.new(cipher_type)
aes.decrypt
aes.key = key
aes.iv = iv if iv != nil
aes.update(encrypted_data) + aes.final
end
def self.encrypt_data(data, key, iv, cipher_type)
aes = OpenSSL::Cipher::Cipher.new(cipher_type)
aes.encrypt
aes.key = key
aes.iv = iv if iv != nil
aes.update(data) + aes.final
end
end
And the php code:
echo base64_encode($encrypted_data = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, hash('sha256', 'p4ssw0rd'), 'hey', MCRYPT_MODE_CBC));
have a look at
https://github.com/nirnanaaa/xlix/blob/master/xlix/lib/Xlix/Bundle/Crypto/Aes/TwoLevel.php
I wrote this a while ago for my crypto functions based on a GIST I have found on the web

PHP to Delphi and back Encryption-Decryption using Rijndael

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;
Neither your PHP nor your Delphi methods appear to specify any padding. If the default paddings are different then you will get problems. Explicitly specify PKCS7 (or PKCS5) for both.
GregS' comment about the result of decoding Base64 is correct. You are supplying encrypted cyphertext to your decRJ() method. That will be random appearing bytes. Attempting to convert it to UTF-8 will mangle it enough that it cannot be decrypted. The incoming cyphertext must be converted from Base64 direct to a byte array. Cyphertext is not a character string, which is why it needs to be converted to Base64 to be transmitted as text. It will only be text again after it has been decrypted back to plaintext.

Categories