Java:甲骨文 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 日更新:添加代码以显示相同的代码与 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)