javascriptphpnode.jsnode-crypto

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.


Solution

  • 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:

    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.