Java 使用CryptoJS解密AES/CBC/PKCS5P

Java 使用CryptoJS解密AES/CBC/PKCS5P,java,cryptography,aes,cryptojs,javax.crypto,Java,Cryptography,Aes,Cryptojs,Javax.crypto,我使用Javajavax.cryptoAPI生成128位AES/CBC/PKCS5Padding密钥。以下是我使用的算法: public static String encryptAES(String data, String secretKey) { try { byte[] secretKeys = Hashing.sha1().hashString(secretKey, Charsets.UTF_8) .toString().subs

我使用Java
javax.crypto
API生成128位
AES/CBC/PKCS5Padding
密钥。以下是我使用的算法:

public static String encryptAES(String data, String secretKey) {
    try {
        byte[] secretKeys = Hashing.sha1().hashString(secretKey, Charsets.UTF_8)
                .toString().substring(0, 16)
                .getBytes(Charsets.UTF_8);

        final SecretKey secret = new SecretKeySpec(secretKeys, "AES");

        final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, secret);

        final AlgorithmParameters params = cipher.getParameters();

        final byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
        final byte[] cipherText = cipher.doFinal(data.getBytes(Charsets.UTF_8));

        return DatatypeConverter.printHexBinary(iv) + DatatypeConverter.printHexBinary(cipherText);
    } catch (Exception e) {
        throw Throwables.propagate(e);
    }
}


public static String decryptAES(String data, String secretKey) {
    try {
        byte[] secretKeys = Hashing.sha1().hashString(secretKey, Charsets.UTF_8)
                .toString().substring(0, 16)
                .getBytes(Charsets.UTF_8);

        // grab first 16 bytes - that's the IV
        String hexedIv = data.substring(0, 32);

        // grab everything else - that's the cipher-text (encrypted message)
        String hexedCipherText = data.substring(32);

        byte[] iv = DatatypeConverter.parseHexBinary(hexedIv);
        byte[] cipherText = DatatypeConverter.parseHexBinary(hexedCipherText);

        final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

        cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(secretKeys, "AES"), new IvParameterSpec(iv));

        return new String(cipher.doFinal(cipherText), Charsets.UTF_8);
    } catch (BadPaddingException e) {
        throw new IllegalArgumentException("Secret key is invalid");
    }catch (Exception e) {
        throw Throwables.propagate(e);
    }
}
使用这些方法,我可以使用secretKey轻松地加密和解密消息。由于Java默认使用128位AES加密,因此它使用SHA1生成原始密钥的散列,并将散列的前16个字节用作AES中的密钥。然后它以十六进制格式转储IV和密文

例如,
EncryptAE(“测试”、“测试”)
生成
CB5E759CE5FEAFEFCC9BABBFD84DC80C0291ED4917CF1402FF03B8E12716E44C
,我想用CryptoJS解密这个密钥

以下是我的尝试:

var str = 'CB5E759CE5FEAFEFCC9BABBFD84DC80C0291ED4917CF1402FF03B8E12716E44C';

CryptJS.AES.decrypt( 
CryptJS.enc.Hex.parse(str.substring(32)),
CryptJS.SHA1("test").toString().substring(0,16),  
{
  iv: CryptJS.enc.Hex.parse(str.substring(0,32)),
  mode: CryptJS.mode.CBC,
  formatter: CryptJS.enc.Hex, 
  blockSize: 16,  
  padding: CryptJS.pad.Pkcs7 
}).toString()

但是,它返回一个空字符串

问题是您将64位密钥用作128位密钥
Hashing.sha1().hashString(secretKey,Charsets.UTF_8)
是的一个实例,其
toString
方法描述如下:

按顺序返回一个字符串,该字符串包含asBytes()的每个字节,以两位无符号十六进制数的小写形式表示

它是一个十六进制编码的字符串。如果只取该字符串中的16个字符并将其用作密钥,则熵只有64位,而不是128位。您确实应该直接使用
HashCode#asBytes()


无论如何,CryptoJS代码的问题有很多方面:

  • 密文必须是
    CipherParams
    对象,但如果它在
    ciphertext
    属性中包含作为字数组的密文字节,就足够了
  • 密钥必须作为字数组而不是字符串传入。否则,将使用与OpenSSL兼容的(EVP_BytesToKey)密钥派生函数从字符串(假定为密码)派生密钥和IV
  • 附加选项要么是不必要的,因为它们是默认值;要么是错误的,因为块大小是以字而不是字节计算的
以下是与损坏的Java代码兼容的CryptoJS代码:

var str='CB5E759CE5FEAFEFCC9BABBFD84DC80C0291ED4917CF1402FF03B8E12716E44C';
log(“结果:+CryptoJS.AES.decrypt({
密文:CryptoJS.enc.Hex.parse(str.substring(32))
},CryptoJS.enc.Utf8.parse(CryptoJS.SHA1(“test”).toString().substring(0,16)),
{
iv:CryptoJS.enc.Hex.parse(str.substring(0,32)),
}).toString(CryptoJS.enc.Utf8))

问题是您将64位密钥用作128位密钥
Hashing.sha1().hashString(secretKey,Charsets.UTF_8)
是的一个实例,其
toString
方法描述如下:

按顺序返回一个字符串,该字符串包含asBytes()的每个字节,以两位无符号十六进制数的小写形式表示

它是一个十六进制编码的字符串。如果只取该字符串中的16个字符并将其用作密钥,则熵只有64位,而不是128位。您确实应该直接使用
HashCode#asBytes()


无论如何,CryptoJS代码的问题有很多方面:

  • 密文必须是
    CipherParams
    对象,但如果它在
    ciphertext
    属性中包含作为字数组的密文字节,就足够了
  • 密钥必须作为字数组而不是字符串传入。否则,将使用与OpenSSL兼容的(EVP_BytesToKey)密钥派生函数从字符串(假定为密码)派生密钥和IV
  • 附加选项要么是不必要的,因为它们是默认值;要么是错误的,因为块大小是以字而不是字节计算的
以下是与损坏的Java代码兼容的CryptoJS代码:

var str='CB5E759CE5FEAFEFCC9BABBFD84DC80C0291ED4917CF1402FF03B8E12716E44C';
log(“结果:+CryptoJS.AES.decrypt({
密文:CryptoJS.enc.Hex.parse(str.substring(32))
},CryptoJS.enc.Utf8.parse(CryptoJS.SHA1(“test”).toString().substring(0,16)),
{
iv:CryptoJS.enc.Hex.parse(str.substring(0,32)),
}).toString(CryptoJS.enc.Utf8))

这个对我来说非常合适

import * as CryptoJS from 'crypto-js';    
const SECRET_CREDIT_CARD_KEY = '1231231231231231' // 16 digits key
    
    decrypt(cipherText) {
        const iv = CryptoJS.enc.Hex.parse(this.SECRET_CREDIT_CARD_KEY);
        const key = CryptoJS.enc.Utf8.parse(this.SECRET_CREDIT_CARD_KEY);
        const result = CryptoJS.AES.decrypt(cipherText, key,
          {
            iv,
            mode: CryptoJS.mode.ECB,
          }
          )
          const final  = result.toString(CryptoJS.enc.Utf8)
          return final
      }
    
    console.log(decrypt('your encrypted text'))
在Angular 8中使用此库

这个对我来说非常合适

import * as CryptoJS from 'crypto-js';    
const SECRET_CREDIT_CARD_KEY = '1231231231231231' // 16 digits key
    
    decrypt(cipherText) {
        const iv = CryptoJS.enc.Hex.parse(this.SECRET_CREDIT_CARD_KEY);
        const key = CryptoJS.enc.Utf8.parse(this.SECRET_CREDIT_CARD_KEY);
        const result = CryptoJS.AES.decrypt(cipherText, key,
          {
            iv,
            mode: CryptoJS.mode.ECB,
          }
          )
          const final  = result.toString(CryptoJS.enc.Utf8)
          return final
      }
    
    console.log(decrypt('your encrypted text'))
在Angular 8中使用此库

Java代码是
PKCS5Padding
,而JS代码是
pad.Pkcs7
。5和7不一样。@Andreas是的,是一样的:@ArtjomB。找到“PKCS#5 padding不能用于AES”的答案链接,证明5和7可用于AES总是很有趣的。@Andreas这是Java命名约定的遗物。它只是停留在DES时代。如果您进一步查看,您会发现
RSA/ECB/PKCS1Padding
也没有意义,因为ECB不适用于RSA。Java代码是
PKCS5Padding
,而JS代码是
pad.Pkcs7
。5和7不一样。@Andreas是的,是一样的:@ArtjomB。找到“PKCS#5 padding不能用于AES”的答案链接,证明5和7可用于AES总是很有趣的。@Andreas这是Java命名约定的遗物。它只是停留在DES时代。如果您进一步查看,您会发现
RSA/ECB/PKCS1Padding
也没有意义,因为ECB不适用于RSA。我对代码进行了如下修改:
Arrays.copyOfRange(Hashing.sha1().hashString(secretKey,Charsets.UTF_8).asBytes(),0,16)
,正如您所说,它现在生成不同的散列,但在CryptoJS中我仍然得到一个空字符串。这里是encryptAES(“测试”、“测试”)的一个示例输出F6A5230232062D2F0BDC2080021E997C6D07A733000487544C9DDE7708975525谢谢,现在它可以正常工作了!之所以使用HashCode.toString()是因为SHA1的字符串表示在Java和Javascript中都是相同的。为什么你认为Java代码现在是坏的?Java代码是坏的,因为它只使用64位键(16个十六进制)。如今,即使是对个人来说,这样一把钥匙也属于暴力强迫的范畴。你有什么建议