【问题标题】:CryptoJs AES encryption and Java DecryptCryptoJs AES 加密和 Java 解密
【发布时间】:2020-07-08 14:27:21
【问题描述】:

CryptoJS AES encryption and Java AES decryption 我遇到了上述解决方案。我之前尝试将散列从 md5 更改为 SHA-256,但它不起作用。 给我以下错误:

javax.crypto.BadPaddingException:给定的最终块未正确填充。如果在解密期间使用了错误的密钥,则可能会出现此类问题。

辅助函数有什么需要修改的地方

【问题讨论】:

  • 由于您向我们提供的信息非常稀少,我们只能开启“猜测模式”。我相信这是 MD5 与 SHA-256 相比的输出长度。 MD5-hash 是 SHA-256 长度的一半,因此这可能会导致错误,但如果没有看到您在 CryptoJS + Java 端用于加密和解密的源代码,这都是猜测。
  • CryptoJS.AES.encrypt 使用 implicit(和硬编码)MD5(作为 OpenSSL 函数 EVP_BytesToKey 的摘要)。如果在 Java 代码中使用 SHA256 而不是 MD5,则会导致加密和解密的密钥不同。
  • 我的解决方案与上面的链接完全相同。请参考我描述中的链接

标签: java aes cryptojs


【解决方案1】:

我使用CryptoJS AES encryption and Java AES decryption 中的答案作为包含加密并将 MessageDigest 从 MD5 更改为 SHA-256 的程序的基础。

运行这个程序,它成功地解密了加密数据(作为密文中的 Base64 字符串):

Changes: added encryption and using SHA-256 as hash algorithm
cipherText: AAAAAAAAAAAUUMNwaxNxbgvkWpqE+kfq0f3K/cQ6wwwiwFFsIBa8PXsoi0Z7dRhtNVurV1MbysOzNh9R9YMltSM/6en8e9JmEingdD3Rgp8=
The quick brown fox jumps over the lazy dog. ? ?

“独立”运行此程序将按预期工作,但不会按要求工作“在 CryptoJS 中加密并在 Java 中解密”。这是因为 MD5 散列是“内置”(或“硬编码”)在 CryptoJS 中的,正如几个小时前 @Topaco 已经评论过的那样(所有功劳归功于他)。因此,最终没有其他方法可以在 Java 端使用 MD5,因为 CryptoJS 将其用作 MessageDigest。也许还有另一个可用于 JavaScript 的加密库。

这是我的代码:

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Arrays;
import java.util.Base64;

public class MainOrgSha256WithEncryption {
    public static void main(String[] args) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        System.out.println("https://stackoverflow.com/questions/41432896/cryptojs-aes-encryption-and-java-aes-decryption");
        System.out.println("Changes: added encryption and using SHA-256 as hash algorithm");

        String secret = "René Über";
        String plainText = "The quick brown fox jumps over the lazy dog. \uD83D\uDC7B \uD83D\uDC7B";
        // generate random salt
        SecureRandom secureRandom = new SecureRandom();
        byte[] saltDataEncryption = new byte[8];
        secureRandom.nextBytes(saltDataEncryption);
        // changed MessageDigest from MD5 to SHA-256
        MessageDigest md5 = MessageDigest.getInstance("SHA-256");
        //MessageDigest md5 = MessageDigest.getInstance("MD5");
        final byte[][] keyAndIVEncryption = GenerateKeyAndIV(32, 16, 1, saltDataEncryption, secret.getBytes(StandardCharsets.UTF_8), md5);
        SecretKeySpec keyEncryption = new SecretKeySpec(keyAndIVEncryption[0], "AES");
        IvParameterSpec ivEncryption = new IvParameterSpec(keyAndIVEncryption[1]);
        // encryption
        Cipher aesCBCEncryption = Cipher.getInstance("AES/CBC/PKCS5Padding");
        aesCBCEncryption.init(Cipher.ENCRYPT_MODE, keyEncryption, ivEncryption);
        byte[] cipherTextEncryption = aesCBCEncryption.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
        // concate 8 zero bytes + salt + cipherTextEncryption
        int arrayLength = 8 + saltDataEncryption.length + cipherTextEncryption.length;
        byte[] cipherTextEncryptionComplete = new byte[arrayLength];
        System.arraycopy(saltDataEncryption, 0, cipherTextEncryptionComplete, 8, saltDataEncryption.length);
        System.arraycopy(cipherTextEncryption, 0, cipherTextEncryptionComplete, 16, cipherTextEncryption.length);
        String cipherTextBase64 = Base64.getEncoder().encodeToString(cipherTextEncryptionComplete);
        // now we are using the new cipherTextBase64 as input for the decryption
        String cipherText = cipherTextBase64;
        System.out.println("cipherText: " + cipherText);

        // decryption
        byte[] cipherData = Base64.getDecoder().decode(cipherText);
        byte[] saltData = Arrays.copyOfRange(cipherData, 8, 16);

        final byte[][] keyAndIV = GenerateKeyAndIV(32, 16, 1, saltData, secret.getBytes(StandardCharsets.UTF_8), md5);
        SecretKeySpec key = new SecretKeySpec(keyAndIV[0], "AES");
        IvParameterSpec iv = new IvParameterSpec(keyAndIV[1]);

        byte[] encrypted = Arrays.copyOfRange(cipherData, 16, cipherData.length);
        Cipher aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding");
        aesCBC.init(Cipher.DECRYPT_MODE, key, iv);
        byte[] decryptedData = aesCBC.doFinal(encrypted);
        String decryptedText = new String(decryptedData, StandardCharsets.UTF_8);

        System.out.println(decryptedText);
    }
    /**
     * Generates a key and an initialization vector (IV) with the given salt and password.
     * <p>
     * This method is equivalent to OpenSSL's EVP_BytesToKey function
     * (see https://github.com/openssl/openssl/blob/master/crypto/evp/evp_key.c).
     * By default, OpenSSL uses a single iteration, MD5 as the algorithm and UTF-8 encoded password data.
     * </p>
     * @param keyLength the length of the generated key (in bytes)
     * @param ivLength the length of the generated IV (in bytes)
     * @param iterations the number of digestion rounds
     * @param salt the salt data (8 bytes of data or <code>null</code>)
     * @param password the password data (optional)
     * @param md the message digest algorithm to use
     * @return an two-element array with the generated key and IV
     */
    public static byte[][] GenerateKeyAndIV(int keyLength, int ivLength, int iterations, byte[] salt, byte[] password, MessageDigest md) {

        int digestLength = md.getDigestLength();
        int requiredLength = (keyLength + ivLength + digestLength - 1) / digestLength * digestLength;
        byte[] generatedData = new byte[requiredLength];
        int generatedLength = 0;

        try {
            md.reset();

            // Repeat process until sufficient data has been generated
            while (generatedLength < keyLength + ivLength) {

                // Digest data (last digest if available, password data, salt if available)
                if (generatedLength > 0)
                    md.update(generatedData, generatedLength - digestLength, digestLength);
                md.update(password);
                if (salt != null)
                    md.update(salt, 0, 8);
                md.digest(generatedData, generatedLength, digestLength);

                // additional rounds
                for (int i = 1; i < iterations; i++) {
                    md.update(generatedData, generatedLength, digestLength);
                    md.digest(generatedData, generatedLength, digestLength);
                }

                generatedLength += digestLength;
            }

            // Copy key and IV into separate byte arrays
            byte[][] result = new byte[2][];
            result[0] = Arrays.copyOfRange(generatedData, 0, keyLength);
            if (ivLength > 0)
                result[1] = Arrays.copyOfRange(generatedData, keyLength, keyLength + ivLength);

            return result;

        } catch (DigestException e) {
            throw new RuntimeException(e);

        } finally {
            // Clean out temporary data
            Arrays.fill(generatedData, (byte)0);
        }
    }
}

【讨论】:

  • FWIW cryptojs.AES 仅强制旧的 OpenSSL 样式的密钥派生,即 EVP_BytesToKey(md5,salt8,n=1) 包括 IV,以及 Salted__ 的数据格式,如果你给它一个密码一个js字符串。您可以改为通过在 js Buffer(s) 中给它一个密钥和 IV(如果适用)来进行标准加密,并且该密钥可以使用更好的 PBKDF(如 PBKDF2、bcrypt、argon2)或非基于密码的方案(如DH 或 EG)您选择并实施。
猜你喜欢
  • 1970-01-01
  • 2013-07-20
  • 2019-08-13
  • 2014-08-17
  • 1970-01-01
  • 2016-02-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多