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

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

Mi problema: para encriptar 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 al encriptar 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 saber los parámetros AES utilizados por CryptoJS al cifrar un mensaje ( No estoy seguro de qué son, pero suena como: tamaño de clave (256), relleno (pkcs5), modo (CBC), algoritmo PBE (PBKDF2), sal ( al azar), recuento de iteraciones (100) ). Sería de gran ayuda si alguien pudiera confirmarlo … He estado tratando de resolver este misterio en los últimos días.

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

CryptoJS utiliza el OpenSSL KDF no estandarizado para la derivación de claves ( 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 el texto cifrado, la contraseña y el sal se necesitan para descifrar esto en Java.

En otras palabras, PBKDF2 no se usa para derivación de clave en el modo de contraseña de CryptoJS. Por defecto, AES-256 se usa en modo CBC con relleno PKCS5 (que es lo mismo que relleno de PKCS7 ). Tenga en cuenta que puede necesitar los archivos de la Política de Jurisdicción de Fuerza Ilimitada de la JCE . Consulte también ¿Por qué existen limitaciones para usar el 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 proceden 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://stackoverflow.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 de 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 viendo la documentación aquí:

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

Cosas para generar la clave:

  • salt: generado y almacenado en el objeto de texto cifrado: usar con “encrypted.salt” (aunque realmente no dice eso, así que estoy adivinando aquí)
  • algoritmo pbe: Incierto. No está documentado.
  • recuento de iteraciones: no puedo encontrar esto documentado en ningún lado. Los ejemplos en el código parecen usar 1000.

Puede establecer los parámetros a mano, lo que es quizás más seguro que confiar en los valores predeterminados, por ejemplo, algún pseudocódigo basado 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 tengas que experimentar. Yo lo tomaría paso a paso. Obtenga las claves basadas en contraseña para que coincidan al manipular esos parámetros, luego haga que el texto cifrado coincida y luego descifre el descifrado. Evite el impulso de simplificar cosas como saltarse el IV o usar ECB.