Skip to content
Advertisement

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

I have little to no knowledge in encryption and I’ve been facing issues trying to figure out how to convert these PHP functions to work with node.js and the crypto module.

function encryptAES($str,$key) {
    $iv = "PJKKIOKDOICIVSPC"
    $str = pkcs5_pad($str); 
  
    $encrypted = openssl_encrypt($str, "AES-256-CBC", $key, OPENSSL_ZERO_PADDING, $iv);
    $encrypted = base64_decode($encrypted);
    $encrypted = unpack('C*', ($encrypted));
    $encrypted = byteArray2Hex($encrypted);
    $encrypted = urlencode($encrypted);
    return $encrypted;
}

function pkcs5_pad ($text) {
    $blocksize = openssl_cipher_iv_length("AES-256-CBC");
    $pad = $blocksize - (strlen($text) % $blocksize);
    return $text . str_repeat(chr($pad), $pad);
}

function byteArray2Hex($byteArray) {
    $chars = array_map("chr", $byteArray);
    $bin = join($chars);
    return bin2hex($bin);
}

Any help would be much appreciated.

Advertisement

Answer

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.

User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement