2

I am in process of moving a small project from PHP to NodeJS that includes a small part of AES Encryption.

As PHP code works fine, it goes as

  function decysek($data, $app_key) {
    $output = openssl_decrypt(base64_decode($data), 'AES-256-ECB', $app_key, OPENSSL_RAW_DATA);
    return $output;
  }

  function decyGetBillData($rek , $data , $decrypted_sek){
    $decrypted_rek =  openssl_decrypt(base64_decode($rek), 'AES-256-ECB', $decrypted_sek, OPENSSL_RAW_DATA);

    $decrypted_data =  openssl_decrypt(base64_decode($data), 'AES-256-ECB', $decrypted_rek, OPENSSL_RAW_DATA);
    return $decrypted_data;
  }

  $sekdec = decysek($request['sek'], $request['appKey']);
  $data = decyGetBillData($response['rek'], $response['data'], $sekdec);

  echo json_decode($data, true);

The NodeJS Conversion of the same goes as follows

var aes256 = require("aes256");
var js_base64_1 = require("js-base64");

function decysek(data, app_key) {
    var cipher = aes256.createCipher(app_key);
    var output = cipher.decrypt(js_base64_1.Base64.decode(data));
    return output;
}
function decyGetBillData(rek, data, decrypted_sek) {
    var cipher = aes256.createCipher(decrypted_sek);
    var decrypted_rek = cipher.decrypt(js_base64_1.Base64.decode(rek));
    var cipher2 = aes256.createCipher(decrypted_rek);
    var decrypted_data = cipher2.decrypt(js_base64_1.Base64.decode(data));
    return decrypted_data;
}
var sekdec = decysek(request["sek"], request["appKey"]);
var data = decyGetBillData(response["rek"], response["data"], sekdec);
console.log(data);

There is Some thing wrong with NodeJS version as it fails to give me the output, rather throws an error.

Provided "encrypted" must decrypt to a non-empty string.

Can you figure out the issue?

1 Answer 1

6

The Node.js aes256 module doesn't support your PHP encryption algorithm, AES-256-ECB. It uses AES-256-CTR for encryption, and SHA256 as a key derivation function. The IV is generated randomly, and prepended to the ciphertext.

If you want to use this module, you should be able to encrypt - decrypt your data in PHP using the functions below.

/**
 * Encrypts data with the supplied passphrase, using AES-256-CTR.
 * 
 * @param string $plaintext the plaintext data.
 * @param string $passphrase a passphrase/password.
 * @return string|false encrypted data: iv + ciphertext or `false` on error.
 */
function encrypt($plaintext, $passphrase) {
    $key = hash('SHA256', $passphrase, true);
    $iv = openssl_random_pseudo_bytes(16);
    $ct = openssl_encrypt($plaintext, 'AES-256-CTR', $key, 1, $iv);

    return base64_encode($iv.$ct);
}

/**
 * Decrypts data with the supplied passphrase, using AES-256-CTR.
 * 
 * @param string $ciphertext encrypted data.
 * @param string $passphrase a passphrase/password.
 * @return string|false plaintext data or `false` on error.
 */
function decrypt($ciphertext, $passphrase) {
    $data = base64_decode($ciphertext);
    $ciphertext = substr($data, 16);
    $key = hash('SHA256', $passphrase, true);
    $iv = substr($data, 0, 16);

    return openssl_decrypt($ciphertext, 'AES-256-CTR', $key, 1, $iv);
}

The aes256 module is using crypto internally, which is a built-in module and it supports AES-256-ECB. So you could still port your PHP code to JS, but I wouldn't recommend that. AES-256-ECB is a very weak encryption algorithm, and it doesn't provide authentication.


Both PHP7 and crypto support authenticated encryption algorithms, so you could use GCM for example. Also it's best to use a KDF, like PBKDF2 (which is also supported by PHP and crypto) for creating the key.

PHP encryption with AES-256-GCM, PBKDF2 with SHA256:

/**
 * Encrypts data with the supplied passphrase, using AES-256-GCM and PBKDF2-SHA256.
 * 
 * @param string $plaintext the plaintext data.
 * @param string $passphrase a passphrase/password.
 * @return string|false encrypted data: salt + nonce + ciphertext + tag or `false` on error.
 */
function encrypt(string $plaintext, string $passphrase) {
    $salt = openssl_random_pseudo_bytes(16);
    $nonce = openssl_random_pseudo_bytes(12);
    $key = hash_pbkdf2("sha256", $passphrase, $salt, 40000, 32, true);
    $ciphertext = openssl_encrypt($plaintext, 'aes-256-gcm', $key, 1, $nonce, $tag);

    return base64_encode($salt.$nonce.$ciphertext.$tag);
}

/**
 * Decrypts data with the supplied passphrase, using AES-256-GCM and PBKDF2-SHA256.
 * 
 * @param string $ciphertext encrypted data.
 * @param string $passphrase a passphrase/password.
 * @return string|false plaintext data or `false` on error.
 */
function decrypt(string $ciphertext, string $passphrase) {
    $input = base64_decode($ciphertext);
    $salt = substr($input, 0, 16);
    $nonce = substr($input, 16, 12);
    $ciphertext = substr($input, 28, -16);
    $tag = substr($input, -16);
    $key = hash_pbkdf2("sha256", $passphrase, $salt, 40000, 32, true);

    return openssl_decrypt($ciphertext, 'aes-256-gcm', $key, 1, $nonce, $tag);
}

JS encryption with AES-256-GCM, PBKDF2 with SHA256:

const crypto = require('crypto');

/**
 * Encrypts data with the supplied passphrase, using AES-256-GCM and PBKDF2-SHA256.
 * 
 * @param {String} $plaintext the plaintext data.
 * @param {String} $passphrase a passphrase/password.
 * @return {String} encrypted data: salt + nonce + ciphertext + tag.
 */
function encrypt(plaintext, passphrase) {
    var salt = crypto.randomBytes(16);
    var nonce = crypto.randomBytes(12);
    var key = crypto.pbkdf2Sync(passphrase, salt, 40000, 32, 'sha256');

    var cipher = crypto.createCipheriv('aes-256-gcm', key, nonce);
    var ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
    var output = Buffer.concat([salt, nonce, ciphertext, cipher.getAuthTag()]);

    return output.toString('base64');
}

/**
 * Decrypts data with the supplied passphrase, using AES-256-GCM and PBKDF2-SHA256.
 * 
 * @param {String} $ciphertext encrypted data.
 * @param {String} $passphrase a passphrase/password.
 * @return {String} plaintext data.
 */
function decrypt(ciphertext, passphrase) {
    var input = new Buffer(ciphertext, 'base64');
    var salt = input.slice(0, 16);
    var nonce = input.slice(16, 28);
    ciphertext = input.slice(28, -16);
    var tag = input.slice(-16);
    var key = crypto.pbkdf2Sync(passphrase, salt, 40000, 32, 'sha256');

    var cipher = crypto.createDecipheriv('aes-256-gcm', key, nonce);
    cipher.setAuthTag(tag);
    var plaintext = Buffer.concat([cipher.update(ciphertext), cipher.final()]);

    return plaintext.toString('utf-8');
}

Those functions produce compatible results, so ciphertext that is created with encrypt in PHP can be decrypted with decrypt in JS, and vice versa. Of course, this is just a basic example, production code would have more features, exception handling and possibly settings for the cipher and KDF.

Sign up to request clarification or add additional context in comments.

4 Comments

Can you tell what is passphrase here
@TusharKale passphrase is just a string, a password. I've added some coments to make it more clear.
vice versa encryption not working. Ex. encrypt in node js but that cipertext not work for decrypt in PHP.. In same platform working. @t.m.adam
Thank you @t.m.adam. It was typo on PHP password phrase. Thank you again for demo too.. :+1

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.