Шифрование AES в Node.js Расшифровка в PHP. Потерпеть поражение. - PullRequest
10 голосов
/ 18 мая 2011

В 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 и php. Благодарю. :)


@ Мел, вот что у меня есть в 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";

Однако, это все еще не работает.

Зашифрованная база 64 в PHP: 80022AGM4 / 4qQtiGU5oJDQ == Зашифрованная база 64 в nodejs: EoYRm5SCK7EPe847CwkffQ ==

Таким образом, я не могу расшифровать nodejs в PHP.

Интересно, потому что для nodejs не требуется $ iv?

Ответы [ 7 ]

17 голосов
/ 07 января 2012

Опоздал на семь месяцев, но я тоже боролся с этим и нашел решение.Очевидно, PHP дополняет ввод нулевыми байтами, чтобы его размер был кратен размеру блока.Например, при использовании AES-128 14-байтовые входные «контрабасисты» будут дополнены двумя нулевыми байтами, например:

"contrabassists\0\0"

AN * байтовый размер байта оставлен в покое.

Стандартные криптографические функции Node, однако, используют другую схему заполнения, которая называется PKCS5.PKCS5 не добавляет нули, но добавляет длину заполнения, поэтому снова, используя AES-128, «контрабасисты» станут:

"contrabassists\2\2"

Даже ввод байтов размером N * блока дополняется в PKCS5.В противном случае невозможно удалить отступ после декодирования.Тогда входная «спектрогелиограмма» станет такой:

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

Другой способпримерно, вам придется прочитать последний байт декодированных данных и самостоятельно обрезать заполнение.

Примеры функций: (добавлено 01-14-2012)

В PHP эта функция будет возвращать зашифрованные в шестнадцатеричном формате 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));
}

В узле расшифровываются данные следующим образом:

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

Я еще не сделал обратное, но это должно быть просто, как только вы поймете схему заполнения.Я не делал никаких предположений относительно генерации ключа / iv.

2 голосов
/ 18 мая 2011

Я только начинаю возиться с node.js, но думаю, что ваша проблема связана с несоответствием IV.Вместо этого попробуйте сделать следующее:

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

PS: я не уверен, как создать хеш MD5 в node.js, вам придется выяснить это для себя и измените приведенный выше код соответствующим образом.

И в PHP:

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

Это должно гарантировать, что обе реализации используют один и тот же вектор инициализации.

Iтакже рекомендуем внести следующие изменения:

  • пароль: md5 (оригинальный_пароль)
  • iv = md5 (md5 (оригинальный_пароль))

Это будетубедитесь, что PHP не выдаст глупых ошибок.См. Лучший способ использовать PHP для шифрования и дешифрования паролей?

1 голос
/ 11 февраля 2015

Я нашел несколько вещей, которые могут быть причинами, по которым дешифрование / шифрование на PHP и Node.js не совпадают.

PHP использовал алгоритм MCRYPT_RIJNDAEL_256.AES 256 основан на MCRYPT_RIJNDAEL_256, но отличается от него.AES 256 это стандарт шифрования, но не алгоритм.

Если вы пытаетесь зашифровать что-то с помощью стандартных простых функций (например, «mcrypt_encrypt» и «mcrypt_decrypt» в PHP), вы не сможете увидеть всешаги, и вы, конечно, не можете знать, почему вы не можете расшифровать то, что вы зашифровали.Это может быть то же самое для 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);

PS Не знаю почему, но Node.js игнорирует IV (в моем примере), поэтому шифр всегда будет одинаковым.PHP всегда использует IV, и он должен быть строгим, поэтому PHP всегда возвращает разные шифры.Но я попробовал наоборот (зашифрован PHP и расшифрован Node.js), и он работает.

1 голос
/ 18 мая 2011

AES является риджнэлем с фиксированным размером 16 байт IV. Подробности здесь . Не может быть использован для расшифровки. Что еще более важно, я не могу расшифровать вашу строку с помощью 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, и я не вижу способа предоставить IV в node.js.

0 голосов
/ 28 января 2015

У меня есть другой рабочий пример в этом другом посте , если это помогает кому-то еще.

Если вы убедитесь, что используете клавишу длиной 32 символа /secret "и длиной 16 символов IV как в PHP, так и в Node, а также в кодировке base64 и в кодировке utf8 в Node, поэтому у вас не должно возникнуть проблем с различиями в схеме заполнения.

С уважением, Игнасио

0 голосов
/ 25 сентября 2013

Если вы застряли в сторонней библиотеке, которая использует 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);

Вы можете сделатьто же самое в узле:

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

узел-rijndael на GitHub

узел-rijndael на npm

0 голосов
/ 07 января 2012

Node.js делает что-то волшебное с вашим входным паролем для получения ключа и iv. Трудно понять, как это будет работать в PHP, если PHP не использует точно такой же ключ и магию деривации 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. Если вы управляете ключевыми и iv параметрами, вам будет гораздо проще определить, можете ли вы передавать данные в PHP.

Вы не можете просто хэшировать пароль для генерации iv. Предполагается, что iv должен быть разным для каждого зашифрованного сообщения, в противном случае он бесполезен.

Кроме того, вы говорите Node.js, что ваша входная строка "Да" закодирована в Base64, но я думаю, что это действительно ASCII или UTF-8.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...