javascript security encryption cryptojs

javascript - ¿Cuáles son los parámetros AES utilizados y los pasos realizados internamente por crypto-js mientras se encripta un mensaje con una contraseña?



security encryption (3)

Antecedentes: se supone que la aplicación en la que estoy trabajando debe funcionar sin conexión. Debería encriptar algunos datos de texto usando una contraseña como clave en el lado del servidor Java. Los datos cifrados se pasan a la página HTML5 y, en el lado del cliente, utilizando la biblioteca crypto-js, los datos cifrados del servidor deben descifrarse.

Mi problema: para cifrar mi mensaje de tal manera que el cliente pueda descifrarlo con crypt-js (usando una contraseña ingresada por el usuario), necesito saber los pasos exactos que crypto-js espera mientras encripta un mensaje.

Lo que necesito saber: tengo el siguiente código de cifrado que hace el cifrado de un mensaje en el lado del cliente usando crypto-js.

var message = "my message text"; var password = "user password"; var encrypted = CryptoJS.AES.encrypt( message ,password ); console.log(encrypted.toString());

Necesito conocer los parámetros AES utilizados por CryptoJS mientras encripto un mensaje ( No estoy seguro de lo que son, pero suena como: tamaño de clave (256), relleno (pkcs5), modo (CBC), algoritmo PBE (PBKDF2), sal ( aleatorio), recuento de iteraciones (100) ). Sería de gran ayuda si alguien pudiera confirmarlo ... ¿He estado tratando de resolver este misterio durante los últimos días?

Necesito saber los diferentes pasos realizados por CryptoJS mientras AES encripta un mensaje


CryptoJS uses el OpenSSL KDF no estandarizado para la derivación de clave ( EvpKDF ) con MD5 como algoritmo de hash y 1 iteración. El IV también se deriva de la contraseña, lo que significa que solo se necesita el texto cifrado real, la contraseña y la sal para descifrar esto en el lado Java.

En otras palabras, PBKDF2 no se utiliza para la derivación de claves en el modo de contraseña de CryptoJS. Por defecto, AES-256 se utiliza en modo CBC con relleno PKCS5 (que es lo mismo que relleno PKCS7 ). Tenga en cuenta que es posible que necesite los archivos de política de jurisdicción de fuerza ilimitada de JCE . Consulte también ¿Por qué hay limitaciones en el uso de cifrado con claves más allá de cierta longitud?

El siguiente código recrea el KDF en Java ( keySize e ivSize son 8 respectivamente 4 para AES-256 y provienen de).

public static byte[] evpKDF(byte[] password, int keySize, int ivSize, byte[] salt, int iterations, String hashAlgorithm, byte[] resultKey, byte[] resultIv) throws NoSuchAlgorithmException { int targetKeySize = keySize + ivSize; byte[] derivedBytes = new byte[targetKeySize * 4]; int numberOfDerivedWords = 0; byte[] block = null; MessageDigest hasher = MessageDigest.getInstance(hashAlgorithm); while (numberOfDerivedWords < targetKeySize) { if (block != null) { hasher.update(block); } hasher.update(password); block = hasher.digest(salt); hasher.reset(); // Iterations for (int i = 1; i < iterations; i++) { block = hasher.digest(block); hasher.reset(); } System.arraycopy(block, 0, derivedBytes, numberOfDerivedWords * 4, Math.min(block.length, (targetKeySize - numberOfDerivedWords) * 4)); numberOfDerivedWords += block.length/4; } System.arraycopy(derivedBytes, 0, resultKey, 0, keySize * 4); System.arraycopy(derivedBytes, keySize * 4, resultIv, 0, ivSize * 4); return derivedBytes; // key + iv }

Aquí está la clase completa para referencia:

public class RecreateEVPkdfFromCryptoJS { public static void main(String[] args) throws UnsupportedEncodingException, GeneralSecurityException { String msg = "hello"; String password = "mypassword"; String ivHex = "aab7d6aca0cc6ffc18f9f5909753aa5f"; int keySize = 8; // 8 words = 256-bit int ivSize = 4; // 4 words = 128-bit String keyHex = "844a86d27d96acf3147aa460f535e20e989d1f8b5d79c0403b4a0f34cebb093b"; String saltHex = "ca35168ed6b82778"; String openSslFormattedCipherTextString = "U2FsdGVkX1/KNRaO1rgneK9S3zuYaYZcdXmVKJGqVqk="; String cipherTextHex = "af52df3b9869865c7579952891aa56a9"; String padding = "PKCS5Padding"; byte[] key = hexStringToByteArray(keyHex); byte[] iv = hexStringToByteArray(ivHex); byte[] salt = hexStringToByteArray(saltHex); byte[] cipherText = hexStringToByteArray(cipherTextHex); byte[] javaKey = new byte[keySize * 4]; byte[] javaIv = new byte[ivSize * 4]; evpKDF(password.getBytes("UTF-8"), keySize, ivSize, salt, javaKey, javaIv); System.out.println(Arrays.equals(key, javaKey) + " " + Arrays.equals(iv, javaIv)); Cipher aesCipherForEncryption = Cipher.getInstance("AES/CBC/PKCS5Padding"); // Must specify the mode explicitly as most JCE providers default to ECB mode!! IvParameterSpec ivSpec = new IvParameterSpec(javaIv); aesCipherForEncryption.init(Cipher.DECRYPT_MODE, new SecretKeySpec(javaKey, "AES"), ivSpec); byte[] byteMsg = aesCipherForEncryption.doFinal(cipherText); System.out.println(Arrays.equals(byteMsg, msg.getBytes("UTF-8"))); } public static byte[] evpKDF(byte[] password, int keySize, int ivSize, byte[] salt, byte[] resultKey, byte[] resultIv) throws NoSuchAlgorithmException { return evpKDF(password, keySize, ivSize, salt, 1, "MD5", resultKey, resultIv); } public static byte[] evpKDF(byte[] password, int keySize, int ivSize, byte[] salt, int iterations, String hashAlgorithm, byte[] resultKey, byte[] resultIv) throws NoSuchAlgorithmException { int targetKeySize = keySize + ivSize; byte[] derivedBytes = new byte[targetKeySize * 4]; int numberOfDerivedWords = 0; byte[] block = null; MessageDigest hasher = MessageDigest.getInstance(hashAlgorithm); while (numberOfDerivedWords < targetKeySize) { if (block != null) { hasher.update(block); } hasher.update(password); block = hasher.digest(salt); hasher.reset(); // Iterations for (int i = 1; i < iterations; i++) { block = hasher.digest(block); hasher.reset(); } System.arraycopy(block, 0, derivedBytes, numberOfDerivedWords * 4, Math.min(block.length, (targetKeySize - numberOfDerivedWords) * 4)); numberOfDerivedWords += block.length/4; } System.arraycopy(derivedBytes, 0, resultKey, 0, keySize * 4); System.arraycopy(derivedBytes, keySize * 4, resultIv, 0, ivSize * 4); return derivedBytes; // key + iv } /** * Copied from http://.com/a/140861 * */ public static byte[] hexStringToByteArray(String s) { int len = s.length(); byte[] data = new byte[len / 2]; for (int i = 0; i < len; i += 2) { data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i+1), 16)); } return data; } }

y el código JavaScript que se usó para la generación de los valores en el código Java:

var msg = "hello"; var password = "mypassword"; // must be present on the server var encrypted = CryptoJS.AES.encrypt( msg, password ); var ivHex = encrypted.iv.toString(); var ivSize = encrypted.algorithm.ivSize; // same as the blockSize var keySize = encrypted.algorithm.keySize; var keyHex = encrypted.key.toString(); var saltHex = encrypted.salt.toString(); // must be sent as well var openSslFormattedCipherTextString = encrypted.toString(); // not used var cipherTextHex = encrypted.ciphertext.toString(); // must be sent


Estoy mirando la documentación aquí:

  • tamaño de la clave: "Si usa una frase de contraseña, generará una clave de 256 bits".
  • relleno: Pkcs7 (el valor predeterminado)
  • modo: CBC (el valor predeterminado)
  • iv: generado y almacenado en el objeto de texto cifrado: uso con "encrypted.iv"

Cosas para generar la clave:

  • salt: generado y almacenado en el objeto de texto cifrado: utilícelo con "encrypted.salt" (aunque en realidad no dice eso, así que supongo que aquí)
  • algoritmo pbe: poco claro. No está documentado
  • recuento de iteraciones: no puedo encontrar esto documentado en ninguna parte. Los ejemplos en el código parecen usar 1000.

Puede configurar los parámetros a mano, lo que puede ser más seguro que confiar en los valores predeterminados, por ejemplo, algunos pseudocódigos basados ​​en los ejemplos de la documentación:

var salt = CryptoJS.lib.WordArray.random(128/8); var iv = CryptoJS.lib.WordArray.random(128); var key256Bits10000Iterations = CryptoJS.PBKDF2("Secret Passphrase", salt, { keySize: 256/32, iterations: 10000 }); //I don''t know this is dividing by 32 var encrypted = CryptoJS.AES.encrypt("Message", key, { mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7, iv:iv });

Probablemente tendrás que experimentar. Lo daría un paso a la vez. Obtenga las claves basadas en contraseña para que coincidan al manipular esos parámetros, luego obtenga el texto cifrado para que coincida y luego descifre. Evite la necesidad de simplificar cosas como omitir la vía intravenosa o usar el BCE.


Siguiendo la gran respuesta de @Artjom B tanto en esta pregunta como aquí para los usuarios de Python , me uno al código completo de Java que me ayudó a descifrar una cadena que se cifró de esta manera

var encrypted = CryptoJS.AES.encrypt(message, password).toString();

Este fragmento de código Java es útil cuando solo conoce la contraseña (es decir, no se envió sal con la cadena cifrada):

public String decrypt(String encrypted, String password) throws Exception { int keySize = 8; int ivSize = 4; // Start by decoding the encrypted string (Base64) // Here I used the Android implementation (other Java implementations might exist) byte[] cipherText = Base64.decode(encrypted, Base64.DEFAULT); // prefix (first 8 bytes) is not actually useful for decryption, but you should probably check that it is equal to the string "Salted__" byte[] prefix = new byte[8]; System.arraycopy(cipherText, 0, prefix, 0, 8); // Check here that prefix is equal to "Salted__" // Extract salt (next 8 bytes) byte[] salt = new byte[8]; System.arraycopy(cipherText, 8, salt, 0, 8); // Extract the actual cipher text (the rest of the bytes) byte[] trueCipherText = new byte[cipherText.length - 16]; System.arraycopy(cipherText, 16, trueCipherText, 0, cipherText.length - 16); byte[] javaKey = new byte[keySize * 4]; byte[] javaIv = new byte[ivSize * 4]; evpKDF(password.getBytes("UTF-8"), keySize, ivSize, salt, javaKey, javaIv); Cipher aesCipherForEncryption = Cipher.getInstance("AES/CBC/PKCS5Padding"); IvParameterSpec ivSpec = new IvParameterSpec(javaIv); aesCipherForEncryption.init(Cipher.DECRYPT_MODE, new SecretKeySpec(javaKey, "AES"), ivSpec); byte[] byteMsg = aesCipherForEncryption.doFinal(trueCipherText); return new String(byteMsg, "UTF-8"); } public byte[] evpKDF(byte[] password, int keySize, int ivSize, byte[] salt, byte[] resultKey, byte[] resultIv) throws NoSuchAlgorithmException { return evpKDF(password, keySize, ivSize, salt, 1, "MD5", resultKey, resultIv); } public byte[] evpKDF(byte[] password, int keySize, int ivSize, byte[] salt, int iterations, String hashAlgorithm, byte[] resultKey, byte[] resultIv) throws NoSuchAlgorithmException { int targetKeySize = keySize + ivSize; byte[] derivedBytes = new byte[targetKeySize * 4]; int numberOfDerivedWords = 0; byte[] block = null; MessageDigest hasher = MessageDigest.getInstance(hashAlgorithm); while (numberOfDerivedWords < targetKeySize) { if (block != null) { hasher.update(block); } hasher.update(password); block = hasher.digest(salt); hasher.reset(); // Iterations for (int i = 1; i < iterations; i++) { block = hasher.digest(block); hasher.reset(); } System.arraycopy(block, 0, derivedBytes, numberOfDerivedWords * 4, Math.min(block.length, (targetKeySize - numberOfDerivedWords) * 4)); numberOfDerivedWords += block.length/4; } System.arraycopy(derivedBytes, 0, resultKey, 0, keySize * 4); System.arraycopy(derivedBytes, keySize * 4, resultIv, 0, ivSize * 4); return derivedBytes; // key + iv }