I found couple of things which might be the reasons why decryption/encryption on PHP and Node.js are not the same.
PHP used MCRYPT_RIJNDAEL_256 algorythm. AES 256 is based on MCRYPT_RIJNDAEL_256, but is not the same. AES 256 this is encryption standart, but not algorythm.
If you trying to encrypt some thing by using standart simple functions ("mcrypt_encrypt" and "mcrypt_decrypt" on PHP for example), you can't see all of steps and you surely can't know why you can't decrypt that what you encrypted. It can be same for Node.js, because need to use function which can encrypt step by step to prevent substitution to default parameters.
To encrypt/decrypt some thing you need to know (to set):
encryption method (algorythm)
encryption mode (CBF, ECB, CBC...)
key to decryption
key lenght
initialisation vector lenght
And check it on the both sides. It should be the same.
Also need to find right combination "encryption method" + "encryption mode" which surely working on the both sides.
My solution is RIJNDAEL_256 + ECB.
You should install node-rijndael, because it uses RIJNDAEL_256 for sure. If not - my example will not work.
Here is Node.js example for encryption.
Install node-rijndael in some folder where should be two .js files.
r256.js - it is functions for encrypt/decrypt. I found it here.
var Rijndael = require('node-rijndael');
/**
* Pad the string with the character such that the string length is a multiple
* of the provided length.
*
* @param {string} string The input string.
* @param {string} chr The character to pad with.
* @param {number} length The base length to pad to.
* @return {string} The padded string.
*/
function rpad(string, chr, length) {
var extra = string.length % length;
if (extra === 0)
return string;
var pad_length = length - extra;
// doesn't need to be optimized because pad_length will never be large
while (--pad_length >= 0) {
string += chr;
}
return string;
}
/**
* Remove all characters specified by the chr parameter from the end of the
* string.
*
* @param {string} string The string to trim.
* @param {string} chr The character to trim from the end of the string.
* @return {string} The trimmed string.
*/
function rtrim(string, chr) {
for (var i = string.length - 1; i >= 0; i--)
if (string[i] !== chr)
return string.slice(0, i + 1);
return '';
}
/**
* Encrypt the given plaintext with the base64 encoded key and initialization
* vector.
*
* Null-pads the input plaintext. This means that if your input plaintext ends
* with null characters, they will be lost in encryption.
*
* @param {string} plaintext The plain text for encryption.
* @param {string} input_key Base64 encoded encryption key.
* @param {string} input_iv Base64 encoded initialization vector.
* @return {string} The base64 encoded cipher text.
*/
function encrypt(plaintext, input_key, input_iv) {
var rijndael = new Rijndael(input_key, {
mode: Rijndael.MCRYPT_MODE_ECB,
encoding: 'base64',
iv: input_iv
});
console.log("Rijndael.blockSize", Rijndael.blockSize);
var padded = rpad(plaintext, '\0', Rijndael.blockSize);
return rijndael.encrypt(padded, 'binary', 'base64');
}
/**
* Decrypt the given ciphertext with the base64 encoded key and initialization
* vector.
*
* Reverses any null-padding on the original plaintext.
*
* @param {string} ciphertext The base64 encoded ciphered text to decode.
* @param {string} input_key Base64 encoded encryption key.
* @param {string} input_iv Base64 encoded initialization vector.
* @param {string} The decrypted plain text.
*/
function decrypt(ciphertext, input_key, input_iv) {
var rijndael = new Rijndael(input_key, {
mode: Rijndael.MCRYPT_MODE_ECB,
encoding: 'base64',
iv: input_iv
});
console.log('lol', rijndael.decrypt(ciphertext, 'base64', 'binary'));
return rtrim(rijndael.decrypt(ciphertext, 'base64', 'binary'), '\0');
}
exports.decrypt = decrypt;
exports.encrypt = encrypt;
encrypt.js - it is example for encryption.
var crypto = require('crypto');
var key = new Buffer('theonetruesecretkeytorulethemall', 'utf-8').toString('base64'); //secret key to decrypt
var iv = crypto.randomBytes(32).toString('base64');
console.log({"key":key, "iv":iv});
var rijndael = require('./r256');
var plaintext = 'lalala'; //text to encrypt
var ciphertext = rijndael.encrypt(plaintext, key, iv);
console.log({"ciphertext":ciphertext});
Here is PHP example for decryption.
<?php
echo "<PRE>";
$mcrypt_method = MCRYPT_RIJNDAEL_256;
$mcrypt_mode = MCRYPT_MODE_ECB;
$mcrypt_iv = '123456'; //needed only for encryption, but needed for mcrypt_generic_init, so for decryption doesn't matter what is IV, main reason it is IV can exist.
$mcrypt_key = 'theonetruesecretkeytorulethemall';
$data_to_decrypt = base64_decode('ztOS/MQgJyKJNFk073oyO8KklzNJxfEphu78ok6iRBU='); //node.js returns base64 encoded cipher text
$possible_methods = array_flip(mcrypt_list_algorithms());
if(empty($possible_methods[$mcrypt_method]))
{
echo "method $mcrypt_method is impossible".PHP_EOL;
exit();
}
$possible_modes = array_flip(mcrypt_list_modes());
if(empty($possible_modes[$mcrypt_mode]))
{
echo "mode $mcrypt_mode is impossible".PHP_EOL;
exit();
}
if(!@mcrypt_get_block_size($mcrypt_method, $mcrypt_mode))
{
echo "method $mcrypt_method does not support mode $mcrypt_mode".PHP_EOL;
exit();
}
$mcrypt = mcrypt_module_open($mcrypt_method,'', $mcrypt_mode, '');
$ivsize = mcrypt_enc_get_iv_size($mcrypt);
if($ivsize != strlen($mcrypt_iv))
{
$mcrypt_iv = str_pad($mcrypt_iv, $ivsize, '#');
}
if($ivsize < strlen($mcrypt_iv))
{
$mcrypt_iv=substr($mcrypt_iv,0,$ivsize);
}
$keysize = mcrypt_enc_get_key_size($mcrypt);
if($keysize != strlen($mcrypt_key))
{
$mcrypt_key = str_pad($mcrypt_key, $keysize, '#');
}
if($keysize < strlen($mcrypt_key))
{
$mcrypt_key=substr($mcrypt_key,0,$keysize);
}
$mcrypt_isblock = (int)mcrypt_enc_is_block_mode($mcrypt);
$mcrypt_blocksize = mcrypt_enc_get_block_size($mcrypt);
$mcrypt_method = mcrypt_enc_get_algorithms_name($mcrypt);
$mcrypt_mode = mcrypt_enc_get_modes_name($mcrypt);
echo "used method=$mcrypt_method \nmode=$mcrypt_mode \niv=$mcrypt_iv \nkey=$mcrypt_key \nkey with blocksize=$mcrypt_blocksize \nisblock=$mcrypt_isblock".PHP_EOL;
if(mcrypt_generic_init($mcrypt,$mcrypt_key,$mcrypt_iv)< 0)
{
echo "mcrypt_generic_init failed...".PHP_EOL;
exit();
}
$result = mdecrypt_generic($mcrypt, $data_to_decrypt);
echo PHP_EOL."decryption result|".$result.'|';
mcrypt_generic_deinit($mcrypt);
P.S.
I don't know why, but Node.js ignores IV (in my example), so cipher will be always the same.
PHP always uses IV and it should be strict lenght, so PHP returns diffirent ciphers always. But i tried it the other way round (encrypt by PHP and decrypt by Node.js) and it works.