尝试使用 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。