Enuff Enuff - 6 months ago 24
PHP Question

Duplicate php openssl_encrypt in JavaScript

I'm trying to duplicate PHP string encryption using JavaScript. Here is the PHP code:

<?php

$iv = "1234567890123456";
$key = "aaaaaaaaaaaaaaaa";
$input = "texttexttexttext";

$encrypted = openssl_encrypt($input, "AES-256-CBC", $key, 0, $iv);

echo $encrypted;
// "ZwY1i+vqP3acszeDiscCTx/R4a6d2AtkcInmN9OTCNE="


However, when I try to duplicate it in JavaScript it gives a different ciphertext:

var aesjs = require("aes-js");
var base64 = require("js-base64");

var iv = aesjs.utils.utf8.toBytes("1234567890123456");
var key = aesjs.utils.utf8.toBytes("aaaaaaaaaaaaaaaa");
var text = aesjs.utils.utf8.toBytes("texttexttexttext");

var aesCbc = new aesjs.ModeOfOperation.cbc(key, iv);
var encryptedBytes = aesCbc.encrypt(text);

var b64encoded = base64.Base64.encode(encryptedBytes);

console.log(b64encoded);
// "MTcyLDIsNjAsMTU5LDcxLDEwLDE4Myw4LDE…wyMTIsMjIyLDk3LDEyNCw1MywxNzIsMjIy"


I have no clue on how to make it give the same output. Any ideas?

Answer Source

Some things are going wrong:

First, the output from the JavaScript code is actually the base64 encoding of the string 172,2,60,159,71,10,183,8,1,…, not the encoding of the raw byte-buffer. I do not really know how to fix this idiomatically, but by using the aes.js hex-encoding utility function, we can convert it to base64:

var hex = aesjs.utils.hex.fromBytes(encryptedBytes);
var buf = Buffer.from(hex, 'hex');

console.log(buf.toString('base64'));
// rAI8n0cKtwiu1N5hfDWs3g==

The second problem is that in aes.js you are using AES128 encryption (aaaaaaaaaaaaaaaa is 128 bits long), but you are using AES256 encryption in the PHP code. We should update the PHP code (or the JS code):

$encrypted = openssl_encrypt($input, "AES-128-CBC", $key, 0, $iv);
echo $encrypted;
// rAI8n0cKtwiu1N5hfDWs3rPbz0UmvlbW+LJliYox03c=

We almost have the same output. But wait, the PHP output is twice as long. What happened?

Well, OpenSSL uses PKCS#7 padding. The Javascript code is unpadded however. To fix this, you should use PKCS#7 padding for the javascript text. For this you can just use the pkcs7 module. Another option is to use AES in counter (CTR) mode instead of CBC mode, if this is an option for you.

This is the PHP code that I have in the end:

<?php
$iv = "1234567890123456";
$key = "aaaaaaaaaaaaaaaa";
$input = "texttexttexttext";
$encrypted = openssl_encrypt($input, "AES-128-CBC", $key, 0, $iv);
echo $encrypted;
// output: 'rAI8n0cKtwiu1N5hfDWs3rPbz0UmvlbW+LJliYox03c='

And this is the JavaScript code:

var aesjs = require("aes-js");
var base64 = require("js-base64");
var pkcs7 = require("pkcs7");

var iv = aesjs.utils.utf8.toBytes("1234567890123456");
var key = aesjs.utils.utf8.toBytes("aaaaaaaaaaaaaaaa");
var text = aesjs.utils.utf8.toBytes("texttexttexttext");

var aesCbc = new aesjs.ModeOfOperation.cbc(key, iv);
var encryptedBytes = aesCbc.encrypt(pkcs7.pad(text));

var hex = aesjs.utils.hex.fromBytes(encryptedBytes);
var buf = Buffer.from(hex, 'hex');

console.log(buf.toString('base64'));
// output: 'rAI8n0cKtwiu1N5hfDWs3rPbz0UmvlbW+LJliYox03c='

PS I personally prefer using CTR mode, because PKCS#7 implementations sometimes expose padding oracles which break the encryption. (I checked the mentioned pkcs#7 library which should be good, but please don't try to implement this yourself.)

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download