【问题标题】:CryptoJs's decrypt method returns an empty stringCryptoJs 的解密方法返回一个空字符串
【发布时间】:2015-03-08 17:02:30
【问题描述】:

我正在尝试使用 AES256 加密/解密,使用 Java 进行加密,使用 CryptoJS 进行解密。 加密经过Java测试工作正常,但JavaScript中的解密方法返回一个空字符串。请注意,为了测试 JavaScript,我在 tmp 文件中打印了 data、IV 和 salt 的值,然后在 JS 中进行了硬编码。 (注:文件格式为:data (byte[] base64) , Iv(string base64) and salt(string base64) )。

这是java中的代码:

public byte[] encrypt(String plainText) throws Exception {        
    //get salt
    salt = generateSalt();      
    byte[] saltBytes = salt.getBytes("UTF-8");

    // Derive the key
    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
    PBEKeySpec spec = new PBEKeySpec(
            password.toCharArray(), 
            saltBytes, 
            pswdIterations, 
            keySize
            );

    SecretKey secretKey = factory.generateSecret(spec);
    SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES");

    //encrypt the message
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(Cipher.ENCRYPT_MODE, secret);
    AlgorithmParameters params = cipher.getParameters();
    ivBytes = params.getParameterSpec(IvParameterSpec.class).getIV();
    byte[] encryptedTextBytes = cipher.doFinal(plainText.getBytes("UTF-8"));
    return Base64.encode(encryptedTextBytes);
}

下面的 JavaScript 中的解密代码有什么问题?

// the password that user provides
var userPass = document.getElementById("password").value; 
console.log("user pass : " + userPass);
// hash contains 5 bytes
var hashedPass = CryptoJS.SHA1(userPass);
console.log("hashed pass : " + hashedPass.toString(CryptoJS.enc.Base64) + " | array length " + hashedPass.words.length + " | " + typeof(hashedPass));
// use only 4 bytes (128 bits) from the hashed pass
// (same as used in java when encrypting)
/////////////////////////var hashed4bytes =  CryptoJS.lib.WordArray.create(hashedPass.words.slice(0,4));
//console.log( "hashed4bytes encoded 64 = " + hashed4bytes.toString(CryptoJS.enc.Base64));

// get the encrypted msg 
var encMsg64 = document.getElementById("themessage").innerHTML;
encMsg64 = encMsg64.toString( CryptoJS.enc.Base64);
//var encMsg = CryptoJS.enc.Base64.parse(encMsg64);
var salt =CryptoJS.enc.Base64.parse("EAWnOgxUDuvhWqrSUsugq1umMpI=");
var iv =CryptoJS.enc.Base64.parse("xWpmXNbmbFjmWBUajuWYXQ==");
//var salt = "EAWnOgxUDuvhWqrSUsugq1umMpI=";
//var iv = "xWpmXNbmbFjmWBUajuWYXQ==";
 console.log('salt  '+ salt );
 console.log('iv  '+ iv );


 var key = CryptoJS.PBKDF2(hashedPass, salt, { keySize: 256/32, iterations: 1000 });
console.log( 'key '+ key);

var decText = '';
var ok = true;
try {
    debugger;
    var decMsg = CryptoJS.AES.decrypt( encMsg64, key, {
        iv:iv,
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7
        } );
    console.log( "decryptedData = " + decMsg );

    // convert to UTF8 string
    decText = decMsg.toString( CryptoJS.enc.Utf8 );
    console.log( "decryptedText = " + decText );

    if (decText == '') {
        ok = false;
    }   
}
catch (e) {
    //console.log("Error when decrypting: " + e.message)
    ok = false;
}

在 mafe 之后更改的问题仍然存在 这是更改后的完整代码

JAVA

public class AES256EncryptionServiceBean implements EncryptionService {

    private static final Logger LOGGER = LoggerFactory
            .getLogger(AES256EncryptionServiceBean.class);
    private String salt = null; //get bytes out of UTF-8 for decryption
    private static final int PSWDITERATIONS = 1000;//65536;
    private static final int KEYSIZE = 256;
    private static final String AES_ALGO = "AES";
    private static final String SHA1_ALGO = "PBKDF2WithHmacSHA1";
    private static final String AES_CBC_PKCS5_TRANSFORM = "AES/CBC/PKCS5Padding";
    private byte[] Iv;

    /**
     * Encrypts the data with AES-256 algorithm Encrypted data will be encoded
     * with base64 algorithm and the returned. Initial vector is being used
     * during encryption along with CBC encryption mode.
     * 
     * output format: [algo indicator(1char)][Initialization vector()][salt()][encoded data(variable size)]
     */
    @Override
    public byte[] encrypt(String password, byte[] data) throws PibException {
        byte[] encodedData = null;
        try {
            byte[] encryptedData = encryptCBC256Bits(password, data);
            encodedData = Base64.encodeBase64(encryptedData);
            /*String finalStr=null;
            String algo256 = "2";
            String datastr = Base64.encodeBase64String(encryptedData);
            String ivstr = new String(Iv);
            finalStr = algo256 +ivstr+salt+datastr;

            encodedData = finalStr.getBytes();
             */
        } catch (Exception e) {
            throw ExceptionFactory.createPibException(
                    MessageCodes.PIB_ENCRYPTION_FAILED, e, LOGGER);
        }
        return encodedData;
    }

    /**
     * Encrypts the input data with AES CBC transformation using 256 bits (32
     * bytes) Key is generated based on the provided password and random salt.
     * Salt is the extra bits added to the password to ensure every key is
     * unique SHA1 hashing is also participate in key generation.
     * 
     * @throws PibException
     * 
     */
    private byte[] encryptCBC256Bits(String password, byte[] data)
            throws PibException {

        salt = generateSalt();
        byte[] saltBytes = salt.getBytes(StandardCharsets.UTF_8);
        byte[] encryptedTextBytes = null;

        // Derive the key

        try {
            SecretKeyFactory factory = SecretKeyFactory.getInstance(SHA1_ALGO);
            // Password based key specification
            PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), saltBytes,
                    PSWDITERATIONS, KEYSIZE);
            SecretKey secretKey = factory.generateSecret(spec);
            SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(),
                    AES_ALGO);

            // encrypt the data
            Cipher cipher = Cipher.getInstance(AES_CBC_PKCS5_TRANSFORM);
            // SecureRandom random = new SecureRandom();
            // byte[] ivTemp = new byte[16];
            // random.nextBytes(ivTemp);
            cipher.init(Cipher.ENCRYPT_MODE, secret);
            AlgorithmParameters params = cipher.getParameters();
            Iv = params.getParameterSpec(IvParameterSpec.class).getIV();
            encryptedTextBytes = cipher.doFinal(data);

        } catch (NoSuchAlgorithmException | InvalidKeySpecException
                | NoSuchPaddingException | InvalidKeyException
                | InvalidParameterSpecException | IllegalBlockSizeException
                | BadPaddingException e) {
            throw ExceptionFactory.createPibException(
                    MessageCodes.PIB_ENCRYPTION_FAILED, e, LOGGER);
        }

        return encryptedTextBytes;
    }

    private String generateSalt() {

        SecureRandom random = new SecureRandom();
        byte bytes[] = new byte[20];
        random.nextBytes(bytes);
        String s = new String(bytes);
        return s;

    }

    public String getSalt() {
        return salt;
    }

    public byte[] getIv() {
        return Iv;
    }

}

Javascript

function decryptMsg256() {

                // the password that user provides
                var userPass = document.getElementById("password").value; 
                console.log("user pass : " + userPass);


                // get the encrypted msg 
                var encMsg64 = document.getElementById("themessage").innerHTML;
                var encMsg = CryptoJS.enc.Base64.parse(encMsg64);
                var salt =CryptoJS.enc.Utf8.parse("?E€O5?…°®I^y??O:n");
                var iv =CryptoJS.enc.Utf8.parse("S;Ui?¨=ENzI—$");

                 console.log('salt  '+ salt );
                 console.log('iv  '+ iv );


                 var key = CryptoJS.PBKDF2("password", salt, { keySize: 256/32, iterations: 1000 });
                console.log( 'key '+ key);

                var decText = '';
                var ok = true;
                try {
                    debugger;
                    var decMsg = CryptoJS.AES.decrypt( encMsg, key, {
                        iv:iv,
                        mode: CryptoJS.mode.CBC,
                        padding: CryptoJS.pad.Pkcs7
                        } );
                    console.log( "decryptedData = " + decMsg );

                    // convert to UTF8 string
                    decText = decMsg.toString( CryptoJS.enc.Utf8 );
                    console.log( "decryptedText = " + decText );

                    if (decText == '') {
                        ok = false;
                    }   
                }
                catch (e) {
                    //console.log("Error when decrypting: " + e.message)
                    ok = false;
                }

我不明白出了什么问题,请帮忙 CipherText、Salt 和 Iv 检索如下:

public void testEncryption_WriteToFile() throws Exception {

        byte[] data = IOUtils.toByteArray(this.getClass().getClassLoader()
                .getResourceAsStream(SOME_FILE_NAME));

        byte[] encryptedData = this.encryptionService.encrypt(PASSWORD, data);
        byte[] initial_vector = ((AES256EncryptionServiceBean) encryptionService)
        .getIv();
        String salt = ((AES256EncryptionServiceBean) encryptionService)
                .getSalt();


        IOUtils.write(encryptedData, new FileOutputStream(
                "C:\\Temp\\data.encrypted"));
        /*IOUtils.write(new String(encryptedData), new FileOutputStream(
                "C:\\Temp\\data[byte32string].encrypted"));
        */
        IOUtils.write(Base64.encodeBase64String(salt.getBytes(StandardCharsets.UTF_8)), new FileOutputStream(
                "C:\\Temp\\salt.encrypted"));
        /*IOUtils.write(salt.getBytes(StandardCharsets.UTF_8), new FileOutputStream(
                "C:\\Temp\\salt.encrypted"));
        */
        IOUtils.write(Base64.encodeBase64String(initial_vector), new FileOutputStream(
                "C:\\Temp\\iv.encrypted"));
        /*IOUtils.write(initial_vector, new FileOutputStream(
                "C:\\Temp\\iv.encrypted"));*/


    }

【问题讨论】:

  • 非常感谢您的回复。我在 Js 中对密码进行哈希处理,因为我在 Java 中使用“PBKDF2WithHmacSHA1”生成密钥,这不正确吗?我使用 toString 而不是 Base64.parse 因为消息已经在 base64 中,因为它是从 java (byte[] base64) 返回的,这也不正确吗?
  • 不幸的是,更改后的问题仍然存在(返回一个空字符串):
  • 您引入了一个新问题,请参阅我的更新答案。您是否验证过您生成了相同的密钥?此外,它不是完整的代码。您还没有展示如何从 Java 代码中获取密文、盐和 IV。
  • 我从上面的 encrypt 方法中获取密文作为 byte[]Base64 并在 JS.Salt 和 Iv 中使用该密文,也从上面的类中检索并存储为类的私有成员。 Salt 是一个字符串并从中检索 byte[]。Iv 也从密码参数 getIv 方法中检索为 byte[]。
  • 可能是这样,但byte[] 无法复制。那么,你该怎么做呢?例如,var iv =CryptoJS.enc.Utf8.parse("S;Ui?¨=ENzI—$"); 不是有效的 IV,因为它不是 16 字节长。

标签: javascript encryption aes sha1 pbkdf2


【解决方案1】:

CryptoJS.PBKDF2 默认使用 SHA1。因此,只要提供相同的密码、盐、密钥大小和迭代次数,它就会产生相同的密钥。问题在于,在 JavaScript 中,您还使用 SHA1 对密码进行哈希处理。不要这样做,直接将密码传递给 PBKDF2,就像在 Java 中一样。

第二个问题是尝试解密时密文应该是 CryptoJS 的原生格式。由于您从 Java 获得 base 64 编码的密文,因此您必须对其进行解码。取消注释该行:

var encMsg = CryptoJS.enc.Base64.parse(encMsg64);

不要这样做encMsg64 = encMsg64.toString( CryptoJS.enc.Base64);,因为这会再次编码已经编码的密文。


对于更新后的代码,您不能简单地将密钥和盐打印为字符串并期望它在 JavaScript 中工作。这些是byte[] 是有原因的。它们包含不可打印的字符,当您尝试在 JavaScript 中对其进行解析时,这些字符将会丢失。您必须将要从 Java 传输到 JavaScript 的所有 byte[] 值编码为 Base64,然后在 JavaScript 中对其进行解码。

【讨论】:

  • 非常感谢,现在事情更清楚了。我还在 Js (1000) 中使用不同的迭代计数,而不是在 java 中使用的 65000+。
  • 还有一个问题,声明 var encMsg = CryptoJS.enc.Base64.parse(encMsg64);是不是一定要跟toString一起?
  • 不,它没有。 parse 在 CryptoJS 中生成本机二进制表示,它是一个整数数组(4 字节字),这是 CryptoJS.AES.decrypt 需要的表示。 toString 会用它生成一个字符串,如果你传递一个编码器,它还会用它另外编码。
  • 非常感谢!我的最后一个问题是,如果我对从 Java 返回的 Base64.encode(..) 或 Base64.encodeString(..) 的结果执行解析,结果会是一样的吗?
  • Base64 是一种编码,因此如果您确定所有位都保持不变,您可以确保解码相同的数据(以不同的表示形式,因为 Java 和 JavaScript 没有相同的数据类型)。
猜你喜欢
  • 1970-01-01
  • 2019-12-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-30
  • 2015-12-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多