我正在编写对文件进行 AES 加密/解密的 android 应用程序。我希望能够检测是否指定了不正确的密码,因此不匹配的密钥被派生用于解密。我正在使用带有 256 位密钥的 AES/CBC/PKCS7Padding。如果我执行 cipher.doFinal() 我可以尝试/捕获 BadPaddingException,它会告诉我有问题并且可能密钥不正确。但是如果我使用 CipherInputStream 来读取加密文件,我不会得到关于填充正确性的反馈。因此,如果我故意指定错误的密码,它会解密文件,然后报告一切正常,但解密的文件完全是垃圾。所以我的问题是如何在使用 CipherInputStream 时检测到错误的填充?
6 回答
将一些已知的标头添加到您的数据中。首先解密它,如果它不符合您的预期,停止并返回错误。
尝试改用 GCM 模式(Java 7 或 Bouncy Castle 提供程序)。填充的技巧是,有时在消息被更改后它是正确的(大约 256 次一次)。GCM 模式将添加完整性保护,因此任何更改都将导致从 BadPaddingException 派生的异常。
不过有一件事:在使用 GCM 加密时,您应该预先添加一个(随机)随机数(实际上也是 CBC 模式的规则,但在 CBC 中使用非随机 IV 的影响不那么严重)。
请注意,您需要执行最终计算以获得 badpaddingexception,因此不要忘记关闭或结束底层流。这可能是您当前的问题。
[编辑]:这不是答案,但输入可用于生成更好的CipherInputStream
,请参阅我关于此问题的其他答案。
我认为由于某种原因,错误的填充被捕获,这是来自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;
}
这是 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;
}
我有同样的问题,如何知道用于加密的密钥是否与用于解密的密钥相同,因为在我的情况下,我可以解密字符串但它返回了一些垃圾,我需要知道加密的字符串(它是随机的)得到正确的值。
所以我所做的是;
解密加密的字符串。
使用正确的密钥再次加密字符串。
解密之前的加密字符串。
如果原始解密密钥等于新解密密钥,则匹配。
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/CBC/PKCS5Padding 的一千次运行中可靠地失败了几次。
要修复,您可以按照上面的建议进行操作并使用充气城堡。
但是,我做了一个不同的修复,只是在加密之前向纯文本添加了一个 md5 内容哈希,我在解密时验证了这一点。只需将内容附加到 md5 散列并在解密时获取 md5 散列的前 22 个字符并验证字符串的其余部分是否具有相同的散列,如果没有则抛出异常或返回明文(没有 md5 散列)如果匹配。无论加密算法如何,这都将起作用。可能在 GCM 模式下,这确实是不需要的。无论如何,这样你可以避免对充气城堡的额外依赖。