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, theOPENSSL_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 thebyteArray2Hex()
method are not needed. Instead the ciphertext can be hex encoded directly withbin2hex()
. - 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
Proper Prevention of Mail Injection in PHP
How to Connect SQL Server with PHP Using Xampp
Laravel Checking If a Record Exists
PHP Sessions in a Load Balancing Cluster - How
Mysql_Escape_String VS MySQL_Real_Escape_String
Issue in Installing PHP7.2-Mcrypt
Woocommerce - Get Category for Product Page
How to Manage a Single PHP5 Session on Multiple Apache Servers
PHP SQL, Query Returns Only One Row of Data
Vim Inoremap for Specific Filetypes
Use Openssl_Encrypt to Replace Mcrypt for 3Des-Ecb Encryption
PHP Composer Update "Cannot Allocate Memory" Error (Using Laravel 4)
Laravel Advanced Wheres How to Pass Variable into Function
How to Set Cron Job Url for Codeigniter