Cifrado en JavaScript y descifrado con PHP
encryption mcrypt (2)
El problema es que en el código CryptoJS se usa una contraseña para derivar la clave y el IV para el cifrado AES, pero mcrypt solo usa la clave para cifrar / descifrar. Esta información debe pasarse a php. Como no desea transmitir la contraseña, debe derivar la clave y el IV de la misma manera en php.
El siguiente código deriva la clave y el IV de una contraseña y salt. Está modelado después del código en mi respuesta here (para más información).
function evpKDF($password, $salt, $keySize = 8, $ivSize = 4, $iterations = 1, $hashAlgorithm = "md5") {
$targetKeySize = $keySize + $ivSize;
$derivedBytes = "";
$numberOfDerivedWords = 0;
$block = NULL;
$hasher = hash_init($hashAlgorithm);
while ($numberOfDerivedWords < $targetKeySize) {
if ($block != NULL) {
hash_update($hasher, $block);
}
hash_update($hasher, $password);
hash_update($hasher, $salt);
$block = hash_final($hasher, TRUE);
$hasher = hash_init($hashAlgorithm);
// Iterations
for ($i = 1; $i < $iterations; $i++) {
hash_update($hasher, $block);
$block = hash_final($hasher, TRUE);
$hasher = hash_init($hashAlgorithm);
}
$derivedBytes .= substr($block, 0, min(strlen($block), ($targetKeySize - $numberOfDerivedWords) * 4));
$numberOfDerivedWords += strlen($block)/4;
}
return array(
"key" => substr($derivedBytes, 0, $keySize * 4),
"iv" => substr($derivedBytes, $keySize * 4, $ivSize * 4)
);
}
La sal se genera durante el cifrado en CryptoJS y debe enviarse a php con el texto cifrado.
Antes de invocar
evpKDF
la sal debe convertirse a una cadena binaria desde hexadecimal.
$keyAndIV = evpKDF("Secret Passphrase", hex2bin($saltHex));
$decryptPassword = mcrypt_decrypt(MCRYPT_RIJNDAEL_128,
$keyAndIV["key"],
hex2bin($cipherTextHex),
MCRYPT_MODE_CBC,
$keyAndIV["iv"]);
Si solo se envió
encryptedPassword.toString()
al servidor, entonces es necesario dividir la sal y el texto cifrado real antes de su uso.
El formato es un formato compatible con OpenSSL patentado con los primeros 8 bytes "Salados__", los siguientes 8 bytes son la sal aleatoria y el resto es el texto cifrado real.
Todo junto está codificado en Base64.
function decrypt($ciphertext, $password) {
$ciphertext = base64_decode($ciphertext);
if (substr($ciphertext, 0, 8) != "Salted__") {
return false;
}
$salt = substr($ciphertext, 8, 8);
$keyAndIV = evpKDF($password, $salt);
$decryptPassword = mcrypt_decrypt(MCRYPT_RIJNDAEL_128,
$keyAndIV["key"],
substr($ciphertext, 16),
MCRYPT_MODE_CBC,
$keyAndIV["iv"]);
// unpad (PKCS#7)
return substr($decryptPassword, 0, strlen($decryptPassword) - ord($decryptPassword[strlen($decryptPassword)-1]));
}
Lo mismo se puede lograr con la extensión OpenSSL en lugar de Mcrypt:
function decrypt($ciphertext, $password) {
$ciphertext = base64_decode($ciphertext);
if (substr($ciphertext, 0, 8) != "Salted__") {
return false;
}
$salt = substr($ciphertext, 8, 8);
$keyAndIV = evpKDF($password, $salt);
$decryptPassword = openssl_decrypt(
substr($ciphertext, 16),
"aes-256-cbc",
$keyAndIV["key"],
OPENSSL_RAW_DATA, // base64 was already decoded
$keyAndIV["iv"]);
return $decryptPassword;
}
Estoy encriptando mi contraseña de usuario en JavaScript así:
var encryptedPassword = CryptoJS.AES.encrypt(password, "Secret Passphrase");
Funciona bien, pero ahora estoy tratando de descifrar en PHP en el lado del servidor de esta manera:
$iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC), MCRYPT_RAND);
$decryptPassword = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, "Secret Passphrase", base64_decode($password), MCRYPT_MODE_CBC, $iv);
no funciona en absoluto, la cadena de contraseña descifrada se ve muy extraña:
string(64) ">�OX2MS��댗v�<$�ʕ��i�̄��_��P���/�կ=�_6(�m����,4WT7��a"
Aquí está el estado actual de mi código en JavaScript después de los útiles comentarios:
var encryptedPassword = CryptoJS.AES.encrypt(password, "Secret Passphrase");
var ivHex = encryptedPassword.iv.toString();
var ivSize = encryptedPassword.algorithm.ivSize; // same as blockSize
var keySize = encryptedPassword.algorithm.keySize;
var keyHex = encryptedPassword.key.toString();
var saltHex = encryptedPassword.salt.toString(); // must be sent
var openSslFormattedCipherTextString = encryptedPassword.toString(); // not used
var cipherTextHex = encryptedPassword.ciphertext.toString(); // must be sent
Estoy enviando saltHex y CipherTextHex al servidor PHP y estoy usando mcrypt_decrypt () de esta manera:
$iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC), $saltHex);
$decryptPassword = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, "Secret Passphrase", base64_decode($cipherTextHex), MCRYPT_MODE_CBC, $iv);
Todavía no funciona con este código actualizado.
¿Alguien puede ayudarme a descifrar correctamente con la función PHP mcrypt_decrypt () para un método de cifrado AES simple? Estoy seguro de que estoy haciendo algo mal con el cifrado, el modo mcrypt y los parámetros IV dentro de mi método mcrypt_decrypt (). Gracias si lo sabes.
No puede descifrar con un vector de inicialización aleatorio: debe usar el mismo IV con el que se cifraron los datos. Además, IIRC, AES adopta por defecto una representación de 8 bits de los datos cifrados que deberán manejarse con cuidado en la transferencia a través de HTTP.