3

我已经在学校作业上拼凑了 3 天,今天终于完成了,没有错误并且工作正常!除了,我在 Java 1.7 上测试它,而学校服务器(教授将在其中编译它)运行 1.6。所以,我在 1.6 上测试了我的代码,想要覆盖我所有的基础,我BadPaddingException在解密时得到了一个。

[编辑]警告:此代码不遵循常见的安全实践,不应在生产代码中使用。

最初,我有这个,它在 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;
    }
}

但是,我的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)

这就是我试图解决的问题(基本上,我自己添加了所有的填充/取消填充,并使用了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;
    }
}

使用它时,我只会进进出出胡言乱语:

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)

还值得注意的是,1.6 和 1.7 产生不同的加密字符串。

例如,在 1.7 上,使用密钥加密xy(包括SHA-1哈希)hi会产生:

XLUVZBIJv1n/FV2MzaBK3FLPQRCQF2FY+ghyajdqCGsggAN4aac8bfwscrLaQT7BMHJgfnjJLn+/rwGv0UEW+dbRIMQkNAwkGeSjda3aEpk=

在 1.6 上,同样的事情会产生:

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

没想到作业要花这么长时间,所以我的时间已经用完了,确实需要今晚完成。但是,如果到那时还没有答案,我会就此给我的老师留个便条。这似乎是在 1.7 中修复的一些问题......尽管希望可以通过我的代码中的正确添加/修复来解决。

非常感谢大家的时间!

4

2 回答 2

3

首先:

对于几乎所有系统,两次加密相同的明文应该总是(即以非常高的概率)产生不同的密文。

传统的例子是它允许 CPA 对手仅用两个查询来区分 E(“黎明攻击”)和 E(“黄昏攻击”)。(有一些系统需要确定性加密,但正确的方法是“合成 IV”或 CMC 和 EME 等密码模式。)

最终,问题在于它SecureRandom()不适合密钥派生。

  • 如果输入的“密钥”是密码,您应该使用类似 PBKDF2(或scrypt()bcrypt())的东西。
    • 此外,您应该使用显式字符集,例如String.getBytes("UTF-8").
  • 如果输入的“key”是一个键,最常见的字符串表示是一个 hexdump。Java 不包含 unhexing 函数,但这里有几个
    • 如果输入是“主密钥”并且您想要派生子密钥,那么您应该将其与其他数据进行散列。如果子键始终相同,则没有多大意义。

额外的挑剔:

  • 您的代码容易受到填充预言机攻击;您确实应该在对数据进行任何操作之前验证 MAC(或者更好的是,使用经过身份验证的加密模式)。
  • 在您的第二个清单中,您明确地重用了 IV。坏的!假设 CBC 模式,使用的 IV 应该是不可预测的;SecureRandom在这里很有用。
于 2012-10-09T01:37:53.680 回答
2

我一直在看一遍又一遍,我必须同意 NullUserException。问题是使用SecureRandom. 这意味着您永远不会真正知道您的密钥是什么,因此它不一定是相同的密钥。

encKey 来自 SecureRandom,它由提供的密钥播种。因此,如果密钥相同,则种子相同,所以随机数应该相同......

...当然,除非 Oracle(或其他提供商)更改版本之间的实现。

好的,添加更多我研究的信息。我认为这个答案是最有帮助的。

从用户那里获取密码和明文,并将它们转换为字节数组。
生成一个安全的随机盐。
将盐附加到密码并计算其加密哈希。重复多次。
使用生成的哈希作为初始化向量和/或密钥来加密明文。
保存盐和生成的密文。

对我来说,这听起来像是SecureRandom一次用于生成 asaltsalt必须与密码文本一起保存才能撤消密码过程。额外的安全性来自步骤的重复和变化(模糊性)。

注意:我无法就这些步骤是最佳实践达成共识。

于 2012-10-09T01:06:20.073 回答