1

I'm trying to convert my symmetric AES-256-CBC encryption from PHP to NodeJS. But i think i'm not converting correctly the IV, and i tried different things, but not worked.

My PHP Code:

<?php
    class Encryption{
        const method = 'AES-256-CBC';
        private static $passwordString;
        private static $iv;
        private static $password;
        
        public static function encryptString($string){
            self::setEncryptionPasswordString();
            self::setEncryptionIV();
            self::setEncryptionPassword();
            
            $encrypted = openssl_encrypt($string, self::method, self::$password, OPENSSL_RAW_DATA, self::$iv);
            $encrypted = base64_encode($encrypted);
            $encrypted = str_replace(['+', '/', '='], ['-', '_', ''], $encrypted);
            return $encrypted;
        }
        
        public static function decryptString($string){
            self::setEncryptionPasswordString();
            self::setEncryptionIV();
            self::setEncryptionPassword();

            $string = str_replace(['-', '_'], ['+', '/'], $string);
            $string = base64_decode($string);
            $decrypted = openssl_decrypt($string, self::method, self::$password, OPENSSL_RAW_DATA, self::$iv);
            return $decrypted;
        }
        
        private static function setEncryptionPasswordString(){
            self::$passwordString = getenv("ENC_PASS");
        }

        private static function setEncryptionIV(){
            self::$iv = chr(0x0) . chr(0x0) . chr(0x0) . chr(0x0) . chr(0x0) . chr(0x0) . chr(0x0) . chr(0x0) . chr(0x0) . chr(0x0) . chr(0x0) . chr(0x0) . chr(0x0) . chr(0x0) . chr(0x0) . chr(0x0);
        }
        
        private static function setEncryptionPassword(){
            self::$password = substr(hash('sha256', self::$passwordString, true), 0, 32);
        }
    }
?>

My NodeJS code:

const crypto = require("crypto");
const method = "AES-256-CBC";
const iv = Buffer.alloc(16);
const password = "my_password";

function encrypt(string){   
    //let key = crypto.createHash("SHA-256").update(String(password)).digest("base64").substr(0, 32); Not Working
    let key = "my_iv";
    let cipher = crypto.createCipheriv(method, key.substr(0, 32), iv);
    let encrypted = cipher.update(string);
    let result = encrypted.toString("base64").replace(/[=]/g, "").replace(/[+]/g, "-").replace(/[\/]/g, "_");

    return result;
}

function decrypt(string){

}

module.exports.encrypt = (string) => encrypt(string);
module.exports.decrypt = (string) => decrypt(string);

I think, Buffer.alloc(16) is not working as i expect, and i don't know how to change it. Can someone help-me please!

Topaco
  • 40,594
  • 4
  • 35
  • 62
João Pedro
  • 115
  • 1
  • 12
  • 2
    Why are you using a static IV? Please see https://cwe.mitre.org/data/definitions/329.html and especially the "Observed Examples" section, which lists how a static IV helps attackers break the security of popular software products. – Robert Kawecki Mar 30 '21 at 22:59
  • @RobertKawecki 'Cos i want static encrypted responses, isn't it the better way? – João Pedro Mar 30 '21 at 23:08
  • 2
    "static encrypted responses" == "encryption that's easy to break". Ideally calling `encrypt("foo")` _N_ times should yield _N_ distinct outputs so as to make cryptanalysis difficult. If you want to uniquely identify a piece of encrypted content then you could provide a hash, HMAC, or signature of the plaintext content, with the signature having the additional effect of validating message origin. – Sammitch Mar 30 '21 at 23:14
  • @Sammitch So, if i want to store this data in database, for every encrypted column, i would have a "blind index", with an HMAC. For example, if i store "Foo Bar Baz" and "Foo Bar Qux", i store in blind index of both "Foo", search for it, and decrypt every row to find my data? – João Pedro Apr 05 '21 at 11:04
  • 1
    "Securely encrypted" and "easily searchable" are mutually exclusive concepts outside of the blind index. You might want to re-examine your design and reconsider when and where you really need to implement additional security. – Sammitch Apr 05 '21 at 18:44

1 Answers1

1

The Node.js code below should give the same result as the PHP code:

Node.js

const crypto = require("crypto");
const Algorithm = "aes-256-cbc";

function encrypt(plainText, key, iv, outputEncoding = "base64") {
    const cipher = crypto.createCipheriv(Algorithm, key, iv);
    const output = Buffer.concat([cipher.update(plainText), cipher.final()]).toString(outputEncoding);
    return output.replace('+', '-').replace('/', '_').replace('=', '');
}

function decrypt(cipherText, key, iv, outputEncoding = "base64") {
    cipherText = Buffer.from(cipherText.replace('-', '+').replace('_', '/'), "base64");
    const cipher = crypto.createDecipheriv(Algorithm, key, iv);
    return Buffer.concat([cipher.update(cipherText), cipher.final()]).toString(outputEncoding);
}

const passwordString = "test password";

const KEY = crypto.createHash('sha256').update(passwordString).digest();
const IV = Buffer.alloc(16);

console.log("Key length (bits):", KEY.length * 8);
console.log("IV length (bits):", IV.length * 8);
const encrypted = encrypt("Brevity is the soul of wit", KEY, IV, "base64");
console.log("Encrypted (base64):", encrypted);
const decrypted = decrypt(encrypted, KEY, IV, "utf8")
console.log("Decrypted:", decrypted);

PHP

putenv('ENC_PASS=test password');
$x = new Encryption();
$encrypted = $x->encryptString("Brevity is the soul of wit");
echo "Encrypted (base64): " . $encrypted . "\r\n";
echo "Decrypted: " . $x->encryptString("Brevity is the soul of wit") . "\r\n";

Both should give an output of

IEqTyL_k1xgQaBZGFLYYfTmzytKzFgicM-5mWMyxgYw

As @robert-kawecki pointed out, you should ideally use a different IV for each encryption to ensure the best level of security.

Terry Lennox
  • 29,471
  • 5
  • 28
  • 40