Node.js 中的 AES 加密 PHP 中的解密。失败。

发布于 2024-11-08 02:22:25 字数 2281 浏览 0 评论 0原文

在node.js中,我使用内置函数来加密数据,如下所示:

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;

输出(加密文本)为: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 中的加密方式,它看起来与 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";

它可以加密和解密。加密数据为:njCE/fk3pLD1/JfiQuyVa6w5H+Qb/utBIT3m7LAcetM=,

这与node.js的输出非常不同,请告知我如何在node.js和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 中的加密基 64 为:80022AGM4/4qQtiGU5oJDQ== Nodejs 中的加密基 64 为: EoYRm5SCK7EPe847CwkffQ==

因此,我无法解密 PHP 中的 NodeJS。

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

In node.js, I use the build in function to encrypt data like that:

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;

the output (encrypted text) is: OnNINwXf6U8XmlgKJj48iA==

Then I use decrypt it in PHP:

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

I will get some funny characters, which I can't decrypted it. I tried with/without base64_decode or MCRYPT_RIJNDAEL_128.. all fail.

Then I check how the encryption in PHP, it looks very different from the output from 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";

It can encrypt and decrypt. and the encrypted data is : njCE/fk3pLD1/JfiQuyVa6w5H+Qb/utBIT3m7LAcetM=

which is very different from the output from node.js please advise how I can encrypt and decrypt between node.js & php. thanks. :)


@Mel here is what I have in 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";

However, it still doesn't work.

The encrypted base 64 in PHP is: 80022AGM4/4qQtiGU5oJDQ==
The encrypted base 64 in nodejs is: EoYRm5SCK7EPe847CwkffQ==

thus, i can't decrypt the nodejs one in PHP.

I wonder if it is because nodejs doesn't require $iv?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

记忆里有你的影子 2024-11-15 02:22:25

晚了七个月,但我也在为此苦苦挣扎,并找到了解决方案。显然,PHP 用零字节填充输入,使其大小成为块大小的倍数。例如,使用 AES-128,14 字节输入“低音提琴手”将用两个零字节填充,如下所示:

"contrabassists\0\0"

AN*blocksize 字节输入保持不变。

然而,标准节点加密函数使用不同的填充方案,称为 PKCS5。 PKCS5 不会添加零,而是添加填充的长度,因此再次使用 AES-128,“低音提琴手”将变为:

"contrabassists\2\2"

即使是 N*blocksize 字节输入也会在 PKCS5 中进行填充。否则,解码后无法去除填充。输入“spectroheliogram”将变成:

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

为了使 PHP m_crypt 加密与 Node 解密兼容,您必须自己填充输入:

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

相反,您必须读取解码数据的最后一个字节并截断自己填充。

示例函数:(2012 年 1 月 14 日添加)

在 PHP 中,此函数将返回可由 Node 解密的 AES-128 加密的十六进制编码数据:

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('')
}

我还没有做相反的事情,但是一旦你理解了填充方案,它应该很简单。我没有对 key/iv 生成做出任何假设。

Seven months late, but I was struggling with this as well, and found a solution. Apparently, PHP pads the input with zero bytes to make its size a multiple of the block size. For example, using AES-128, the 14 byte input "contrabassists" will be padded with two zero bytes, like this:

"contrabassists\0\0"

A N*blocksize byte input is left alone.

The standard Node crypto functions, however, use a different padding scheme called PKCS5. PKCS5 doesn't add zeros, but adds the length of the padding, so again using AES-128, "contrabassists" would become:

"contrabassists\2\2"

Even a N*blocksize byte input gets padded in PKCS5. Otherwise, it's impossible to remove the padding after decoding. The input "spectroheliogram" would then become:

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

To make PHP m_crypt encryption compatible with Node decryption, you'll have to pad the input yourself:

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

The other way around, you'll have to read the last byte of the decoded data and cut off the padding yourself.

Example functions: (added 01-14-2012)

In PHP, this function would return AES-128 encrypted, hex encoded data that can be decrypted by Node:

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

In Node, the following would decrypt the data:

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('')
}

I haven't done the reverse yet, but it should be straightforward once you understand the padding scheme. I haven't made any assumptions about key/iv generation.

月亮是我掰弯的 2024-11-15 02:22:25

我刚刚开始摆弄 node.js,但我认为你的问题与 IV 不匹配有关。请尝试执行以下操作:

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

PS:我不确定如何在 node.js 中创建 MD5 哈希,您必须 自己弄清楚并相应地更改上面的代码。

在 PHP 中:

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

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

我还建议您进行以下更改:

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

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

I'm just starting messing around with node.js but I think your problem is related to mismatching IVs. Try doing the following instead:

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

PS: I'm not sure how to create an MD5 hash in node.js, you'll have to figure it out for yourself and change the above code accordingly.

And in PHP:

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

This should make sure both implementations use the same initialization vector.

I also recommend that your make the following changes:

  • password: md5(original_password)
  • iv = md5(md5(original_password))

This will make sure PHP won't throw any stupid errors. See Best way to use PHP to encrypt and decrypt passwords?

厌倦 2024-11-15 02:22:25

我在另一篇文章中还有另一个工作示例,如果它对其他人有帮助的话。

如果您确保在 PHP 和 Node 中使用 32 个字符长度的“key/secret”和 16 个字符长度的 IV,以及 base64 加密编码和 utf8 消息编码节点,那么您不应该对填充模式的任何差异有任何问题。

问候,
伊格纳西奥

I have another working example in this other post if it helps for anyone else.

If you make sure you use an 32 characters length "key/secret" and a 16 characters length IV in both PHP and Node, and base64 encryption encoding and utf8 message encoding in Node, then you should not have any issues with any differences in the padding schema.

Regards,
Ignacio

笑脸一如从前 2024-11-15 02:22:25

AES 是固定大小 16 字节 IV 的 rijndael。详细信息此处
不能用于解密。
更重要的是,我无法使用 openssl 解密您的字符串:

% 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 似乎正在使用一些未记录的功能来创建 IV,并且我看不到如何在 node.js 中提供 IV。

AES is rijndael with fixed size 16 byte IV. Details here.
Can't be used to decrypt.
More importantly, I can't decrypt your string using openssl either:

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

Or using 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));

Output:

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

So it seems that node.js is using some undocumented feature to create the IV and I see no way to provide the IV in node.js.

稳稳的幸福 2024-11-15 02:22:25

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

PHP 使用 MCRYPT_RIJNDAEL_256 算法。 AES 256 基于 MCRYPT_RIJNDAEL_256,但并不相同。 AES 256 这是加密标准,但不是算法。

如果您尝试使用标准的简单函数(例如 PHP 上的“mcrypt_encrypt”和“mcrypt_decrypt”)来加密某些内容,您将无法看到所有步骤,并且您肯定不知道为什么无法解密该内容加密的。对于 Node.js 来说也是一样,因为需要使用可以逐步加密的函数来防止替换默认参数。

要加密/解密一些您需要知道(设置)的事情:

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

并在两侧进行检查。应该是一样的。
还需要找到对双方都有效的正确组合“加密方法”+“加密模式”。

我的解决方案是RIJNDAEL_256 + ECB
您应该安装 node-rijndael,因为它使用 RIJNDAEL_256当然。如果没有 - 我的例子将不起作用。

这是用于加密的 Node.js 示例

在某个文件夹中安装node-rijndael,其中应该有两个.js 文件。

r256.js - 它是加密/解密的函数。我在这里找到了它。

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 - 它是加密的示例。

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

聚苯乙烯
我不知道为什么,但 Node.js 忽略 IV(在我的示例中),因此密码将始终相同。
PHP 总是使用 IV 并且它应该是严格的长度,所以 PHP 总是返回不同的密码。但我尝试了相反的方法(通过 PHP 加密并通过 Node.js 解密)并且它有效。

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.

浅沫记忆 2024-11-15 02:22:25

Node.js 正在对您输入的密码进行一些魔法来派生密钥和 iv。很难看出这在 PHP 中是如何工作的,除非 PHP 执行完全相同的 key 和 iv 派生魔法。

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

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

就有这样的功能在 Node.js 的更高版本中

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

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

你不能仅仅对密码进行哈希处理来生成 iv。每个加密消息的 iv 应该是不同的,否则就没用。

另外,您告诉 Node.js 您的输入字符串“Yes”是 Base64 编码的,但我认为它实际上是 ASCII 或 UTF-8。

Node.js is doing some magic with your input password to derive a key and iv. It's hard to see how that would work in PHP unless PHP does exactly the same key and iv derivation magic.

Why don't you use createCipheriv instead. Use a password-based key derivation function to create a key from the password. For example:

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

Such a function is available in later versions of Node.js

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

Provide a good iv as well; you can create one using crypto.randomBytes. If you control the key and iv parameters then you will have a much easier time determining if you can round-trip your data to PHP.

You can't just hash the password to generate an iv. The iv is supposed to be different for every encrypted message, otherwise it is useless.

Also, you are telling Node.js that your input string "Yes" is Base64 encoded, but I think it's really ASCII or UTF-8.

删除会话 2024-11-15 02:22:25

如果您无法使用使用 MCRYPT_RIJNDAEL_256 的第三方库,请注意 256 指定的是块大小,而不是密钥大小。 AES 使用 128 位的固定块大小,并且 openssl 没有实现更通用的 Rijndael 算法。为了避免这个问题,我发布了一个绑定到 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);

GitHub 上的node-rijndael

npm 上的node-rijndael

If you're stuck with a third-party library which uses MCRYPT_RIJNDAEL_256, know that the 256 specifies the block size, not the key size. AES uses a fixed block-size of 128 bits, and openssl does not implement more generic Rijndael algorithms. To circumvent this I published a module that binds to libmcrypt, just as PHP does. It's a pretty limited use-case, but it ensures it will be compatible with 256-bit block size rijndael.

If you're using this in PHP

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

You can do the same in 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 on GitHub

node-rijndael on npm

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文