.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
相关产品推荐
相关产品推荐

