简体   繁体   English

在使用密码加密消息时,crypto-js内部使用的AES参数和步骤是什么?

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

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. 我应该使用密码作为Java服务器端的密钥加密一些文本数据。 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. 加密数据传递到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) ) . 我需要知道CryptoJS在加密消息时使用的AES参数( 不确定它们是什么,但听起来像:密钥大小(256),填充(pkcs5),模式(CBC),PBE算法(PBKDF2),盐(随机),迭代计数(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?. 如果有人能证实这一点,那将是一个很大的帮助......过去几天我一直试图解开这个谜团?

I need to know the different steps performed by CryptoJS while AES encrypting a message 我需要知道在加密消息时CryptoJS执行的不同步骤

CryptoJS uses the non-standardized OpenSSL KDF for key derivation ( EvpKDF ) with MD5 as the hashing algorithm and 1 iteration. CryptoJS 使用非标准化的OpenSSL KDF进行密钥推导( EvpKDF ), MD5作为哈希算法和1次迭代。 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. IV也来自密码,这意味着在Java端只需要实际的密文,密码和盐来解密它。

In other words, PBKDF2 is not used for key derivation in password mode of CryptoJS. 换句话说,PBKDF2不用于CryptoJS的密码模式中的密钥派生。 By default AES-256 is used in CBC mode with PKCS5 padding (which is the same as PKCS7 padding ). 默认情况下,AES-256在CBC模式下使用PKCS5填充( 与PKCS7填充相同 )。 Keep in mind that you might need the JCE Unlimited Strength Jurisdiction Policy Files . 请记住,您可能需要JCE Unlimited Strength Jurisdiction Policy Files See also Why there are limitations on using encryption with keys beyond certain length? 另请参阅为什么使用超过一定长度的密钥加密有限制?

The following code recreates the KDF in Java ( keySize and ivSize are 8 respectively 4 for AES-256 and come from ). 以下代码在Java中重新创建KDF(对于AES-256, keySizeivSize分别为8和4)。

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

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 (ie salt was not sent with the encrypted string): 当您只知道密码(即盐未与加密字符串一起发送)时,这段Java代码非常有用:

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
}

I'm looking at the documentation here: 我正在看这里的文档

  • key size: "If you use a passphrase, then it will generate a 256-bit key." 密钥大小:“如果使用密码短语,那么它将生成一个256位密钥。”
  • padding: Pkcs7 (the default) padding:Pkcs7(默认值)
  • mode: CBC (the default) 模式:CBC(默认)
  • iv: generated and stored in the cipher text object: use with "encrypted.iv" 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) salt:生成并存储在密文对象中:与“encrypted.salt”一起使用(尽管它并没有真正说明,所以我在这里猜测)
  • pbe algorithm: Unclear. pbe算法:不清楚。 It's not documented. 它没有记录。
  • iteration count: I can't find this documented anywhere. 迭代计数:我无法在任何地方找到这个记录。 The examples in the code seem to use 1000. 代码中的示例似乎使用1000。

You can set the parameters by hand, which is maybe safer than relying on the defaults, eg 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之类的事情的冲动。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM