【问题标题】:Java AES decryption detect incorrect keyJava AES 解密检测到不正确的密钥
【发布时间】:2012-07-19 14:40:47
【问题描述】:

我正在编写对文件进行 AES 加密/解密的 android 应用程序。我希望能够检测是否指定了不正确的密码,因此不匹配的密钥被派生用于解密。 我正在使用带有 256 位密钥的 AES/CBC/PKCS7Padding。 如果我执行 cipher.doFinal() 我可以尝试/捕获 BadPaddingException ,它会告诉我出现问题并且可能密钥不正确。但是如果我使用 CipherInputStream 来读取加密文件,我不会得到关于填充正确性的反馈。因此,如果我故意指定错误的密码,它会解密文件,然后报告一切正常,但解密的文件完全是垃圾。 所以我的问题是如何在使用 CipherInputStream 时检测到错误的填充?

【问题讨论】:

  • 我正在使用 PBEWithSHA256And256BitAES-CBC-BC

标签: java android encryption aes padding


【解决方案1】:

尝试改用 GCM 模式(Java 7 或 Bouncy Castle 提供程序)。填充的技巧是,有时在消息被更改后它是正确的(大约 256 次一次)。 GCM 模式将添加完整性保护,因此任何更改都将导致从 BadPaddingException 派生的异常。

但有一件事:您应该在使用 GCM 加密时预先添加(随机)随机数(实际上也是 CBC 模式的规则,但在 CBC 中使用非随机 IV 的影响不那么严重)。

请注意,您需要执行最终计算以获得 badpaddingexception,因此不要忘记关闭或结束底层流。这可能是您当前的问题。

[编辑]:这不是答案,但输入可用于生成更好的CipherInputStream,请参阅我的other answer 关于这个问题。

【讨论】:

  • 感谢您的建议,但在 256 中每次都不会成功。我阅读了 CipherInputStream 的代码,它在处理最终块时捕获了 BadPaddingException 并返回该流结束。也许我需要以某种方式修改 CipherInputStream 类的代码?
  • 如果您设置了 NoPadding,那么您的代码将永远不会捕获错误的填充。检查您的解密代码是否设置为 PKCS7。
  • 当然设置为PKCS7Padding。这就是我问这个问题的原因。
  • 对不起,这当然没有帮助,应该关闭或在最后关闭不正确的 InputStream。
  • @rossum 似乎 CipherInputStream 在桌子底下刷了这个异常(这对我来说闻起来像是很糟糕的设计),看看我的另一个答案。
【解决方案2】:

在您的数据中添加一些已知的标头。先解密,如果不符合预期,停止并返回错误。

【讨论】:

  • 如果整个消息或密钥都受到影响,这将起作用,否则它将是一个糟糕的人类 CRC。就我个人而言,我宁愿自己解决问题(参见我的第二个答案),然后使用 GCM 模式加密。
  • 这样做的目的不是为了成为一个 CRC,只是为了更容易/更快地检查他们是否拥有正确的密钥/密码。 GCM 或 CBC+HMAC 可用于确保整个消息未被修改。如果他们从密码中获取密钥,他们也可以使用某种密钥校验和/哈希来快速验证密码。
  • 我想我会扩展CipherInputStream并修改它以抛出BadPaddingException,因为填充验证对我来说是完全可以接受的,这就是为什么我在最初开发我的应用程序时没有使用HMAC验证。
  • 好吧,您最了解您的应用程序,但是如果有人剥离了您的一些数据块,并且它正确解密但实际上不完整,会发生什么?
  • 你已知的数据方法可以作为一个指标,它肯定比仅仅依靠填充更好(至少会出错 1/256 次)。也就是说,如果您可以添加数据,我宁愿使用身份验证标签 (GCM) 或额外的 HMAC(这需要与您用于加密的密钥不同的密钥)。 CBC 有一个有趣的特性,即如果一个位被翻转,错误只会在两个块内引入。
【解决方案3】:

我认为错误的填充是由于某种原因被捕获的,这来自CipherInputStream 来源:

private int getMoreData() throws IOException {
    if (done) return -1;
    int readin = input.read(ibuffer);
    if (readin == -1) {
        done = true;
        try {
            obuffer = cipher.doFinal();
        }
        catch (IllegalBlockSizeException e) {obuffer = null;}
        catch (BadPaddingException e) {obuffer = null;}
        if (obuffer == null)
            return -1;
        else {
            ostart = 0;
            ofinish = obuffer.length;
            return ofinish;
        }
    }
    try {
        obuffer = cipher.update(ibuffer, 0, readin);
    } catch (IllegalStateException e) {obuffer = null;};
    ostart = 0;
    if (obuffer == null)
        ofinish = 0;
    else ofinish = obuffer.length;
    return ofinish;
}

【讨论】:

  • 这可能是为了正确处理异常,流不应该抛出安全异常。请注意,我之前的答案包括 GCM 模式,但这无济于事,因为完整性差导致的异常也是 BadPaddingException。也许取 CipherInputStream 并重写它 - 将 BadPaddingException 包装到 IOException 中,而不是遵守合同。
【解决方案4】:

这是 CipherInputStream 中 getMoreData() 方法的修改版本,它可能对遇到我的问题的人有用:

private int getMoreData() throws IOException {
    if (done) return -1;
    int readin = input.read(ibuffer);
    if (readin == -1) {
        done = true;
        try {
            obuffer = cipher.doFinal();
        }
        catch (IllegalBlockSizeException e) {
            throw new IOException(e);
        }
        catch (BadPaddingException e) {
            throw new IOException(e);
        }
        if (obuffer == null)
            return -1;
        else {
            ostart = 0;
            ofinish = obuffer.length;
            return ofinish;
        }
    }
    try {
        obuffer = cipher.update(ibuffer, 0, readin);
    } catch (IllegalStateException e) {obuffer = null;};
    ostart = 0;
    if (obuffer == null)
        ofinish = 0;
    else ofinish = obuffer.length;
    return ofinish;
}

【讨论】:

  • 能否请您至少upvote我的答案包括来源?
【解决方案5】:

我也遇到过这个。我有一个测试,在使用 Java 支持的 AES/CBC/PKCS5Padding 的一千次运行中可靠地失败了几次。

要修复,您可以按照上面的建议进行操作并使用充气城堡。

但是,我做了一个不同的修复,只是在加密之前向纯文本添加了一个 md5 内容哈希,我在解密时验证了这一点。只需将内容附加到 md5 散列并在解密时获取 md5 散列的前 22 个字符并验证字符串的其余部分是否具有相同的散列,如果没有则抛出异常或返回明文(没有 md5 散列)如果匹配。无论加密算法如何,这都将起作用。可能在 GCM 模式下,这确实是不需要的。无论如何,这样你可以避免对充气城堡的额外依赖。

【讨论】:

  • 您需要解密整个流并执行 MD5 才能知道解密是否值得;)
【解决方案6】:

我有同样的问题,如何知道用于加密的密钥是否与用于解密的密钥相同,因为在我的情况下,我可以解密字符串但它返回了一些垃圾,我需要知道加密的字符串(它是随机的)以获得正确的值。

所以我所做的是;

解密加密的字符串。
使用正确的密钥再次加密字符串。
解密之前的加密字符串。
如果原始解密密钥等于新解密密钥,则匹配。

    Cipher c = Cipher.getInstance(algorithm);
    c.init(Cipher.DECRYPT_MODE, keyspec, ivspec);
    byte[] decValue = c.doFinal(encryptedData.getBytes());
    decryptedValue = new String(decValue,"UTF-8");

  //now i have the string decrypted in decryptedValue

  byte[] encryptAgain = encrypt(decryptedValue);
  String encryptAgaindecripted = new String(c.doFinal(encryptAgain),"UTF-8");

  //if keys match then it uses the same key and string is valid
 if (decryptedValue.equals(encryptAgaindecripted)){
  //return valid
 }

希望这对某人有所帮助。

【讨论】:

    猜你喜欢
    • 2012-08-27
    • 2015-09-23
    • 1970-01-01
    • 2021-05-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多