简体   繁体   English

AES-128加密不适用于Java <1.7

[英]AES-128 Encryption not working on Java < 1.7

I've been chipping away at a school assignment for 3 days, and finally finished it today, error-free and working fine! 我已经在学校任务中​​捣乱了3天,终于在今天完成了它,没有错误且工作正常! Except, I was testing it on Java 1.7, and the school servers (where the professor will compile it) run 1.6. 除了,我在Java 1.7上测试它,学校服务器(教授将编译它)运行1.6。 So, I tested my code on 1.6, wanting to cover all my bases, and I get a BadPaddingException upon decryption. 所以,我在1.6上测试了我的代码,希望覆盖我的所有基础,并在解密时得到BadPaddingException

[EDIT] Warning: this code does not follow common security practices and should not be used in production code. [编辑] 警告:此代码不遵循常见的安全实践,不应在生产代码中使用。

Originally, I had this, which works fine on 1.7 (sorry, lots of code.. all relevant..): 最初,我有这个,在1.7上工作正常(对不起,很多代码..所有相关..):

public static String aes128(String key, String data, final int direction) {
    SecureRandom rand = new SecureRandom(key.getBytes());
    byte[] randBytes = new byte[16];
    rand.nextBytes(randBytes);
    SecretKey encKey = new SecretKeySpec(randBytes, "AES");

    Cipher cipher = null;
    try {
        cipher = Cipher.getInstance("AES");
        cipher.init((direction == ENCRYPT ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE), encKey);
    } catch (InvalidKeyException e) {
        return null;
    } catch (NoSuchPaddingException e) {
        return null;
    } catch (NoSuchAlgorithmException e) {
        return null;
    }

    try {
        if (direction == ENCRYPT) {
            byte[] encVal = cipher.doFinal(data.getBytes());
            String encryptedValue = Base64.encode(encVal);
            return encryptedValue;
        } else {
            byte[] dataBytes = Base64.decode(data);
            byte[] encVal = cipher.doFinal(dataBytes);
            return new String(encVal);
        }
    } catch (NullPointerException e) {
        return null;
    } catch (BadPaddingException e) {
        return null;
    } catch (IllegalBlockSizeException e) {
        return null;
    }
}

However, my BadPaddingException catch block executes upon decryption: 但是,我的BadPaddingException catch块在解密时执行:

javax.crypto.BadPaddingException: Given final block not properly padded
        at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..)
        at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..)
        at com.sun.crypto.provider.AESCipher.engineDoFinal(DashoA13*..)
        at javax.crypto.Cipher.doFinal(DashoA13*..)
        at CipherUtils.aes128(CipherUtils.java:112)
        at CipherUtils.decryptFile(CipherUtils.java:44)
        at decryptFile.main(decryptFile.java:21)

This is what I tried to fix it (basically, I added all the padding/unpadding myself, and used NoPadding ): 这就是我试图解决的问题(基本上,我自己添加了所有填充/取消填充,并使用NoPadding ):

public static String aes128(String key, String data, final int direction) {
    // PADCHAR = (char)0x10 as String
    while (key.length() % 16 > 0)
        key = key + PADCHAR; // Added this loop

    SecureRandom rand = new SecureRandom(key.getBytes());
    byte[] randBytes = new byte[16];
    rand.nextBytes(randBytes);
    SecretKey encKey = new SecretKeySpec(randBytes, "AES");
    AlgorithmParameterSpec paramSpec = new IvParameterSpec(key.getBytes()); // Created this

    Cipher cipher = null;
    try {
        cipher = Cipher.getInstance("AES/CBC/NoPadding"); // Added CBC/NoPadding
        cipher.init((direction == ENCRYPT ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE), encKey, paramSpec); // Added paramSpec
    } catch (InvalidKeyException e) {
        return null;
    } catch (NoSuchPaddingException e) {
        return null;
    } catch (NoSuchAlgorithmException e) {
        return null;
    } catch (InvalidAlgorithmParameterException e) {
        return null; // Added this catch{}
    }

    try {
        if (direction == ENCRYPT) {
            while (data.length() % 16 > 0)
                data = data + PADCHAR; // Added this loop

            byte[] encVal = cipher.doFinal(data.getBytes());
            String encryptedValue = Base64.encode(encVal);
            return encryptedValue;
        } else {
            byte[] dataBytes = Base64.decode(data);
            byte[] encVal = cipher.doFinal(dataBytes);
            return new String(encVal);
        }
    } catch (NullPointerException e) {
        return null;
    } catch (BadPaddingException e) {
        return null;
    } catch (IllegalBlockSizeException e) {
        return null;
    }
}

When using this, I just get gibberish in and out: 使用它时,我只是乱搞乱哄哄:

Out: u¢;èÉ÷JRLòB±J°N°[9cRÐ{ªv=]I¯¿©:
´RLA©êí;R([¶Ü9¸ßv&%®µ^#û|Bá (80)
Unpadded: u¢;èÉ÷JRLòB±J°N°[9cRÐ{ªv=]I¯¿©:
´RLA©êí;R([¶Ü9¸ßv&%®µ^#û|Bá (79)

It is also worth noting that 1.6 and 1.7 produce different encrypted strings. 值得注意的是1.6和1.7产生不同的加密字符串。

For example, on 1.7, encrypting xy (including a SHA-1 hash) with key hi produces: 例如,在1.7上,使用key hi加密xy (包括SHA-1哈希)会产生:

XLUVZBIJv1n/FV2MzaBK3FLPQRCQF2FY+ghyajdqCGsggAN4aac8bfwscrLaQT7BMHJgfnjJLn+/rwGv0UEW+dbRIMQkNAwkGeSjda3aEpk=

On 1.6, the same thing produces: 在1.6,同样的事情产生:

nqeahRnA0IuRn7HXUD1JnkhWB5uq/Ng+srUBYE3ycGHDC1QB6Xo7cPU6aEJxH7NKqe3kRN3rT/Ctl/OrhqVkyDDThbkY8LLP39ocC3oP/JE=

I didn't expect the assignment to take so long, so my time has run out and it does need to be done tonight. 我没想到任务需要这么长时间,所以我的时间已经用完了,今晚确实需要完成。 If there is no answer by then, however, I'll just leave a note to my teacher regarding this. 如果那时候没有答案,我只会给老师留言。 It appears to be some issue that was fixed in 1.7... though hopefully can be remedied through the correct addition/fix in my code. 它似乎是在1.7中修复的一些问题...虽然希望可以通过我的代码中的正确添加/修复来解决。

Thanks a ton for everyone's time! 非常感谢每个人的时间!

First off: 首先:

For almost all systems, encrypting the same plaintext twice should always (ie with very very high probability) produce different ciphertext. 对于几乎所有系统,两次加密相同的明文应该总是 (即非常非常高的概率)产生不同的密文。

The traditional example is that it allows a CPA adversary to distinguish E("attack at dawn") from E("attack at dusk") with only two queries. 传统的例子是,它允许CPA对手通过两个查询区分E(“黎明时的攻击”)和E(“黄昏时的攻击”)。 (There are a handful of systems where you want deterministic encryption, but the right way to do this is "synthetic IV" or cipher modes like CMC and EME.) (有一些系统需要确定性加密,但正确的方法是“合成IV”或密码模式,如CMC和EME。)

Ultimately, the problem is that SecureRandom() is not intended for key derivation. 最终,问题是SecureRandom()不是用于密钥派生的。

  • If the input "key" is a passphrase, you should be using something like PBKDF2 (or scrypt() or bcrypt() ). 如果输入“key”是密码,那么你应该使用类似PBKDF2(或scrypt()bcrypt() )的东西。
    • Additionally, you should be using an explicit charset, eg String.getBytes("UTF-8") . 此外,您应该使用显式字符集,例如String.getBytes("UTF-8")
  • If the input "key" is a key, the most common string representation is a hexdump. 如果输入“key”是键,则最常见的字符串表示形式是hexdump。 Java doesn't include an unhexing function, but there are several here . Java不包括unhexing功能,但有几个在这里
    • If the input is a "master key" and you want to derive a subkey, then you should be hashing it with other data . 如果输入是“主密钥”并且您想要派生子密钥,那么您应该使用其他数据对其进行哈希处理 There's not much point if the subkey is always the same. 如果子键始终相同,则没有多大意义。

Additional nitpicks: 额外的挑剔:

  • Your code is vulnerable to a padding oracle attack; 您的代码容易受到填充oracle攻击; you really should be verifying a MAC before doing anything with the data (or better, using an authenticated encryption mode). 你真的应该在对数据做任何事情之前验证MAC(或者更好,使用经过验证的加密模式)。
  • In your second listing, you explicitly reuse the IV. 在第二个列表中,您明确重用了IV。 Bad! 坏! Assuming CBC mode, the IV used should be unpredictable; 假设使用CBC模式,使用的IV应该是不可预测的; SecureRandom is useful here. SecureRandom在这里很有用。

I've been looking over and over and I have to agree with NullUserException. 我一直在看,我不得不同意NullUserException。 The problem is the use of SecureRandom . 问题是使用SecureRandom This means that you never really know what your key is and therefore it is not necessarily ever the same key. 这意味着你永远不会真正知道你的密钥是什么,因此它不一定是相同的密钥。

encKey comes from SecureRandom, which is seeded by the key provided. encKey来自SecureRandom,它由提供的密钥播种。 Therefore, if the key is the same, the seed is the same, so the random should be the same... 因此,如果键是相同的,种子是相同的,所以随机应该是相同的...

...unless of course Oracle (or another provider) changes the implementation between versions. ...当然,除非Oracle(或其他提供商)更改版本之间的实现。

Okay, adding more information that I researched. 好的,添加我研究的更多信息。 I think this answer was most helpful . 我认为这个答案最有帮助

Get password and cleartext from the user, and convert them to byte arrays. 从用户获取密码和明文,并将它们转换为字节数组。
Generate a secure random salt. 生成安全的随机盐。
Append the salt to the password and compute its cryptographic hash. 将salt附加到密码并计算其加密哈希值。 Repeat this many times. 重复多次。
Encrypt the cleartext using the resulting hash as the initialization vector and/or secret key. 使用生成的哈希作为初始化向量和/或密钥加密明文。
Save the salt and the resulting ciphertext. 保存盐和生成的密文。

To me, it sounds like SecureRandom is used once to generate a salt but then salt must be saved with the cypher text in order to undo the cyphering process. 对我来说,听起来像SecureRandom只使用一次来生成salt但是必须使用密码文本保存salt以撤消加密过程。 Additional security comes from repetition and variance of steps (obscurity). 额外的安全性来自步骤的重复和变化(默默无闻)。

Note: I couldn't find any consensus that these steps are best practices. 注意:我无法找到任何共识,即这些步骤是最佳做法。

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

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