Search code examples
phpnode.jsencryptionopensslnode-crypto

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


Solution

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