You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET转Java的AES解密方法报错:WRONG_FINAL_BLOCK_LENGTH排查

.NET加密Java解密时CipherInputStream抛出WRONG_FINAL_BLOCK_LENGTH错误

我正在开发一套.NET端加密、Android端(Java)解密的应用。为验证.NET加密逻辑的正确性,先实现了.NET版本的DecryptStreamWithEmbeddedHash解密方法,随后编写对应的Java版本,但执行CipherInputStream读取解密内容的循环时,抛出错误:java.io.IOException: javax.crypto.IllegalBlockSizeException: error:1e00007b:Cipher functions:OPENSSL_internal:WRONG_FINAL_BLOCK_LENGTH。调试时流的索引与位置看似正常,想请教问题的具体原因。

.NET原实现代码

public static byte[] DecryptStreamWithEmbeddedHash(Stream inputStream, string password)
{
    const int iterations = 10000;
    const string customSalt = "myCustomSalt";
    static HashAlgorithmName hashAlgorith = HashAlgorithmName.SHA256;
    byte[] salt = Encoding.ASCII.GetBytes(password + customSalt);

    using (var aesAlg = Aes.Create())
    {
        using (var keyDerivation = new Rfc2898DeriveBytes(password, salt, iterations, hashAlgorith))
        {
            aesAlg.Key = keyDerivation.GetBytes(32);
            aesAlg.IV = keyDerivation.GetBytes(16);

            // read hash length
            byte[] hashLengthBytes = new byte[sizeof(int)];
            inputStream.Read(hashLengthBytes, 0, sizeof(int));
            int hashLength = BitConverter.ToInt32(hashLengthBytes, 0);

            // read hash
            byte[] embeddedHash = new byte[hashLength];
            inputStream.Read(embeddedHash, 0, hashLength);

            // decrypt content
            using (var cryptoStream = new CryptoStream(inputStream, aesAlg.CreateDecryptor(), CryptoStreamMode.Read))
            using (var memoryStream = new MemoryStream())
            {
                cryptoStream.CopyTo(memoryStream);
                return memoryStream.ToArray();
            }
        }
    }
}

Java原实现代码

public static byte[] decryptStreamWithEmbeddedHash(InputStream inputStream, String password) throws Exception {
    int iterations = 10000;
    String customSalt = "myCustomSalt";
    String hashAlgorithm = "SHA-256";
    byte[] salt = (password + customSalt).getBytes(StandardCharsets.US_ASCII);

    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
    KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, iterations, 256);
    SecretKey tmp = factory.generateSecret(spec);
    SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
    byte[] iv = Arrays.copyOfRange(salt, 0, 16); // IV is the first 16 bytes of salt

    // read hash length
    byte[] hashLengthBytes = new byte[4]; // sizeof(int)
    inputStream.read(hashLengthBytes, 0, 4);
    int hashLength = ByteBuffer.wrap(hashLengthBytes).order(ByteOrder.LITTLE_ENDIAN).getInt();

    // read hash
    byte[] embeddedHash = new byte[hashLength];
    inputStream.read(embeddedHash, 0, hashLength);

    // decrypt content
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
    ByteArrayOutputStream decryptedStream = new ByteArrayOutputStream();
    try (CipherInputStream cryptoStream = new CipherInputStream(inputStream, cipher)) {
        byte[] buffer = new byte[8192];
        int len;
        while ((len = cryptoStream.read(buffer)) > 0) {
            decryptedStream.write(buffer, 0, len);
        }
    } catch (Exception err) {
        err.printStackTrace();
    }
    byte[] decryptedData = decryptedStream.toByteArray();

    // verify hash
    MessageDigest md = MessageDigest.getInstance(hashAlgorithm);
    byte[] calculatedHash = md.digest(decryptedData);
    if (!Arrays.equals(calculatedHash, embeddedHash)) {
        throw new Exception("Hash verification failed.");
    }

    return decryptedData;
}

问题核心原因

  • IV生成逻辑完全不匹配:.NET中通过Rfc2898DeriveBytes生成32字节Key后,继续调用GetBytes(16)获取IV;但Java代码直接取salt的前16字节作为IV,导致解密时IV不匹配,破坏了AES-CBC的块解密逻辑,引发块长度错误。
  • PBKDF2哈希算法不一致:.NET使用Rfc2898DeriveBytes结合SHA256生成密钥,而Java代码用的是PBKDF2WithHmacSHA1,算法差异导致生成的AES Key完全不同,解密过程直接失效。
  • 流读取未确保完整性:原Java代码中inputStream.read()方法无法保证一次性读取完整的哈希长度和哈希内容,当读取不完整时,后续解密的起始位置偏移,导致密文块结构被破坏。

修正后的Java实现代码

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.MessageDigest;
import java.security.spec.KeySpec;
import java.util.Arrays;

public class DecryptionUtils {
    public static byte[] decryptStreamWithEmbeddedHash(InputStream inputStream, String password) throws Exception {
        int iterations = 10000;
        String customSalt = "myCustomSalt";
        String hashAlgorithm = "SHA-256";
        byte[] salt = (password + customSalt).getBytes(java.nio.charset.StandardCharsets.US_ASCII);

        // 匹配.NET的PBKDF2WithHmacSHA256算法,总长度为Key(32字节)+IV(16字节)=384位
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, iterations, 384);
        SecretKey tmp = factory.generateSecret(spec);
        byte[] derivedBytes = tmp.getEncoded();
        
        // 前32字节作为AES Key
        SecretKey secret = new SecretKeySpec(Arrays.copyOfRange(derivedBytes, 0, 32), "AES");
        // 后16字节作为IV,完全匹配.NET逻辑
        byte[] iv = Arrays.copyOfRange(derivedBytes, 32, 48);

        // 确保读取完整的哈希长度字节
        byte[] hashLengthBytes = new byte[4];
        int bytesRead = 0;
        while (bytesRead < 4) {
            int read = inputStream.read(hashLengthBytes, bytesRead, 4 - bytesRead);
            if (read == -1) {
                throw new Exception("Unexpected end of stream when reading hash length");
            }
            bytesRead += read;
        }
        int hashLength = ByteBuffer.wrap(hashLengthBytes).order(ByteOrder.LITTLE_ENDIAN).getInt();

        // 确保读取完整的哈希内容
        byte[] embeddedHash = new byte[hashLength];
        bytesRead = 0;
        while (bytesRead < hashLength) {
            int read = inputStream.read(embeddedHash, bytesRead, hashLength - bytesRead);
            if (read == -1) {
                throw new Exception("Unexpected end of stream when reading embedded hash");
            }
            bytesRead += read;
        }

        // 解密内容
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
        ByteArrayOutputStream decryptedStream = new ByteArrayOutputStream();
        try (CipherInputStream cryptoStream = new CipherInputStream(inputStream, cipher)) {
            byte[] buffer = new byte[8192];
            int len;
            while ((len = cryptoStream.read(buffer)) != -1) {
                decryptedStream.write(buffer, 0, len);
            }
        }
        byte[] decryptedData = decryptedStream.toByteArray();

        // 验证哈希
        MessageDigest md = MessageDigest.getInstance(hashAlgorithm);
        byte[] calculatedHash = md.digest(decryptedData);
        if (!Arrays.equals(calculatedHash, embeddedHash)) {
            throw new Exception("Hash verification failed.");
        }

        return decryptedData;
    }
}

内容的提问来源于stack exchange,提问作者markzzz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 22:27:33