使用密码加密消息时,crypto-js 使用了哪些 AES 参数以及在内部执行的步骤是什么?

IT技术 javascript security encryption cryptojs
2021-03-01 18:49:47

背景: 我正在开发的应用程序应该可以脱机工作。我应该在java服务器端使用密码作为密钥来加密一些文本数据。加密的数据被传递到 HTML5 页面,在客户端使用 crypto-js 库,服务器加密的数据应该被解密。

我的问题: 为了以客户端可以使用 crypt-js(使用用户输入的密码)解密的方式加密我的消息,我需要知道 crypto-js 在加密消息时期望的确切步骤。

我需要知道的是: 我有以下加密代码,它使用 crypto-js 在客户端对消息进行加密。

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

我需要知道 CryptoJS 在加密消息时使用的 AES 参数(不确定它们是什么,但听起来像:密钥大小(256)、填充(pkcs5)、模式(CBC)、PBE 算法(PBKDF2)、盐(随机), 迭代次数 (100) ) 。如果有人能证实它,那将是一个很大的帮助......过去几天我一直在努力解开这个谜团?。

我需要知道 CryptoJS 在 AES 加密消息时执行的不同步骤

3个回答

CryptoJS使用非标准化的 OpenSSL KDF 进行密钥派生 ( EvpKDF ),以MD5作为散列算法和1次迭代。IV 也来自密码,这意味着在 Java 端解密它只需要实际的密文、密码和盐。

换句话说,PBKDF2 不用于 CryptoJS 的密码模式下的密钥派生。默认情况下,AES-256 在 CBC 模式下使用 PKCS5 填充(与 PKCS7 填充相同)。请记住,您可能需要JCE Unlimited Strength Jurisdiction Policy Files另请参阅为什么对超过特定长度的密钥使用加密有限制?

下面的代码重新创建Java中的KDF(keySizeivSize分别为4 AES-256为8和来自)。

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
}

这是完整的类以供参考:

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

以及用于在 Java 代码中生成值的 JavaScript 代码:

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
类似的代码可以在我对PHPPython 的回答中找到
2021-04-19 18:49:47

遵循@Artjom B 对这个问题和这里为 python 用户的出色回答,我加入了完整的 Java 代码,帮助我解密以这种方式加密的字符串

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

当您只知道密码时,这段 Java 代码很有用(即 salt 没有与加密字符串一起发送):

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
}

这里查看文档

  • 密钥大小:“如果您使用密码,那么它将生成一个 256 位的密钥。”
  • 填充:Pkcs7(默认)
  • 模式:CBC(默认)
  • iv:生成并存储在密文对象中:与“encrypted.iv”一起使用

用于生成密钥的东西:

  • salt:生成并存储在密文对象中:与“encrypted.salt”一起使用(虽然它并没有真正这么说,所以我在这里猜测)
  • pbe 算法:不清楚。它没有记录。
  • 迭代计数:我在任何地方都找不到这个文档。代码中的示例似乎使用了 1000。

您可以手动设置参数,这可能比依赖默认值更安全,例如一些基于文档中示例的伪代码:

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

您可能需要进行实验。我会一步一步来。通过摆弄这些参数来获取基于密码的密钥进行匹配,然后获取密文进行匹配,然后找出解密方法。避免简化事情的冲动,例如跳过 IV 或使用 ECB。

我已经编辑了我的 javascript 和 java 代码的详细信息的问题..请参阅问题
2021-04-17 18:49:47
在密码模式下加密期间实际上并未使用 PBKDF2
2021-04-20 18:49:47
user1455719 为什么不尝试发布您尝试在 Java 和 Javascript 方面使用的特定代码,然后也许有人可以提供帮助。我强烈建议显式生成密钥并逐步调试,直到双方匹配为止。
2021-04-26 18:49:47
奇怪的。我在猜测,因为这是其他地方列出的唯一一个。你知道它是如何在密码模式下生成密钥的吗?如果是这样的话,更有理由手工完成。
2021-05-03 18:49:47
Artjom B,如果你能写一个答案,那将是一个很大的帮助。我有点卡住和困惑。
2021-05-05 18:49:47