Why does decipherIv.final() fail in node.js when decrypting a value encrypted using PHP?

时间:2019-04-17 00:16:36

标签: php node.js encryption openssl cryptography

PHP version: 5.6.39

Node.js version: 10.9.0

Objective: Encrypt using PHP and decrypt using Node.js

Stuck Point: I'm currently assuming that both the PHP and Node.js bindings to OpenSSL make use of PKCS7 padding. Do PHP and Node.js use incompatible bindings to OpenSSL?

Example PHP encryption/decryption code:

class SymmetricEncryption {

   public static function simpleEncrypt($key, $plaintext) {
      $iv = openssl_random_pseudo_bytes(16);
      $ciphertext = openssl_encrypt($plaintext, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv)

      return base64_encode($iv) . "." . base64_encode($ciphertext);
   }

   public static function simpleDecrypt($key, $token) {
       list($iv, $ciphertext) = explode(".", $token);

        return openssl_decrypt(
           base64_decode($ciphertext),
           "aes-256-cbc",
           $key,
           OPENSSL_RAW_DATA,
           base64_decode($iv)
        );
   }
}

Example Node.js encryption/decryption code:

class SymmetricEncryption {

    static simpleEncrypt(key: Buffer, plaintext: string): string {
        const iv = randomBytes(16)
        const cipher = createCipheriv('aes-256-cbc', key, iv)
        const encrypted = cipher.update(plaintext)
        const token = Buffer.concat([encrypted, cipher.final()])

        return iv.toString('base64') + "." + token.toString('base64')
    }

    static simpleDecrypt(key: Buffer, token: string): string {
        const [iv, ciphertext] = token.split(".").map(piece => Buffer.from(piece, 'base64'))
        const decipher = createDecipheriv('aes-256-cbc', key, iv)
        const output = decipher.update(ciphertext)
        return Buffer.concat([output, decipher.final()]).toString('utf8')
    }
}

I've tested each implementation independently of one another successfully, but when encrypting in PHP and decrypting in node.js I get the following error:

Error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt

The stack trace points me to the offending line, which is decipher.final() in the simpleDecrypt method.

I'm using the following (failing) unit test to verify my implementation

it('should be able to decrypt values from php', () => {
    const testAesKey = Buffer.from('9E9CEB8356ED0212C37B4D8CEA7C04B6239175420203AF7A345527AF9ADB0EB8', 'hex')
    const phpToken = 'oMhL/oIPAGQdMvphMyWdJw==.bELyRSIwy+nQGIyLj+aN8A=='
    const decrypted = SymmetricEncryption.simpleDecrypt(testAesKey, phpToken)
    expect(decrypted).toBe('hello world')
})

The phpToken variable I'm using here was created using the following code:

$testAesKey = "9E9CEB8356ED0212C37B4D8CEA7C04B6239175420203AF7A345527AF9ADB0EB8";

echo SymmetricEncryption::simpleEncrypt($testAesKey, "hello world");

1 个答案:

答案 0 :(得分:1)

I suspect your issue is caused by how you pass your key in PHP. The documentation for openssl_encrypt doesn't specify anywhere that it interprets the key as hex. It does, however, truncate keys that are too long.

What is likely happening here is PHP is truncating your 64 character hex string down to 32 bytes, and using those for the key. Try using hex2bin on your PHP key before using it - this should fix your issue!

$testAesKey = hex2bin("9E9CEB8356ED0212C37B4D8CEA7C04B6239175420203AF7A345527AF9ADB0EB8");