在Node.js中进行AESencryption在PHP中解密。 失败。

在node.js中,我使用build in函数来encryption数据:

var text = "Yes"; var password = "123456"; var encrypt = crypto.createCipher('aes-256-cbc', password); var encryptOutput1 = encrypt.update(text, 'base64', 'base64'); var encryptOutput2 = encrypt.final('base64'); var encryptedText = encryptOutput1 + encryptOutput2; 

输出(encryption文本)是:OnNINwXf6U8XmlgKJj48iA ==

然后我用PHP解密:

 $encrypted = 'OnNINwXf6U8XmlgKJj48iA=='; (or $encrypted = base64_decode('OnNINwXf6U8XmlgKJj48iA==') ); $dtext2 = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $encrypted, MCRYPT_MODE_CBC); echo "Decrypted: $dtext2"; 

我会得到一些有趣的人物,我不能解密它。 我尝试了/没有base64_decode或MCRYPT_RIJNDAEL_128 ..都失败了。

然后我检查PHP中的encryption方式,它与node.js的输出看起来有很大的不同。

 $text = "Yes"; $key = "123456"; $eText = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $text, MCRYPT_MODE_CBC); echo "Encrypted: $eText \n"; echo "base64: " . base64_encode($eText) . " \n"; $dtext1 = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $eText, MCRYPT_MODE_CBC); echo "Decrypted: $dtext1 \n\n"; 

它可以encryption和解密。 encryption的数据是:njCE / fk3pLD1 / JfiQuyVa6w5H + Qb / utBIT3m7LAcetM =

这是从node.js输出非常不同,请告诉我如何可以encryption和解密node.js&PHP之间。 谢谢。 🙂


@Mel这里是我在PHP中的:

 $text = "Yes"; $key = "32BytesLongKey560123456789ABCDEF"; $iv = "sixteenbyteslong"; /* Open the cipher */ $td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, ''); /* Intialize encryption */ mcrypt_generic_init($td, $key, $iv); /* Encrypt data */ $eText = mcrypt_generic($td, $text); echo "Encrypted Data: $eText \n"; echo "base64: " . base64_encode($eText) . " \n"; /* Terminate encryption handler */ mcrypt_generic_deinit($td); /* Initialize encryption module for decryption */ mcrypt_generic_init($td, $key, $iv); /* Decrypt encrypted string */ $dText = mdecrypt_generic($td, $eText); /* Terminate decryption handle and close module */ mcrypt_generic_deinit($td); mcrypt_module_close($td); /* Show string */ echo trim($dText) . "\n"; 

但是,它仍然不起作用。

PHP中的encryption基础64是: 80022AGM4 / 4qQtiGU5oJDQ == nodejs中的encryption基础64是: EoYRm5SCK7EPe847CwkffQ ==

因此,我不能在PHP中解密一个nodejs。

我不知道是不是因为nodejs不需要$ iv?

迟了七个月,但我也为此苦苦挣扎,并find了解决办法。 显然,PHP用零字节填充input,使其大小成为块大小的倍数。 例如,使用AES-128,14字节的input“contrabassists”将填充两个零字节,如下所示:

 "contrabassists\0\0" 

AN *块大小的字节input被单独留下。

标准的节点密码函数使用不同的称为PKCS5的填充scheme。 PKCS5不添加零,但增加了填充的长度,所以再次使用AES-128,“最低音”将成为:

 "contrabassists\2\2" 

甚至一个N *块大小的字节input在PKCS5中被填充。 否则,在解码之后不可能删除填充。 input“spectroheliogram”然后将成为:

 "spectroheliogram\16\16\16\16\16\16\16\16\16\16\16\16\16\16\16\16" 

为了使PHP m_cryptencryption与节点解密兼容,您必须自己填充input:

 $pad = $blocksize - (strlen($input) % $blocksize); $input = $input . str_repeat(chr($pad), $pad); 

反过来,你必须读取解码数据的最后一个字节,并自行切断填充。

示例function:(添加01-14-2012)

在PHP中,此函数将返回可由Node解密的AES-128encryption的hex编码数据:

 function nodeEncrypt($data, $key, $iv) { $blocksize = 16; // AES-128 $pad = $blocksize - (strlen($data) % $blocksize); $data = $data . str_repeat(chr($pad), $pad); return bin2hex(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_CBC, $iv)); } 

在Node中,以下将解密数据:

 function nodeDecrypt(data, key, iv) { var decipher = crypto.createDecipheriv('aes-128-cbc', key, iv); var chunks = [] chunks.push(decipher.update(data.toString(),'hex','binary')) chunks.push(decipher.final('binary')) return chunks.join('') } 

我还没有做相反的事,但是一旦你理解了填充scheme,它应该是直截了当的。 我还没有对关键/四代产生任何假设。

我只是开始搞乱node.js,但我认为你的问题是与IVs不匹配有关。 请尝试执行以下操作:

 var encrypt = crypto.createCipheriv('aes-256-cbc', password, /* password.createHash('md5').toHex()*/); 

PS:我不知道如何在node.js中创build一个MD5散列,你必须自己弄清楚,并相应地改变上面的代码。

在PHP中:

 $decrypt = rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, base64_decode($encrypted), MCRYPT_MODE_CBC, md5($key)), "\0"); 

这应该确保两个实现使用相同的初始化向量。

我也build议你做出以下改变:

  • 密码:md5(original_password)
  • iv = md5(md5(original_password))

这将确保PHP不会抛出任何愚蠢的错误。 请参阅使用PHPencryption和解密密码的最佳方法?

AES是固定大小的16字节IV的rijndael。 详情在这里 。 不能用于解密。 更重要的是,我不能用openssl解密你的string:

 % openssl aes-256-cbc -d -in dec.txt -a enter aes-256-cbc decryption password: bad magic number 

或者使用php:

 $encrypted = 'OnNINwXf6U8XmlgKJj48iA=='; $text = 'Yes'; $pw = '123456'; $decrypted = @openssl_decrypt($encrypted, 'aes-256-cbc', $pw); var_dump($decrypted); var_dump(@openssl_encrypt($text, 'aes-256-cbc', $pw, FALSE, $pw)); var_dump(@openssl_encrypt($text, 'aes-256-cbc', $pw)); 

输出:

 bool(false) string(24) "xrYdu2UyJfxhhEHAKWv30g==" string(24) "findrYaZVpZWVhEgOEVQwQ==" 

所以看来node.js正在使用一些未公开的function来创buildIV,而我无法在node.js中提供IV。

我发现了一些可能是PHP和Node.js上的解密/encryption不一样的原因。

PHP使用了MCRYPT_RIJNDAEL_256 algorythm。 AES 256基于MCRYPT_RIJNDAEL_256,但不一样。 AES 256这是encryption标准,但不是algorithm。

如果你想通过使用标准的简单函数(例如PHP上的“mcrypt_encrypt”和“mcrypt_decrypt”)来encryption一些东西,你就看不到所有的步骤,而且你肯定不知道为什么你不能解密你encryption。 对于Node.js可以是相同的,因为需要使用可以逐步encryption的函数来防止replace为默认参数。

encryption/解密你需要知道的一些东西(设置):

 encryption method (algorythm) encryption mode (CBF, ECB, CBC...) key to decryption key lenght initialisation vector lenght 

并检查它的双方。 它应该是一样的。 还需要find正确工作在双方的正确组合“encryption方式”+“encryption方式”。

我的解决scheme是RIJNDAEL_256 + ECB 。 你应该安装node-rijndael ,因为它确实使用了RIJNDAEL_256。 如果没有 – 我的例子将无法正常工作。

这里是Node.jsencryption的例子

在一些文件夹中安装node-rijndael应该是两个.js文件。

r256.js – 这是encryption/解密的function。 我在这里find了

 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 – 这是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}); 

这里是解密的PHP例子

 <?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); 

PS我不知道为什么,但Node.js忽略IV(在我的例子中),所以密码将永远是相同的。 PHP总是使用IV,它应该是严格的长度,所以PHP总是返回不同的密码。 但我反过来试了一下(由PHPencryption和Node.js解密),它的工作原理。

Node.js正在用你的input密码做一些魔术来派生一个密钥和iv。 很难看到如何在PHP中工作,除非PHP执行完全相同的密钥和iv派生魔术。

为什么不使用createCipheriv呢? 使用基于密码的密钥派生函数从密码创build密钥。 例如:

http://en.wikipedia.org/wiki/PBKDF2

Node.js的更高版本提供了这样的function

http://nodejs.org/docs/latest/api/crypto.html#crypto_crypto_pbkdf2_password_salt_iterations_keylen_callback

提供一个好的iv; 你可以使用crypto.randomBytes创build一个。 如果你控制了key和iv参数,那么你将更容易确定是否可以将数据往返于PHP。

你不能只散列密码来生成一个iv。 每个encryption的消息iv应该是不同的,否则它是无用的。

此外,你告诉Node.js你的inputstring“是”是Base64编码,但我认为它是真正的ASCII或UTF-8。

如果您遇到使用MCRYPT_RIJNDAEL_256的第三方库,请MCRYPT_RIJNDAEL_256 ,256指定了块大小,而不是密钥大小。 AES使用固定的128位块大小,openssl不实现更通用的Rijndaelalgorithm。 为了规避这个问题,我发布了一个绑定到libmcrypt的模块,就像PHP一样。 这是一个相当有限的用例,但它确保它将与256位块大小rijndael兼容。

如果你在PHP中使用这个

 mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $plaintext, MCRYPT_MODE_ECB); mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $ciphertext, MCRYPT_MODE_ECB); 

你可以在Node中做同样的事情:

 var rijndael = require('node-rijndael'); // straight through (must be buffers) rijndael.encrypt(plaintext, key); rijndael.decrypt(ciphertext, key); // or bound (can take a string for the key and an encoding) var rijn = rijndael(key); rijn.encrypt(plaintext); // gotta be a buffer again for implementation simplicity rijn.decrypt(ciphertext); 

node-rijndael在GitHub上

node-rijndael on npm

如果这对其他人有帮助,我在这篇文章中还有另外一个例子。

如果你确定在PHP和Node中使用32个字符长度的“密钥/秘密”和16个字符长度的IV,并且在Node中使用base64encryption编码和utf8消息编码,那么你不应该有任何问题填充架构。

问候,伊格纳西奥