在 AEAD GCM 模式下篡改 AES 加密缓冲区时不会抛出 AEADBadTagException

AEADBadTagException doesn't throw when tampering with AES encrypted buffer in AEAD GCM mode

Java: Oracle jre1.8.0_45

供应商:BC,BouncyCastle v1.52

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

AEAD 模式:GCM

算法:AES/GCM/NoPadding

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

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

// 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 class 的标准 Java 8 文档了解到,在 AEAD GCM 模式下,身份验证标签是在加密时创建的(这是因为我在加密输出中看到了它byte[] 附加在末尾)并在解密时验证(我提供了完整的加密输出,包括 16 字节标签作为解密输入)以及标签是否不验证该数据(包括我不验证的 AAD 数据)现在使用但会)它会抛出 AEADBadTagException。在我的代码中它不会那样做。

我对 16 字节的倍数数据以及不是 16 字节的数据进行了尝试。两者的结果相同。如果使用相同的篡改 (0x67) 值,纯文本输出中的第一个字母会随着消息变长而改变,但这是有道理的。如果我在消息中添加一些字节使其不是 16 的倍数,则提到的错误的第一个字符 'c' 变为“6”。在使用的 AES/GCM/NoPadding 中,长度不能是 16 的倍数反正。

这是对文档的误解吗,是否需要调用其他方法来 'enable' 这种抛出行为(我能找到),或者 BounceyCastle 不抛出它(我了解提供商需要实施加密 classes ISP,以便一切都按照 Java 8 Docs Cipher class.

中的描述进行操作

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

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

更新 29/8 月:添加代码以显示相同的代码抛出 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 中,这些代码 运行s 并执行预期的操作。如果未标记 SunJCE 提供程序,则会抛出错误:

class java.security.InvalidAlgorithmParameterException: Unsupported parameter: 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)

原post有两个问题正在讨论中。一个已经解决(AEADBadTagException),另一个仍然悬而未决(参见原始 post 中的更新 29/AUG)。

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

第二期问题悬而未决:

class java.security.InvalidAlgorithmParameterException: Unsupported parameter: javax.crypto.spec.IvParameterSpec@4fccd51b

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

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

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

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