Can't Decrypt Using Pgcrypto from Aes-256-Cbc But Aes-128-Cbc Is Ok

Can't decrypt using pgcrypto from AES-256-CBC but AES-128-CBC is OK

MCRYPT_RIJNDAEL_256 isn't AES-256. It's the Rijndael cipher with a block size of 256 (hence the error). AES is a subset of the Rijndael cipher using block size of 128 bits and key sizes of 128, 192 and 256 bits. This is also reflected in the IV size.

To create an AES-256 encrypted ciphertext you can use MCRYPT_RIJNDAEL_128 with the correct key size (256 bits is 32 bytes). The _128 postfix indicates the block size to be used; you can still use it with any valid key size of 128, 192 or 256 bit.


Beware that mcrypt - especially the underlying C-library - is not maintained anymore. You're better off using the openssl or later crypto libraries.

The mcrypt and OpenSSL wrappers will also happily allow invalid key sizes, only warning you - if you're lucky. That's of course not compatible with about any well defined AES library.

Converting PHP AES-256-CBC encryption into node.js

Actually, porting issues without target code are routinely closed on SO. But in this case, a target code would not bring any particular benefit, because the main problem here is not porting, but an unnecessary complicated PHP code, which should be simplified first. This will make it almost a one-liner, which will also significantly simplify the porting:

  • The pkcs5_pad() function implements PKCS#7 padding, which is supported by PHP/OpenSSL out-of-the-box. However, the OPENSSL_ZERO_PADDING flag must be removed, as this disables padding. For completeness: PKCS#7 padding is often referred to as PKCS#5 padding in the Java world for historical reasons.
  • The explicit Base64 decoding of the ciphertext is not necessary. If the ciphertext should not be Base64 encoded, the implicit Base64 encoding can simply be disabled with the OPENSSL_RAW_DATA flag.
  • For the hex encoding of the ciphertext the unpack() call and the byteArray2Hex() method are not needed. Instead the ciphertext can be hex encoded directly with bin2hex().
  • A hex encoded ciphertext consists only of alphanumeric characters, so urlencode() does not change the result. Therefore this call can be omitted.

With this, your function can be simplified as follows:

function encryptAESsimple($str, $key){
$iv = "PJKKIOKDOICIVSPC";
$encrypted = openssl_encrypt($str, "AES-256-CBC", $key, OPENSSL_RAW_DATA, $iv);
return bin2hex($encrypted);
}

and pkcs5_pad() and byteArray2Hex() are obsolete.


Test:

The two calls

print(encryptAES("The quick brown fox jumps over the lazy dog", "01234567890123456789012345678901") . PHP_EOL);
print(encryptAESsimple("The quick brown fox jumps over the lazy dog", "01234567890123456789012345678901") . PHP_EOL);

return

59d97c5ae90a1ccf2c1d4ac10aebd2db2d4c1ebf743bbe748cb65bc2109aae43e9d7425cbe5b4d17e0324965cfb0db68
59d97c5ae90a1ccf2c1d4ac10aebd2db2d4c1ebf743bbe748cb65bc2109aae43e9d7425cbe5b4d17e0324965cfb0db68

and thus identical ciphertexts.


This simplification also makes NodeJS porting much easier. You can find some examples in the NodeJS documentation, for example:

var crypto = require('crypto')

var plaintext = Buffer.from('The quick brown fox jumps over the lazy dog', 'utf8');
var key = Buffer.from('01234567890123456789012345678901', 'utf8'); // Note: for a hex or base64 encoded key you have to change the encoding from utf8 to hex or base64
var iv = Buffer.from('PJKKIOKDOICIVSPC', 'utf8');

var cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
var ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);

console.log(ciphertext.toString('hex')); // 59d97c5ae90a1ccf2c1d4ac10aebd2db2d4c1ebf743bbe748cb65bc2109aae43e9d7425cbe5b4d17e0324965cfb0db68

which gives the same ciphertext as the PHP code.


Security: The PHP code uses a static IV. This is insecure because it results in the reuse of key/IV pairs for a fixed key. Therefore, in practice, a random IV is generated for each encryption. This IV is not secret and is sent to the decrypting side along with the ciphertext, usually concatenated. The decrypting side separates both and performs the decryption.

PHP AES-256-CBC encryption from Golang given data

You've got two issues:

First, the GO encrypt is not using AES-256-CBC, it's using AES-128-CBC. I'm not sure how to fix this on the GO side, but on the PHP side, just use AES-128-CBC as your cipher string.

Second, the PHP decrypt expects to operate on BASE64 encoded text, not a raw binary string. To decrypt a raw binary string like in your case, you will need to pass the optional OPENSSL_RAW_DATA flag:

$decrypt = openssl_decrypt($data, 'aes-128-cbc', $key, OPENSSL_RAW_DATA, $iv);

My AES function encrypts value correctly, but decrypting returns FALSE

Without seeing the SecretData.php file, I cannot direct you to a specific point of failure. However, I can just create one dummy myself to confirm that the code itself works fine.

Code

<?php

class SecretData
{
public $universalAESKey = '79f0f1a2e72b6654bba3071ff8210c13';
}

class AESEncryption
{
private static $AES_METHOD = 'aes-256-cbc';

public static function encrypt($data, $key = null)
{
if($key == null)
{
$secretData = new SecretData();
$key = $secretData->universalAESKey;
}

$ivSize = openssl_cipher_iv_length(self::$AES_METHOD);
$iv = openssl_random_pseudo_bytes($ivSize);

$encryptedData = openssl_encrypt($data, self::$AES_METHOD, $key, OPENSSL_RAW_DATA, $iv);
return base64_encode($iv . $encryptedData);
}

public static function decrypt($data, $key = null)
{
$data = base64_decode($data);

if($key == null)
{
$secretData = new SecretData();
$key = $secretData->universalAESKey;
}

$ivSize = openssl_cipher_iv_length(self::$AES_METHOD);
$iv = mb_substr($data, 0, $ivSize, '8bit');
$decryptedData = mb_substr($data, $ivSize, null, '8bit');

return openssl_decrypt($decryptedData, self::$AES_METHOD, $key, OPENSSL_RAW_DATA, $iv);
}
}

Test

$data = 'alpha';

$enc = AESEncryption::encrypt($data);
$dec = AESEncryption::decrypt($enc);

echo 'INPUT:' . $data . PHP_EOL;
echo 'ENCRP:' . $enc . PHP_EOL;
echo 'DECRP:' . $dec . PHP_EOL;

$enc = AESEncryption::encrypt($data, "e2e0cc36ea14bc5cd94473facd4731a6");
$dec = AESEncryption::decrypt($enc, "e2e0cc36ea14bc5cd94473facd4731a6");

echo 'INPUT:' . $data . PHP_EOL;
echo 'ENCRP:' . $enc . PHP_EOL;
echo 'DECRP:' . $dec . PHP_EOL;

Result

INPUT:alpha
ENCRP:PUn1xaDRMX4U0K4NVnJiRv4mtROpn3WvcFnSrR9EJ98=
DECRP:alpha

INPUT:alpha
ENCRP:QOs1vvy/6aKRSVGmZQWp7EvSNoISCpJ4Vsy3T3ixXZ4=
DECRP:alpha

openssl_decrypt for aes-128-cbc not working

openssl_decrypt expects key to be binary not hex. You don't need to convert $secretHash to hex and just pass it as is.

$response = openssl_decrypt($encodedStr, $encryptionMethod, $secretHash, OPENSSL_ZERO_PADDING, $iv);

openssl_decrypt() can't decrypt text encrypted on the commandline

The -k option does not specify a key, but a password. From this password, together with a randomly generated 8 bytes salt, the key is derived using the derivation function EVP_BytesToKey(). The encrypted data is returned in OpenSSL format, which consists of the ASCII encoding of Salted__, followed by the 8 bytes salt and the actual ciphertext.

A simplified PHP implementation of this key derivation function that is sufficient here is (since the IV is explicitly specified here with -iv, it is not derived along with the key):

// from: https://gist.github.com/ezimuel/67fa19030c75052b0dde278a383eda1b
function EVP_BytesToKey($salt, $password) {
$bytes = '';
$last = '';

// 32 bytes key
while(strlen($bytes) < 32) {
$last = hash('sha256', $last . $password . $salt, true); // md5 before v1.1.0
$bytes.= $last;
}
return $bytes;
}

Extracting the salt and actual ciphertext is:

$password = "sup3r_s3cr3t_p455w0rd";
$encrypted = file_get_contents("<path to enc file>");
$salt = substr($encrypted, 8, 8);
$key = EVP_BytesToKey($salt, $password);
$ciphertext = substr($encrypted, 16);

In addition, since the raw data is passed, the corresponding OPENSSL_RAW_DATA flag must be set:

$iv = hex2bin("504914019097319c9731fc639abaa6ec");
$decrypted = openssl_decrypt($ciphertext, "aes-256-ctr", $key, OPENSSL_RAW_DATA, $iv);

Note that as of OpenSSL v1.1.0 the default digest is SHA256, before MD5. The digests used in EVP_BytesToKey() must be identical for compatibility. Also be aware that EVP_BytesToKey() is considered insecure nowadays.



Related Topics



Leave a reply



Submit