Java AES 解密检测到不正确的密钥

Posted

技术标签:

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

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

【问题讨论】:

我正在使用 PBEWithSHA256And256BitAES-CBC-BC 【参考方案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
 

希望这对某人有所帮助。

【讨论】:

以上是关于Java AES 解密检测到不正确的密钥的主要内容,如果未能解决你的问题,请参考以下文章

Java使用AES加解密

Java AES加密和静态密钥解密

java的aes加密成多少位数

Java笔记-DH密钥交换获取密钥及AES加解密

java AES加密解密

Java 加密解密 对称加密算法 非对称加密算法 MD5 BASE64 AES RSA