Saltar al contenido

Cifrar string en PHP y descifrar en Node.js

Hola, hallamos la respuesta a tu búsqueda, desplázate y la obtendrás un poco más abajo.

Solución:

Estaba luchando con el mismo problema esta semana pero de la manera opuesta (PHP encripta -> NodeJS desencripta) y había logrado que este fragmento funcionara:

aes256cbc.js

var crypto = require('crypto');

var encrypt = function (plain_text, encryptionMethod, secret, iv) 
    var encryptor = crypto.createCipheriv(encryptionMethod, secret, iv);
    return encryptor.update(plain_text, 'utf8', 'base64') + encryptor.final('base64');
;

var decrypt = function (encryptedMessage, encryptionMethod, secret, iv) 
    var decryptor = crypto.createDecipheriv(encryptionMethod, secret, iv);
    return decryptor.update(encryptedMessage, 'base64', 'utf8') + decryptor.final('utf8');
;

var textToEncrypt = new Date().toISOString().substr(0,19) + '|My super secret information.';
var encryptionMethod = 'AES-256-CBC';
var secret = "My32charPasswordAndInitVectorStr"; //must be 32 char length
var iv = secret.substr(0,16);

var encryptedMessage = encrypt(textToEncrypt, encryptionMethod, secret, iv);
var decryptedMessage = decrypt(encryptedMessage, encryptionMethod, secret, iv);

console.log(encryptedMessage);
console.log(decryptedMessage);

aes256cbc.php


El secreto aquí para evitar caer en problemas de clave / tamaño iv / descifrado es tener el secreto de exactamente 32 caracteres de longitud y 16 para el IV. Además, es MUY Es importante usar ‘base64’ y ‘utf8’ en NodeJS ya que estos son los valores predeterminados en PHP.

A continuación, se muestran algunas ejecuciones de muestra:

$ node aes256cbc.js && php aes256cbc.php
zra3FX4iyCc7qPc1dZs+G3ZQ40f5bSw8P9n5OtWl1t86nV5Qfh4zNRPFbsciyyHyU3Qi4Ga1oTiTwzrPIZQXLw==
2015-01-27T18:29:12|My super secret information.
zra3FX4iyCc7qPc1dZs+G3ZQ40f5bSw8P9n5OtWl1t86nV5Qfh4zNRPFbsciyyHyU3Qi4Ga1oTiTwzrPIZQXLw==
2015-01-27T18:29:12|My super secret information.

$ node aes256cbc.js && php aes256cbc.php
zra3FX4iyCc7qPc1dZs+G6B6+8aavHNc/Ymv9L6Omod8Di3tMbvOa2B7O2Yiyoutm9fy9l0G+P5VJT9z2qNESA==
2015-01-27T18:29:15|My super secret information.
zra3FX4iyCc7qPc1dZs+G6B6+8aavHNc/Ymv9L6Omod8Di3tMbvOa2B7O2Yiyoutm9fy9l0G+P5VJT9z2qNESA==
2015-01-27T18:29:15|My super secret information.

$ node aes256cbc.js && php aes256cbc.php
zra3FX4iyCc7qPc1dZs+G4oD1Fr5yLByON6QDE56UOqP6kkfGJzpyH6TbwZYX2oGlh2JGv+aHYUMh0qQnAj/uw==
2015-01-27T18:29:29|My super secret information.
zra3FX4iyCc7qPc1dZs+G4oD1Fr5yLByON6QDE56UOqP6kkfGJzpyH6TbwZYX2oGlh2JGv+aHYUMh0qQnAj/uw==
2015-01-27T18:29:29|My super secret information.

$ node aes256cbc.js && php aes256cbc.php
zra3FX4iyCc7qPc1dZs+G5OVCbCaUy8a0LLF+Bn8UT4X3nYbtynO0Zt2mvXnnli9dRxrxMw43uWnkh8MIwVHXA==
2015-01-27T18:29:31|My super secret information.
zra3FX4iyCc7qPc1dZs+G5OVCbCaUy8a0LLF+Bn8UT4X3nYbtynO0Zt2mvXnnli9dRxrxMw43uWnkh8MIwVHXA==
2015-01-27T18:29:31|My super secret information.

$ node aes256cbc.js && php aes256cbc.php
fdsqSyHBJjlwD0jYfOUZM2FrONG6Fk5d7FOItYEdbnaZIhhmg/apa8/jPwKFkDXD9eNqWC3w0JzY5wjtZADiBA==
2015-01-27T18:30:08|My super secret information.
fdsqSyHBJjlwD0jYfOUZM2FrONG6Fk5d7FOItYEdbnaZIhhmg/apa8/jPwKFkDXD9eNqWC3w0JzY5wjtZADiBA==
2015-01-27T18:30:08|My super secret information.

$ node aes256cbc.js && php aes256cbc.php
fdsqSyHBJjlwD0jYfOUZM4SRfi6jG5EoDFEF6d9xCIyluXSiMaKlhd89ovpeOz/YyEIlPbYR4ly00gf6hWfKHw==
2015-01-27T18:30:45|My super secret information.
fdsqSyHBJjlwD0jYfOUZM4SRfi6jG5EoDFEF6d9xCIyluXSiMaKlhd89ovpeOz/YyEIlPbYR4ly00gf6hWfKHw==
2015-01-27T18:30:45|My super secret information.

NOTA:

Uso un “marca de tiempo|mensaje“formato para evitar hombre en el medio ataca. Por ejemplo, si el mensaje cifrado contiene una identificación para autenticarse, el MitM podría capturar el mensaje y reenviarlo cada vez que quiera volver a autenticarse.

Por lo tanto, podría verificar que la marca de tiempo en el mensaje cifrado esté dentro de un pequeño intervalo de tiempo. De esta forma, el mismo mensaje se cifra de forma diferente cada segundo debido a la marca de tiempo y no se puede utilizar fuera de este intervalo de tiempo fijo.

EDITAR:

Aquí estaba haciendo un mal uso del vector de inicialización (IV). Como @ArtjomB. Explicado, el IV debería ser la primera parte del mensaje cifrado, y también debería ser un valor aleatorio. También se recomienda utilizar un hmac valor en un encabezado HTTP (x-hmac: *value*) para validar que el mensaje se originó a partir de una fuente válida (pero esto no aborda el problema del mensaje de “reenvío” descrito anteriormente).

Aquí está la versión mejorada, incluida la hmac para php y node y el IV como parte del mensaje cifrado:

aes256cbc.js (v2)

var crypto = require('crypto');

var encrypt = function (message, method, secret, hmac) 
    //var iv = crypto.randomBytes(16).toString('hex').substr(0,16);    //use this in production
    var iv = secret.substr(0,16);    //using this for testing purposes (to have the same encryption IV in PHP and Node encryptors)
    var encryptor = crypto.createCipheriv(method, secret, iv);
    var encrypted = new Buffer(iv).toString('base64') + encryptor.update(message, 'utf8', 'base64') + encryptor.final('base64');
    hmac.value = crypto.createHmac('md5', secret).update(encrypted).digest('hex');
    return encrypted;
;

var decrypt = function (encrypted, method, secret, hmac) 
    if (crypto.createHmac('md5', secret).update(encrypted).digest('hex') == hmac.value) 
        var iv = new Buffer(encrypted.substr(0, 24), 'base64').toString();
        var decryptor = crypto.createDecipheriv(method, secret, iv);
        return decryptor.update(encrypted.substr(24), 'base64', 'utf8') + decryptor.final('utf8');
    
;

var encryptWithTSValidation = function (message, method, secret, hmac) 
    var messageTS = new Date().toISOString().substr(0,19) + message;
    return encrypt(messageTS, method, secret, hmac);


var decryptWithTSValidation = function (encrypted, method, secret, hmac, intervalThreshold) 
    var decrypted = decrypt(encrypted, method, secret, hmac);
    var now = new Date();
    var year = parseInt(decrypted.substr(0,4)), month = parseInt(decrypted.substr(5,2)) - 1,
    day = parseInt(decrypted.substr(8,2)), hour = parseInt(decrypted.substr(11,2)), 
    minute = parseInt(decrypted.substr(14,2)), second = parseInt(decrypted.substr(17,2));
    var msgDate = new Date(Date.UTC(year, month, day, hour, minute, second))
    if (Math.round((now - msgDate) / 1000) <= intervalThreshold) 
        return decrypted.substr(19);
    


var message = 'My super secret information.';
var method = 'AES-256-CBC';
var secret = "My32charPasswordAndInitVectorStr"; //must be 32 char length
var hmac = ;

//var encrypted = encrypt(message, method, secret, hmac);
//var decrypted = decrypt(encrypted, method, secret, hmac);
var encrypted = encryptWithTSValidation(message, method, secret, hmac);
var decrypted = decryptWithTSValidation(encrypted, method, secret, hmac, 60*60*12); //60*60m*12=12h

console.log("Use HTTP header 'x-hmac: " + hmac.value + "' for validating against MitM-attacks.");
console.log("Encrypted: " + encrypted);
console.log("Decrypted: " + decrypted);

Tenga en cuenta que crypto.createHmac(...).digest('hex') se digiere con hex. Este es el valor predeterminado en PHP para hmac.

aes256cbc.php (v2)

getTimestamp() - $msgDate->getTimestamp()) <= $intervalThreshold) 
        return substr($decrypted,19);
    


$message = "My super secret information.";
$method = "AES-256-CBC";
$secret = "My32charPasswordAndInitVectorStr";  //must be 32 char length

//$encrypted = encrypt($message, $method, $secret, $hmac);
//$decrypted = decrypt($encrypted, $method, $secret, $hmac);

$encrypted = encryptWithTSValidation($message, $method, $secret, $hmac);
$decrypted = decryptWithTSValidation($encrypted, $method, $secret, $hmac, 60*60*12); //60*60m*12=12h

echo "Use HTTP header 'x-hmac: $hmac' for validating against MitM-attacks.n";
echo "Encrypted: $encryptedn";
echo "Decrypted: $decryptedn";
?>

A continuación, se muestran algunas ejecuciones de muestra:

$ node aes256cbc.js && php aes256cbc.php
Use HTTP header 'x-hmac: 6862972ef0f463bf48523fc9e334bb42' for validating against MitM-attacks.
Encrypted: YjE0ZzNyMHNwVm50MGswbQ==I6cAKeoxeSP5TGgtK59PotB/iG2BUSU8Y6NhAhVabN9UB+ZCTn7q2in4JyLwQiGN
Decrypted: My super secret information.
Use HTTP header 'x-hmac: 6862972ef0f463bf48523fc9e334bb42' for validating against MitM-attacks.
Encrypted: YjE0ZzNyMHNwVm50MGswbQ==I6cAKeoxeSP5TGgtK59PotB/iG2BUSU8Y6NhAhVabN9UB+ZCTn7q2in4JyLwQiGN
Decrypted: My super secret information.

$ node aes256cbc.js && php aes256cbc.php
Use HTTP header 'x-hmac: b2e63f216acde938a82142220652cf59' for validating against MitM-attacks.
Encrypted: YjE0ZzNyMHNwVm50MGswbQ==YsFRdKzCLuCk7Yg+U+S1CSgYBBR8dkZytORm8xwEDmD9WB1mpqC3XnSrB+wR3/KW
Decrypted: My super secret information.
Use HTTP header 'x-hmac: b2e63f216acde938a82142220652cf59' for validating against MitM-attacks.
Encrypted: YjE0ZzNyMHNwVm50MGswbQ==YsFRdKzCLuCk7Yg+U+S1CSgYBBR8dkZytORm8xwEDmD9WB1mpqC3XnSrB+wR3/KW
Decrypted: My super secret information.

$ node aes256cbc.js && php aes256cbc.php
Use HTTP header 'x-hmac: 73181744453d55eb6f81896ffd284cd8' for validating against MitM-attacks.
Encrypted: YjE0ZzNyMHNwVm50MGswbQ==YsFRdKzCLuCk7Yg+U+S1CTGik4Lv9PnWuEg5SiADJcdKX1to0LrNKmuCiYIweBAZ
Decrypted: My super secret information.
Use HTTP header 'x-hmac: 73181744453d55eb6f81896ffd284cd8' for validating against MitM-attacks.
Encrypted: YjE0ZzNyMHNwVm50MGswbQ==YsFRdKzCLuCk7Yg+U+S1CTGik4Lv9PnWuEg5SiADJcdKX1to0LrNKmuCiYIweBAZ
Decrypted: My super secret information.

$ node aes256cbc.js && php aes256cbc.php
Use HTTP header 'x-hmac: 5372ecca442d65f582866cf3b24cb2b6' for validating against MitM-attacks.
Encrypted: YjE0ZzNyMHNwVm50MGswbQ==YsFRdKzCLuCk7Yg+U+S1CYEITF6aozBNp7bA54qY0Ugg9v6ktwoH6nqRyatkFqy8
Decrypted: My super secret information.
Use HTTP header 'x-hmac: 5372ecca442d65f582866cf3b24cb2b6' for validating against MitM-attacks.
Encrypted: YjE0ZzNyMHNwVm50MGswbQ==YsFRdKzCLuCk7Yg+U+S1CYEITF6aozBNp7bA54qY0Ugg9v6ktwoH6nqRyatkFqy8
Decrypted: My super secret information.

Por último, pero no menos importante, si no tiene el mod openssl instalado en php, puede usar mcrypt en cambio con rijndael128 y pkcs7 padding (fuente) así:

aes256cbc-mcrypt.php (v2)

getTimestamp() - $msgDate->getTimestamp()) <= $intervalThreshold) 
        return substr($decrypted,19);
    


$message = "My super secret information.";
$method = MCRYPT_RIJNDAEL_128;
$secret = "My32charPasswordAndInitVectorStr";  //must be 32 char length

//$encrypted = encrypt($message, $method, $secret, $hmac);
//$decrypted = decrypt($encrypted, $method, $secret, $hmac);

$encrypted = encryptWithTSValidation($message, $method, $secret, $hmac);
$decrypted = decryptWithTSValidation($encrypted, $method, $secret, $hmac, 60*60*12); //60*60m*12=12h

echo "Use HTTP header 'x-hmac: $hmac' for validating against MitM-attacks.n";
echo "Encrypted: $encryptedn";
echo "Decrypted: $decryptedn";
?>

Por supuesto, algunas pruebas a continuación:

$ php aes256cbc-mcrypt.php && node aes256cbc.js 
Use HTTP header 'x-hmac: 801282a9ed6b2d5bd2254140d7a17582' for validating against MitM-attacks.
Encrypted: YjE0ZzNyMHNwVm50MGswbQ==ipQ+Yah8xoF0C6yjCJr8v9IyatyGeNT2yebrpJZ5xH73H5fFcV1zhqhRGwM0ToGU
Decrypted: My super secret information.
Use HTTP header 'x-hmac: 801282a9ed6b2d5bd2254140d7a17582' for validating against MitM-attacks.
Encrypted: YjE0ZzNyMHNwVm50MGswbQ==ipQ+Yah8xoF0C6yjCJr8v9IyatyGeNT2yebrpJZ5xH73H5fFcV1zhqhRGwM0ToGU
Decrypted: My super secret information.
$ php aes256cbc-mcrypt.php && node aes256cbc.js 
Use HTTP header 'x-hmac: 0ab2bc83108e1e250f6ecd483cd65329' for validating against MitM-attacks.
Encrypted: YjE0ZzNyMHNwVm50MGswbQ==ipQ+Yah8xoF0C6yjCJr8v79P+j4YUl8ln8eu7FDqEdbxMe1Z7BvW8iVUN1qFCiHM
Decrypted: My super secret information.
Use HTTP header 'x-hmac: 0ab2bc83108e1e250f6ecd483cd65329' for validating against MitM-attacks.
Encrypted: YjE0ZzNyMHNwVm50MGswbQ==ipQ+Yah8xoF0C6yjCJr8v79P+j4YUl8ln8eu7FDqEdbxMe1Z7BvW8iVUN1qFCiHM
Decrypted: My super secret information.

Cuando se trata de un cifrado simétrico como este, el primer paso es darse cuenta de que probablemente será un gran dolor en la parte trasera: nunca, nunca lo había hecho funcionar de inmediato, incluso cuando estaba copiando y pegando mi propio código. Esto se debe principalmente a que los métodos de cifrado y descifrado son, por diseño, absolutamente implacables y rara vez dan mensajes de error útiles. Un solo null El carácter, el retorno de carro, el salto de línea o el tipo convertido dinámicamente pueden hacer explotar silenciosamente todo el proceso.

Sabiendo esto, progrese paso a paso. Sugiero lo siguiente:

Primero, haz que PHP funcione solo. Pase texto de muestra, cifre, descifre inmediatamente y compárelo con estricta igualdad con la variable de texto claro original. ¿Son perfectamente iguales? Salida de ambos, ¿son del mismo tipo y parecen perfectamente sin ser molestados? Tenga cuidado con los caracteres no impresos: compruebe también la longitud y la codificación de caracteres.

Ahora, haga lo anterior con un texto de muestra más de 1 carácter más o menos que el anterior. Esto depura problemas de tamaño de bloque / relleno de ceros, es importante.

Si eso está funcionando, y rara vez lo hace de inmediato, por razones difíciles de predecir, continúe con Node.js.

En Node.js, haga lo mismo que hizo en PHP, incluso si parece un esfuerzo en vano, por razones adicionales que serán obvias en un momento. Cifre y descifre, todos juntos, en su Node.js. ¿Funciona con las mismas condiciones dadas anteriormente?

Una vez hecho esto, aquí viene la parte 'divertida': utilizando los mismos métodos de cifrado de forma independiente en Node.js y PHP, haga que ambos le envíen el texto criptográfico 'final' listo para transmitir que ambos produjeron.

Si todo va bien, deberían ser perfectos, exactamente iguales. Si no es así, tiene un problema con sus implementaciones y métodos de cifrado que no son compatibles entre sistemas. Alguna configuración es incorrecta o conflictiva (quizás con relleno de ceros o una serie de otras posibilidades, o IV, etc.), o necesita probar una implementación diferente.

Si tuviera que adivinar a ciegas, diría que hay un problema con la codificación y decodificación base64 (lo más común es lo que sale mal). Las cosas tienden a hacerse dos veces, porque puede ser complicado depurar tipos de datos binarios en aplicaciones web (a través de un navegador). A veces, las cosas se codifican dos veces pero solo se decodifican una vez, o una implementación codificará / decodificará 'útilmente' algo automáticamente sin tener claro que eso es lo que está haciendo, etc.

También es posible que sea un problema de implementación de relleno cero entre Node y PHP, como se sugiere aquí: Encriptar AES en Node.js Desencriptar en PHP. Fallar.

Estos dos últimos problemas están fuertemente sugeridos por sus códigos de error. Los métodos de cifrado predicen tamaños de bloque de longitud precisa, y si están desactivados, eso indica la corrupción de los datos que se pasan a las funciones, lo que ocurre si se inserta un solo carácter adicional, o si la codificación se maneja de manera diferente, etc.

Si recorre cada uno de los anteriores uno a la vez, asegurándose de que no puede apresurarse y debe verificar cada pequeño y minucioso paso del proceso, debería ser mucho más claro dónde exactamente van mal las cosas, y luego eso puede ser solucionado.

Acuérdate de que tienes la opción de valorar esta crónica si te fue de ayuda.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *