尝试使用 BouncyCastle 中的 AES-CBC 解密加密文本的问题
Issues trying use AES-CBC from BouncyCastle to decrypt the encrypted text
我在使用 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 时还是在其他地方。
有没有人能帮帮我?
PHP 代码使用摘要派生密钥和 IV。为此,使用 hash()
方法的默认 return 值,它不是 return 二进制数据,而是用小写字母编码的十六进制数据。这既降低了安全性(因为每个字节的取值范围缩小到 16 个值),也降低了代码的健壮性(因为,根据实现,十六进制字符串也可以使用大写字母)。
此外,十六进制编码使数据大小加倍。因此,SHA256 的十六进制编码密钥大小为 64 字节,因此不是有效的 AES 密钥。 PHP 通过将 AES-256 的密钥静默截断为 32 字节来更正密钥。
Java 代码中未考虑这些方面。密钥和 IV 必须在 Java 代码中派生如下,以匹配 PHP 代码中的密钥和 IV:
使用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
和IV:
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 解密密文时遇到问题 - 该算法是带有 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 时还是在其他地方。
有没有人能帮帮我?
PHP 代码使用摘要派生密钥和 IV。为此,使用 hash()
方法的默认 return 值,它不是 return 二进制数据,而是用小写字母编码的十六进制数据。这既降低了安全性(因为每个字节的取值范围缩小到 16 个值),也降低了代码的健壮性(因为,根据实现,十六进制字符串也可以使用大写字母)。
此外,十六进制编码使数据大小加倍。因此,SHA256 的十六进制编码密钥大小为 64 字节,因此不是有效的 AES 密钥。 PHP 通过将 AES-256 的密钥静默截断为 32 字节来更正密钥。
Java 代码中未考虑这些方面。密钥和 IV 必须在 Java 代码中派生如下,以匹配 PHP 代码中的密钥和 IV:
使用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
和IV:
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。