尝试使用 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 来解密加密文本的问题的主要内容,如果未能解决你的问题,请参考以下文章

C# BouncyCastle RSA 加密和解密

Bouncycastle PGP 解密和验证

Java 中带有 bouncycastle 的 PBKDF2

使用 BouncyCastle 生成的证书作为服务器进行身份验证时出现“无法识别提供给包的凭据”错误

BouncyCastle 未定义长度 ASN1

如何使用 BouncyCastle 解密,使用 GCM Tag 的字符串,IV 字符串和密钥字符串,所有这些都是十六进制的?