尝试使用 BouncyCastle 的 AES-CBC 来解密加密文本的问题
Posted
技术标签:
【中文标题】尝试使用 BouncyCastle 的 AES-CBC 来解密加密文本的问题【英文标题】:Issues trying use AES-CBC from BouncyCastle to decrypt the encrypted text 【发布时间】:2021-10-23 23:12:19 【问题描述】:我在使用 BouncyCastle 解密密文时遇到问题 - 算法是带有 PKC7 填充的 AES-256CBC。
轻松解密数据的php代码如下:
$output = 'WwBOU6s8DaMWmYdctBJwfuoujFgVygBUjhsbdf8eWqQ='; /* The encrypted return that we are going to decrypt */
$date = '2021-05-26 14:00:00'; /* The date which must correspond to the exact date transmitted during the call */
$private_key = '7X9gx9E3Qx4EiUdB63nc'; /* Your private key obtained when you created your API access */
$key = hash('sha256' , $date . $private_key); /* Creation of the key by concatenating the date and the private key */
$iv = mb_strcut(hash('sha256', hash('sha256', $private_key)), 0, 16,); /* Creation of the initialization vector (2x hash of the private key) by taking 16 bytes */
echo (openssl_decrypt($output, 'aes-256-cbc', $key, false, $iv)); /* Display the return using openssl_decrypt */
我在 Java 中实现相同的尝试如下:
public class Aes
public static void main(String[] args) throws Exception
String date = "2021-05-26 14:00:00";
String private_key = "7X9gx9E3Qx4EiUdB63nc";
String composite = date+private_key;
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(composite.getBytes());
byte[] stringHash = messageDigest.digest();
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(private_key.getBytes());
MessageDigest md2 = MessageDigest.getInstance("SHA-256");
md2.update(md.digest());
String encryptedText = "WwBOU6s8DaMWmYdctBJwfuoujFgVygBUjhsbdf8eWqQ=";
/*
* Register BC provider dynamically, BC needed for AES 256.
* AES 256 is Rijndael 128 using 32 byte key and 16 byte IV.
*/
Security.addProvider(new BouncyCastleProvider());
byte[] skey = stringHash;
//byte[] ivec = new byte[16]; // AES 256 use 16 byte IVEC
byte[] ivec=copyOfRange(md2.digest(), 0,16);
byte[] encrypted = javax.xml.bind.DatatypeConverter.parseBase64Binary(encryptedText);
if (encrypted.length % 16 != 0)
throw new IllegalArgumentException("because has padding, input must be multiple of 16, length=" + encrypted.length);
PaddedBufferedBlockCipher aes = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()));
CipherParameters ivAndKey = new ParametersWithIV(new KeyParameter(skey), ivec);
aes.init(false, ivAndKey); // false for decryption
int minSize = aes.getOutputSize(encrypted.length);
byte[] outBuf = new byte[minSize];
int length1 = aes.processBytes(encrypted, 0, encrypted.length, outBuf, 0);
int length2 = aes.doFinal(outBuf, length1);
int actualLength = length1 + length2;
byte[] decrypted = new byte[actualLength];
System.arraycopy(outBuf, 0, decrypted, 0, actualLength);
String decryptedString = new String(decrypted, "UTF-8");
System.out.println("<[" + decryptedString + "]>");
问题是我得到了异常并且我无法解决它:
Exception in thread "main" org.bouncycastle.crypto.InvalidCipherTextException: pad block corrupted
at org.bouncycastle.crypto.paddings.PKCS7Padding.padCount(Unknown Source)
at org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher.doFinal(Unknown Source)
at Aes.main(Aes.java:68)
我不确定问题出在哪里,是我尝试对私钥进行 2xhash 还是在其他地方。
有没有人能帮帮我?
【问题讨论】:
Java 代码错误地确定了 key 和 IV。这主要是因为在 PHP 代码中,1.hash()
方法默认将数据返回为 小写十六进制字符串,以及 2.PHP 会截断过长的键。 Java 代码中没有正确考虑到这一点。
【参考方案1】:
PHP 代码使用摘要来导出密钥和 IV。为此,使用hash()
方法的默认返回值,它不返回二进制数据,而是用小写字母编码的十六进制数据。这既降低了安全性(因为每个字节的值范围被缩小到 16 个值),也使代码的健壮性降低(因为根据实现,十六进制字符串也可以使用大写字母)。
此外,十六进制编码使数据大小加倍。因此,SHA256 的十六进制编码密钥大小为 64 字节,因此不是有效的 AES 密钥。 PHP 通过静默地将 AES-256 的密钥截断为 32 个字节来更正密钥。
Java 代码中没有考虑这些方面。密钥和 IV 必须在 Java 代码中按如下方式派生,以匹配 PHP 代码中的:
使用SHA256作为摘要:
MessageDigest md = MessageDigest.getInstance("SHA-256");
密钥确定如下:
md.update(composite.getBytes());
byte[] skey = md.digest();
skey = Hex.toHexString(skey).substring(0, 32).getBytes(StandardCharsets.UTF_8); // Truncate key to 32 bytes
System.out.println("Key (hex): " + new String(skey, StandardCharsets.UTF_8)); // 0ff908ad560ccd391a927570b9a4956f
和静脉注射:
md.update(private_key.getBytes());
byte[] ivec = Hex.toHexString(md.digest()).getBytes(StandardCharsets.UTF_8); // Hex encode (conversion to lowercase not necessary, because Hex.toHexString() already uses lowercase)
md.update(ivec);
ivec = Hex.toHexString(md.digest()).substring(0, 16).getBytes(StandardCharsets.UTF_8); // Hex encode (conversion to lowercase not necessary, because Hex.toHexString() already uses lowercase), truncate IV to 16 bytes
System.out.println("IV (hex): " + new String(ivec, StandardCharsets.UTF_8)); // 18ca5cfff8d04d9b
请注意,digest()
隐式执行重置。 Hex.toHexString()
执行十六进制编码,由 BouncyCastle 提供。
有了这个密钥和IV,密文就可以用Java代码成功解密了。
还要注意密钥和 IV 派生是不安全的。更安全的模式是使用 PBKDF2 等密钥导出函数导出密钥,并为每次加密随机生成 IV。
【讨论】:
以上是关于尝试使用 BouncyCastle 的 AES-CBC 来解密加密文本的问题的主要内容,如果未能解决你的问题,请参考以下文章
Java 中带有 bouncycastle 的 PBKDF2
使用 BouncyCastle 生成的证书作为服务器进行身份验证时出现“无法识别提供给包的凭据”错误
如何使用 BouncyCastle 解密,使用 GCM Tag 的字符串,IV 字符串和密钥字符串,所有这些都是十六进制的?