【问题标题】:AEADBadTagException doesn't throw when tampering with AES encrypted buffer in AEAD GCM mode在 AEAD GCM 模式下篡改 AES 加密缓冲区时不会抛出 AEADBadTagException
【发布时间】:2015-11-23 01:10:41
【问题描述】:

Java:Oracle jre1.8.0_45

提供者:BC,BouncyCastle v1.52

密码:AES 256 位密钥(已安装安全策略)

AEAD 模式:GCM

算法:AES/GCM/NoPadding

我使用上面显示的参数进行了完美的 AES 加密/解密。然后在我的调试过程中,我通过在解密前更改加密缓冲区中的数据添加了一个简单的篡改模拟。我预计会抛出 AEADBadTagException,但它没有发生。我还没有使用任何 upedateAAD(),我们谈论的是纯加密数据有效负载。

我像这样简单地进行篡改,在它已经包含加密数据和身份验证标签的 16 个额外字节之后,我覆盖了 byte[] 的第一个字节。

// set-up for encryption, key, IV, etc...
...
try
{
  String sPlainText="The non-encrypted (AES) message.";    
  byte[] baEncrypted=oCIPH.doFinal(sPlainText.getBytes());

  MetaLogbook.info(baEncrypted); // Shows well encrypted buffer

  // Tampering simulations
  baEncrypted[0]=0x67;

  // re-initialize for decryption, same key and IV...

  String sDecryptedText=new String(oCIPH.doFinal(baEncrypted),"UTF-8");
  MetaLogbook.info(sDecryptedText);

  // The above log line shows the plain text with a different first letter
  // each time that i change 0x67 in other values. The rest of the message
  // matches the plain text on input. I can see the 16 extra bytes of the 
  // authentication tag appended to the clear text.
}
catch(Exception e)
{
  // I expected to come here due to a AEADBadTagException but I never 
  // come here.
  MetaLogbook.error(e);
}

当我更改在模拟篡改时分配的值时,生成的解密文本在第一个字符处发生更改。它以非线性方式变化。 0x65 产生一个“c”,而 0x67 产生一个“?”等等。普通消息的其余部分保持正确,只有解密输出的第一个字符似乎受到影响。

我从 Cipher 类的标准 Java 8 文档中了解到,在 AEAD GCM 模式下,身份验证标签是在加密时创建的(这是因为我在末尾附加的加密输出字节 [] 中看到了它)并经过验证在解密时(我提供了完整的加密输出,包括 16 字节标签作为解密输入),如果标签不使用该数据进行验证(包括我现在不使用但会使用的 AAD 数据),它将抛出 AEADBadTagException .在我的代码中它没有这样做。

我用 16 字节的倍数的数据以及不是的数据尝试了这个。两者的结果是相同的。如果使用相同的篡改 (0x67) 值,则纯文本输出中的第一个字母会在消息变长时发生变化,但这是有道理的。如果我在消息中添加一些字节使其不是 16 的倍数,则上述错误的第一个字符“c”变为“6”。在使用的 AES/GCM/NoPadding 中,长度无论如何都不能是 16 的倍数。

这是对文档的误解吗,是否需要调用其他方法来“启用”这种抛出行为(我可以找到任何方法),或者 BounceyCastle 没有抛出它(我了解提供者需要实现加密类 ISP,以便一切行为都如 Java 8 Docs Cipher 类中所述。

我无法与 SunJCE 提供商进行比较,因为它不支持 AES/GCM/NoPadding。

有没有人有一些额外的信息。 TIA

8 月 29 日更新:作为 cmets 讨论的一部分,添加了代码以显示 SunJCE 而不是 BC 提供程序引发的相同代码。

private static void testing()
{
  try
  {
    // Unremark these lines to see it work
    //Security.addProvider(new BouncyCastleProvider()); // "BC"
    //Cipher oCIPH=Cipher.getInstance("AES/GCM/NoPadding", "BC");

    // Unremark these lines to see it fail
    Cipher oCIPH=Cipher.getInstance("AES/GCM/NoPadding", "SunJCE"); 

    // Make a quick and dirty IV and Symmetric Key
    byte[] baIV="EECE34808EF2A9AC".getBytes("UTF-8");
    byte[] baKey="010F05E3E0104EB59D10F37EA8D4BB6B".getBytes("UTF-8");

    // Make IV and Key (well KeySpec for AES) object. Use IV parspec because
    // defaults to 128bit Authentication tag size & works in both GCM & CBC.
    IvParameterSpec ps=new IvParameterSpec(baIV);
    SecretKeySpec sk=new SecretKeySpec(baKey,"AES");

    // Unremakr one line, either shrtline (multiple of 16 bytes) or long line   
    //String sPlainText="The non-encrypted (AES) message.";
    String sPlainText="The non-encrypted (AES) message. Everything after the . makes this NOT a multiple of 16 bytes.";

    // Encrypt
    oCIPH.init(Cipher.ENCRYPT_MODE, sk, ps);
    byte[] baEncrypted=oCIPH.doFinal(sPlainText.getBytes());

    // Decrypt
    oCIPH.init(Cipher.DECRYPT_MODE, sk, ps);
    String sDecryptedText=new String(oCIPH.doFinal(baEncrypted),"UTF-8");        
  }
  catch(Exception e)
  {
    MetaLogbook.log("Security Tools Exception",e);
  }
} 

上面的代码可以在 SunJCE 或 BouncyCastle 上运行,方法是取消顶部需要的行的注释。在 BC 中,这些代码运行并执行预期的操作。如果 SunJCE 提供者未加注释,则会引发错误:

类 java.security.InvalidAlgorithmParameterException: 不支持 参数:javax.crypto.spec.IvParameterSpec@4fccd51b com.sun.crypto.provider.CipherCore.init (CipherCore.java:509) com.sun.crypto.provider.AESCipher.engineInit (AESCipher.java:339) javax.crypto.Cipher.init (Cipher.java:1394) javax.crypto.Cipher.init (Cipher.java:1327)

【问题讨论】:

  • 普通 Java 8 JRE 支持 GCM。真的,这里发生了什么?我真的怀疑一个非常草率的实现。
  • 每一个 AES GCM 代码都可以用 ONE SINGLE LINE 来验证这一点,即回火注释上的那个。执行马虎?它对所有其余部分都 100% 有效。如果添加篡改模拟,就不会抛出那个异常。如果有人可能有同样的问题,或者通过覆盖加密缓冲区中的一个字节,用一行代码创建它,你为什么要关闭这个问题。
  • @Maarten,该算法确实在 SunJCE 中受支持,但相同的代码适用于 BC 而不是 SunJCE 提供者(抛出不支持的参数:javax.crypto.spec.IvParameterSpec@cc62a3b)。我会自己解决这个问题,16 字节的 IV 字节 [] 被 BC 接受,而不是 SunJCE 接受。在主要问题中没有正确表达;对不起。
  • 提示:GCMParameterSpec。我无法使用 Bouncy Castle 重现您的问题。即使我不得不大幅重写您的代码,我也尝试过。因此,关闭和downvote。目前无法回答该问题。
  • 我写了一段代码来显示错误并且可以剪切和粘贴。目前只有参数规范。

标签: java cryptography aes bouncycastle aes-gcm


【解决方案1】:

原来的帖子有两个问题正在讨论中。一个已解决(AEADBadTagException),另一个仍待处理(请参阅原始帖子中的 UPDATE 29/AUG)。

解决的问题: 我最近不得不为使用 AES 的 Key/SecretKey 类的用法编写异常代码,以改用 SecretKeySpec。该更改引发了一个错误,该错误影响了代码所遵循的路径,现在在搜索不抛出 AEADBadTagException 的上下文中进行了更正。所有其余部分都继续工作的事实是因为流量更改导致两次初始化加密而不是第二次解密。 我不明白的是无论如何解密都能正常工作。 AES 是一种对称算法,但它具有 S-Box 和反向 S-Box,因此人们会认为加密不能像 DES 等完全对称密码那样仅用于代替解密。

第二个问题仍然悬而未决:

类 java.security.InvalidAlgorithmParameterException:不支持的参数:javax.crypto.spec.IvParameterSpec@4fccd51b

只需选择代码顶部的提供程序即可通过提供的代码复制它,其余的保持不变。该代码适用于 BC,不适用于 SunJCE。

我看到 Metalogbook 行可能需要在您自己的日志记录代码中进行更改。

尽管 SunJCE 提供程序不是我用于加密的,并且 GCM 问题已解决,但我会继续关注此问题,以便在需要时为 SunJCE 抛出提供更多信息。

更新:通过进一步挖掘,我发现了 IVParamSpec 抛出的问题。 BC 接受 CBC 和 GCM 的此对象,并将身份验证标签默认为 128 位。另一方面,SunJCE 特别需要 GCMParamSpec 对象和 GCM 身份验证标签的显式大小,并接受 CBC 但不接受 GCM 的 IVParamSpec。

【讨论】:

  • 正如 Maarten 13 小时前所说,GCMParameterSpec
  • @dave,是的,正是 Maarten 的评论让我再次查看了 paramspec。但不确定为什么两个提供商的行为不同。由于我使用 IVParamSpec 将我的课程限制为 CBC 和 GCM,因此需要更少的异常代码。无论如何,这个问题已被理解,并感谢 Maarten 的帮助。
  • GCM 使用 CTR 操作模式作为加密原语。如果你看看 CTR 是如何运作的,你就会明白为什么使用相同的密钥/IV 加密也会解密密文。
  • @Maarten,谢谢。我会调查一下我想了解的。
  • “通过进一步挖掘,我发现了问题”:你的意思是,通过最终尝试我们一直在说的内容。
猜你喜欢
  • 2018-02-19
  • 1970-01-01
  • 1970-01-01
  • 2019-12-14
  • 2021-05-12
  • 2021-04-12
  • 1970-01-01
  • 2017-08-11
  • 1970-01-01
相关资源
最近更新 更多