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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/27220297/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-28 07:10:11  来源:igfitidea点击:

What are the AES parameters used and steps performed internally by crypto-js while encrypting a message with a password?

javascriptsecurityencryptioncryptojs

提问by user1455719

Background:The application that I am working on is supposed to work offline. I should encrypt some text data using a password as a key at the java server side. The encrypted data is passed to the HTML5 page and at the client side using crypto-js library the server encrypted data should be decrypted.

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

My issue:In order to encrypt my message in such a way that the client can decrypt it with crypt-js (using a user entered password), I need to know the exact steps that crypto-js expects while encrypting a message.

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

What I need to know:I have the following encryption code which does the encryption of a message at the client side using crypto-js.

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

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

I need to know the AES parameters used by CryptoJS while encrypting a message(Not sure what they are, but it sounds like: key size (256), padding (pkcs5), mode (CBC), PBE algorithm (PBKDF2), salt (random), iteration count (100)) . It would be a great help if some one could confirm it...I been trying to solve this mystery for the last few days?.

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

I need to know the different steps performed by CryptoJS while AES encrypting a message

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

回答by Artjom B.

CryptoJS usesthe non-standardized OpenSSL KDF for key derivation (EvpKDF) with MD5as the hashing algorithm and 1iteration. The IV is also derived from the password which means that only the actual ciphertext, the password and the salt are needed to decrypt this on Java side.

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

In other words, PBKDF2 is not used for key derivation in password mode of CryptoJS. By default AES-256 is used in CBC mode with PKCS5 padding (which is the same as PKCS7 padding). Keep in mind that you might need the JCE Unlimited Strength Jurisdiction Policy Files. See also Why there are limitations on using encryption with keys beyond certain length?

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

The following code recreates the KDF in Java (keySizeand ivSizeare 8 respectively 4 for AES-256 and come from ).

下面的代码重新创建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
}

Here is the complete class for reference:

这是完整的类以供参考:

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

and the JavaScript code which was used for the generation of the values in the Java code:

以及用于在 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

回答by Mohammed El Moumni

Following @Artjom B's great answer both on this question and here for python users, I am joining the full java code that helped me decrypt a string that was encrypted this way

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

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

This piece of Java code is useful when you only know the password (i.e. salt was not sent with the encrypted string):

当您只知道密码时,这段 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
}

回答by Isaac Potoczny-Jones

I'm looking at the documentationhere:

这里查看文档

  • key size: "If you use a passphrase, then it will generate a 256-bit key."
  • padding: Pkcs7 (the default)
  • mode: CBC (the default)
  • iv: generated and stored in the cipher text object: use with "encrypted.iv"
  • 密钥大小:“如果您使用密码,那么它将生成一个 256 位的密钥。”
  • 填充:Pkcs7(默认)
  • 模式:CBC(默认)
  • iv:生成并存储在密文对象中:与“encrypted.iv”一起使用

Stuff for generating the key:

用于生成密钥的东西:

  • salt: generated and stored in the cipher text object: use with "encrypted.salt" (although it doesn't really say that, so I'm guessing here)
  • pbe algorithm: Unclear. It's not documented.
  • iteration count: I can't find this documented anywhere. The examples in the code seem to use 1000.
  • salt:生成并存储在密文对象中:与“encrypted.salt”一起使用(虽然它并没有真正这么说,所以我在这里猜测)
  • pbe 算法:不清楚。它没有记录。
  • 迭代计数:我在任何地方都找不到这个文档。代码中的示例似乎使用了 1000。

You can set the parameters by hand, which is maybe safer than relying on the defaults, e.g. some pseudo-code based on the examples in the documentation:

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

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

You will probably have to experiment. I'd take it one step at a time. Get the password-based keys to match by fiddling those parameters, then get the ciphertext to match, then figure out decryption. Avoid the urge to simplify things like skipping the IV or using ECB.

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