2011-05-18 63 views
9

En Node.js, utilizo la estructura en la función para cifrar los datos de esa manera:cifrar AES en Node.js descifrar en PHP. Fallar.

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; 

la salida (texto cifrado) es: == OnNINwXf6U8XmlgKJj48iA

Luego uso descifrarlo en PHP:

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

Voy a obtener algunos personajes divertidos, que no puedo descifrarlo. Intenté con/sin base64_decode o MCRYPT_RIJNDAEL_128 ... todos fallaron.

Luego miro cómo el cifrado en PHP, se ve muy diferente de la salida de 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"; 

Se puede cifrar y descifrar. y los datos cifrados es: njCE/fk3pLD1/JfiQuyVa6w5H + Qb/utBIT3m7LAcetM =

que es muy diferente de la salida de Node.js aconsejar por favor cómo puedo cifrar y descifrar entre Node.js & php. Gracias. :)


@Mel aquí es lo que tengo en PHP:

$text = "Yes"; 

$key = "32BytesLongKey56ABCDEF"; 
$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"; 

Sin embargo, todavía no funciona.

La base cifrado 64 en PHP es: 80022AGM4/4qQtiGU5oJDQ == La base cifrado 64 en nodejs es: EoYRm5SCK7EPe847CwkffQ ==

por lo tanto, no puede descifrar el nodejs una en PHP.

Me pregunto si es porque nodejs no requiere $ iv?

Respuesta

1

AES es rijndael con tamaño fijo de 16 bytes IV. Detalles here. No se puede utilizar para descifrar. Más importante aún, no puedo descifrar su cadena usando openssl ya sea:

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

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

Salida:

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

Así que parece que es Node.js usando alguna característica no documentada para crear el IV y no veo forma de proporcionar el IV en node.js.

+0

¿tengo que mantener la clave de 32 bytes? – murvinlai

+0

no funciona. He intentado encriptar con AES-256 en node.js. luego descifre con RJ-128 con la misma clave de 32 bytes, y con 16 bytes iv. todavía fallan – murvinlai

+0

Los datos cifrados se codifican en base64 luego de ser encriptados. es por eso que hace la diferencia? – murvinlai

2

Estoy empezando a jugar con node.js, pero creo que su problema está relacionado con los IV no coincidentes. Trata de hacer lo siguiente en su lugar:

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

PS: No estoy seguro de cómo crear un hash MD5 en Node.js, que tendrá que figure it out for yourself y cambiar el código anterior en consecuencia.

Y en PHP:

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

Esto debe asegurarse de que ambas implementaciones utilizan el mismo vector de inicialización.

también recomiendan que su hacer los siguientes cambios:

  • contraseña MD5 (original_password)
  • iv = MD5 (MD5 (original_password))

Esto se asegurará de won PHP No arrojes ningún error estúpido. Veo Best way to use PHP to encrypt and decrypt passwords?

+0

gracias ... lo intentaré ... pero no veo que haya una función llamada crypto.createCipheriv en crypt API ¿Es de otro paquete npm? – murvinlai

+0

@murvinlai: No parece estar documentado, pero está allí: https://github.com/joyent/node/blob/9f0b1a9bc60f70b7c5c014743eb1edd69c36db76/lib/crypto.js#L118. –

16

Siete meses de retraso, pero he tenido problemas con esto también, y encontró una solución. Aparentemente, PHP rellena la entrada con cero bytes para hacer que su tamaño sea un múltiplo del tamaño del bloque. Por ejemplo, usando AES-128, los "contrabajistas" 14 byte de entrada se rellenan con dos cero bytes, como este:

"contrabassists\0\0" 

Un byte de entrada de bloque N * se queda solo.

Las funciones criptográficas Nodo estándar, sin embargo, utilizan un esquema de relleno diferente llamado PKCS5. PKCS5 no añade ceros, pero añade la longitud del relleno, lo que de nuevo usando un algoritmo AES-128, "contrabajistas" se convertirían en:

"contrabassists\2\2" 

Incluso un byte de entrada de bloque N * se rellena en PKCS5. De lo contrario, es imposible eliminar el relleno después de la decodificación. La entrada "spectroheliogram" pasaría a ser:

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

Para hacer cifrado PHP m_crypt compatible con el descifrado de nodo, que tendrá que rellenar la entrada de sí mismo:

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

el revés, que' Tendré que leer el último byte de los datos decodificados y cortar el relleno usted mismo.

funciones Ejemplo: (añadido 01/14/2012)

En PHP, esta función volvería AES-128 encriptado de datos, hex codificado que pueda ser descifrado por el Nodo:

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

en nodo, lo siguiente sería descifrar los datos:

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

no he hecho a la inversa, sin embargo, pero debe ser sencillo una vez que understa nd el esquema de relleno. No he hecho ninguna suposición sobre la generación clave/iv.

+0

gracias para la información. Lo intentaré ... ¿tiene algún ejemplo? – murvinlai

+0

He agregado algunas funciones de ejemplo que deberían funcionar de la caja – Michilus

+0

es útil muchas gracias. – Lupus

0

Node.js está haciendo un poco de magia con su contraseña de entrada para derivar una clave y iv. Es difícil ver cómo funcionaría en PHP a menos que PHP haga exactamente la misma clave y la magia de derivación iv.

Por qué no usa createCipheriv en su lugar. Use una función de derivación de clave basada en contraseña para crear una clave a partir de la contraseña.Por ejemplo:

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

Tal función está disponible en versiones posteriores de Node.js

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

Proporcionar un buen iv así; puedes crear uno usando crypto.randomBytes. Si controla la clave y los parámetros iv, le resultará mucho más fácil determinar si puede redondear sus datos a PHP.

No se puede simplemente hash la contraseña para generar un iv. Se supone que el iv es diferente para cada mensaje encriptado, de lo contrario es inútil.

Además, le está diciendo Node.js que su cadena de entrada "Sí" está codificado base 64, pero yo creo que es muy ASCII o UTF-8.

0

Si tiene una biblioteca de terceros que usa MCRYPT_RIJNDAEL_256, sepa que el 256 especifica el tamaño del bloque, no el tamaño de la clave. AES utiliza un tamaño de bloque fijo de 128 bits, y openssl no implementa algoritmos Rijndael más genéricos. Para eludir esto publiqué un módulo que enlaza al libmcrypt, tal como lo hace PHP. Es un caso de uso bastante limitado, pero asegura que será compatible con el bloque de 256 bits de tamaño rijndael.

Si está usando esto en PHP

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

Usted puede hacer lo mismo en el Nodo:

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

0

He otro ejemplo de trabajo in this other post si ayuda a alguien más.

Si se asegura de que utilice una longitud de 32 caracteres "clave/secreta" y un 16 caracteres de longitud IV, tanto en PHP y nodo, y base64 codificación de cifrado y utf8 codificación de mensajes en el nodo, entonces no debería tener ningún problema con cualquier diferencia en el esquema de relleno.

Saludos, Ignacio

1

me encontraron par de cosas que podrían ser las razones por las que el descifrado/cifrado en PHP y Node.js no son lo mismo.

PHP utiliza MCRYPT_RIJNDAEL_256 algoritmo. AES 256 se basa en MCRYPT_RIJNDAEL_256, pero no es lo mismo. AES 256 esto es estándar de encriptación, pero no algoritmo.

Si intenta encriptar algo mediante el uso de funciones simples estándar ("mcrypt_encrypt" y "mcrypt_decrypt" en PHP, por ejemplo), no puede ver todos los pasos y seguramente no puede saber por qué no puede descifrar eso que encriptaste Puede ser el mismo para Node.js, porque necesita usar una función que pueda encriptarse paso a paso para evitar la sustitución de los parámetros predeterminados.

para cifrar/descifrar algo que necesita saber (para ajustar):

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

y comprobar que funciona en ambos lados. Debería ser lo mismo. También es necesario encontrar la combinación correcta "método de cifrado" + "modo de cifrado", que seguramente funciona en ambos lados.

Mi solución es RIJNDAEL_256 + ECB. Debe instalar node-rijndael, porque usa RIJNDAEL_256 con seguridad. Si no, mi ejemplo no funcionará.

Aquí está el ejemplo de Node.js para el cifrado.

Instale node-rijndael en alguna carpeta donde deben estar dos archivos .js.

r256.js - es funciones para cifrar/descifrar. Lo encontré 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 - es ejemplo para el cifrado.

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

Aquí es ejemplo PHP para el descifrado.

<?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([email protected]_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. No sé por qué, pero Node.js ignora IV (en mi ejemplo), por lo que el cifrado siempre será el mismo. PHP siempre usa IV y debe ser de longitud estricta, por lo que PHP devuelve cifrados diffirent siempre. Pero lo intenté al revés (cifrar por PHP y descifrar por Node.js) y funciona.

Cuestiones relacionadas