我正在使用 BouncyCastle 在 CBC 模式下使用 AES 和 PKCS5 填充来加密/解密一些文件:
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding", "BC");
现在有两个问题:
- 如何检查提供的用于解密数据的密钥是否正确?
- 如何检查加密输入是否未被触及(例如,用户使用 HEX 编辑器未更改)?
谢谢
我正在使用 BouncyCastle 在 CBC 模式下使用 AES 和 PKCS5 填充来加密/解密一些文件:
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding", "BC");
现在有两个问题:
谢谢
您可以使用AEAD 模式(如 CCM 或 GCM)代替 CBC。这些模式对加密消息进行身份验证,因此如果使用了错误的密钥,或者密文已被更改,您可以检测到它。但是,您将无法区分这些情况。
Java 7 的 GCM 加密 API 支持,但 Oracle 的 Java 实现附带的 SunJCE 提供程序还不支持它。您可以通过 BouncyCastle 等第三方提供商获得支持。
如果您使用额外的加密服务,例如数字签名或消息验证码,您可以实现相同的目的。
您应该添加一个 MAC,它首先验证消息的完整性,然后才能解密它。MAC 的一个常见选择是带有您喜欢的任何散列函数的 HMAC,例如 SHA-2。
与其自己做这件事,不如使用经过身份验证的密码。AES-GCM是一种常见的选择。但是在这种情况下,您需要非常小心,不要重复使用 IV。
加密不仅与算法和加密密钥有关,还与系统组织有关。
通常,您无法确定密钥是否正确。任何密钥都可以用来解密应该被解密的数据,但是这取决于其他一些机制来告诉你这是否是“正确”的结果。
通常,您无法确定要解密的数据是否未被触及,除非通过一些外部检查。大多数加密系统的一个特性是,更改任何加密数据都会极大地改变解密的输出,可能会变成你认为是垃圾的东西。
JCE 密码通常非常基础。如果您需要包括完整性和密钥测试在内的全功能保护,则需要将它们结合起来。和往常一样,最好不要自己设备。所以最好选择更高级的格式,如 PKCS7/12 或 PGP。
根据使用的填充,当您尝试使用错误的密钥对其进行解密时,某些密码会给您一个 PaddingException。为了进行更强的完整性检查,我将使用包含 HMAC 字节的填充。
JCE 中包含一个相当完整的方法,它是 AESWrap 算法。它需要填充数据,但会确保完整性。它最好与 RFC 3537 中描述的长度字节结合使用。请注意,这仅适用于较小数量的机密(如对称密钥)。RFC3537 填充限制为 255 个字节。
要将其与密码派生密钥一起使用,您可以使用:
char[] pass = ... // your password
byte[] codeBytes = ... // up to 255 bytes you want to protect
// generate wrapping key from password
SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
SecureRandom rand = SecureRandom.getInstance("SHA1PRNG");
byte[] salt = new byte[16]; rand.nextBytes(salt);
SecretKey kek = f.generateSecret(new PBEKeySpec(pass, salt, 1000, 128));
kek = new SecretKeySpec(password.getEncoded(), "AES"); // convert into AES
// RFC3537 padding (lengthbyte)
byte[] wrappedCodeBytes = new byte[codeBytes + 1 % 8];
System.arraycopy(codeBytes,0,wrappedCodeBytes,1,wrappedCodeBytes.length);
paddedCodeBytes[0]=(byte)codeBytes.length;
byte[] pad = new byte[paddedCodeBytes.length - codeBytes.length -1]; rand.nextBytes(pad);
System.arraycopy(pad,0,paddedCodeBytes,codeBytes.length+1,pad.length);
// AESWrap is WRAP_MODE:needs a SecretKey
SecretKey paddedCodeKey = new SecretKeySpec(paddedCodeBytes, "RAW");
// now wrap the password with AESWrap kek is 128 bit
Cipher c = Cipher.getInstance("AESWrap"); // default IV
c.init(Cipher.WRAP_MODE, kek);
byte[] result = c.warp(paddedCodeKey);
解包留给读者作为练习 :) 示例代码使用 128 位密钥大小,因为无论如何不能从 PBKDF2 获得更多熵。
请注意,这将很有可能检测到错误的密码,一些批评者会认为这是 AESWrap 的一个弱点。
看看这个关于 BC 加密的教程,特别是 InitCiphers 方法,并详细了解第二个代码块,它指定了实际的密码类型。
如何检查提供的用于解密数据的密钥是否正确?
根据JCE Javadocs,特别是Class SecretKeySpec的构造函数:
此构造函数不检查给定字节是否确实指定了指定算法的密钥。例如,如果算法是 DES,则此构造函数不检查密钥是否为 8 字节长,也不检查弱或半弱密钥。为了执行这些检查,应使用特定于算法的密钥规范类(在本例中:DESKeySpec)。
请注意,Interface KeySpec列出了所有实现类,基本上是验证选项列表。
如何检查加密输入是否未被触及(例如,用户使用 HEX 编辑器未更改)?
的确。这是一个很好的。“输入”非常通用。您是指要解密的实际内容吗?好吧,如果它被修改了,我相信它不会正确解密。那有意义吗?
如果您正在谈论更改奇偶校验位的密钥的情况,如Bouncy Castle FAQ中的第 (6) 项所述,您将必须对密钥进行实际的奇偶校验。仅密钥的前 56 个字节用于加密操作,后 8 个字节保留用于奇偶校验。因此,本质上,“密钥”的最后一部分可以更改,而第一部分仍然有用。要检测奇偶校验或密钥是否已更改,您将运行奇偶校验。我在进行奇偶校验时发现了这个小曲子。并且,有关如何在这些密钥中设置奇偶校验的更多信息,请参阅Jan Luehe 的类 DESKeyGenerator 的 JDK7 加密提供程序源中的注释(接近底部)讨论奇偶校验设置。
我最近与 BC 进行了一些互动,希望这些信息对我有所帮助。