Dart AES256-CBC加密后Java解密失败,报输入长度异常
Dart AES256-CBC加密,Java解密报错:
Input length must be multiple of 16 when decrypting with padded cipher 我正在做个人项目,需要实现Dart用pointycastle库做AES256-CBC加密,Java端解密。现在Dart端能正常解密,但Java端解密时抛出错误:Input length must be multiple of 16 when decrypting with padded cipher。以下是我的代码,请帮忙排查:
Dart代码
import 'dart:convert'; import 'dart:math'; import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:pointycastle/api.dart'; import 'package:pointycastle/block/aes.dart'; import 'package:pointycastle/block/modes/cbc.dart'; import 'package:pointycastle/digests/sha1.dart'; import 'package:pointycastle/key_derivators/api.dart'; import 'package:pointycastle/key_derivators/pbkdf2.dart'; import 'package:pointycastle/macs/hmac.dart'; import 'package:pointycastle/padded_block_cipher/padded_block_cipher_impl.dart'; import 'package:pointycastle/paddings/pkcs7.dart'; void main() { runApp(const MyApp()); //Salt = 20 bytes var randomSalt = Random.secure(); var salt = List<int>.generate(20, (i) => randomSalt.nextInt(255)); var saltU = Uint8List.fromList(salt); print("Salt: $saltU"); //IV = 16 bytes var randomIV = Random.secure(); var IV = List<int>.generate(16, (i) => randomIV.nextInt(255)); var IVU = Uint8List.fromList(IV); print("IV: $IVU"); Uint8List encriptedText = encryptList(Uint8List.fromList(utf8.encode("testcrypt")),saltU, IVU); print("Encrypted text: $encriptedText"); var ciphertextFinal = <int>[]; ciphertextFinal.addAll(saltU); ciphertextFinal.addAll(IVU); ciphertextFinal.addAll(encriptedText); var ciphertextFinal64 = base64.encode(ciphertextFinal); print("CiphertextFinal: $ciphertextFinal64"); decrypt("yjQrc7Old3EbC+Vn7EM6jN93bNZ4e1Pf5UFFGJhPpGXJgdkwY3BzXo5IMa9KgI8VLG0j7A=="); } String decrypt(String ciphertext) { Uint8List ciphertextlist = base64.decode(ciphertext); var salt = ciphertextlist.sublist(0, 20); var iv = ciphertextlist.sublist(20, 20 + 16); var encrypted = ciphertextlist.sublist(20 + 16); print('ciphertextlist => $ciphertextlist'); Uint8List key = generateKey("Hello", salt); print('key => $key'); CBCBlockCipher cipher = CBCBlockCipher(AESEngine()); ParametersWithIV<KeyParameter> params = ParametersWithIV<KeyParameter>(KeyParameter(key), iv); PaddedBlockCipherParameters<ParametersWithIV<KeyParameter>, Null> paddingParams = PaddedBlockCipherParameters<ParametersWithIV<KeyParameter>, Null>( params, null); PaddedBlockCipherImpl paddingCipher = PaddedBlockCipherImpl(PKCS7Padding(), cipher); paddingCipher.init(false, paddingParams); var val = paddingCipher.process(encrypted); String decrypted = String.fromCharCodes(val); print(decrypted); return "0"; } Uint8List generateKey(String passphrase, Uint8List salt) { // pass salt Uint8List passphraseInt8List = Uint8List.fromList(passphrase.codeUnits); KeyDerivator derivator = PBKDF2KeyDerivator(HMac(SHA1Digest(), 64)); // 64 byte block size Pbkdf2Parameters params = Pbkdf2Parameters(salt, 65556, 32); // 32 byte key size derivator.init(params); return derivator.process(passphraseInt8List); } Uint8List encryptList(Uint8List data, Uint8List salt, Uint8List IV) { final CBCBlockCipher cbcCipher = CBCBlockCipher(AESEngine()); final ParametersWithIV<KeyParameter> ivParams = ParametersWithIV<KeyParameter>(KeyParameter(generateKey("Hello", salt)), IV); final PaddedBlockCipherParameters<ParametersWithIV<KeyParameter>, Null> paddingParams = PaddedBlockCipherParameters<ParametersWithIV<KeyParameter>, Null>( ivParams, null); final PaddedBlockCipherImpl paddedCipher = PaddedBlockCipherImpl(PKCS7Padding(), cbcCipher); paddedCipher.init(true, paddingParams); return paddedCipher.process(data); }
Java代码
import java.security.AlgorithmParameters; import java.security.SecureRandom; import java.util.Arrays; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.binary.Base64; public class Main { public static void main(String[] args) throws Exception { //encrypt("testCrypto"); decrypt("NOLGiYlfERwx3eTkpc6xCbUxsFOSjeLIB96qDjlseklJrcxxweS4MnT5yrN5EKlTC2lZYQ=="); } public static void encrypt(String item) throws Exception { byte[] ivBytes; String password="Hello"; /*you can give whatever you want for password. This is for testing purpose*/ SecureRandom random = new SecureRandom(); byte bytes[] = new byte[20]; random.nextBytes(bytes); byte[] saltBytes = bytes; // Derive the key SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); PBEKeySpec spec = new PBEKeySpec(password.toCharArray(),saltBytes,65556,256); SecretKey secretKey = factory.generateSecret(spec); System.out.println(secretKey.toString()); SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES"); System.out.println("saltBytes : " + Arrays.toString(saltBytes)); //encrypting the word Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, secret); AlgorithmParameters params = cipher.getParameters(); ivBytes = params.getParameterSpec(IvParameterSpec.class).getIV(); System.out.println("ivBytes : " + Arrays.toString(ivBytes)); byte[] encryptedTextBytes = cipher.doFinal(item.getBytes("UTF-8")); //prepend salt and vi byte[] buffer = new byte[saltBytes.length + ivBytes.length + encryptedTextBytes.length]; System.arraycopy(saltBytes, 0, buffer, 0, saltBytes.length); System.arraycopy(ivBytes, 0, buffer, saltBytes.length, ivBytes.length); System.arraycopy(encryptedTextBytes, 0, buffer, saltBytes.length + ivBytes.length, encryptedTextBytes.length); String val = new Base64().encodeToString(buffer); System.out.println(val); } public static String decrypt(String encrypted) { final String key = "aesEncryptionKey"; final String initVector = "encryptionIntVec"; try { IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8")); SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING"); cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv); byte[] original = cipher.doFinal(Base64.decodeBase64(encrypted)); return new String(original); } catch (Exception ex) { ex.printStackTrace(); } return null; } }
问题分析与修复
Java端的decrypt方法完全未遵循Dart端的加密格式,导致报错:
- 硬编码了固定的key和IV,未从加密字符串中提取Dart端生成的salt、IV和实际密文
- 直接对包含salt+IV+密文的完整字节数组解密,总长度不是16的倍数(AES块大小),触发报错
修复后的Java代码
import java.security.AlgorithmParameters; import java.security.SecureRandom; import java.util.Arrays; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.binary.Base64; public class Main { public static void main(String[] args) throws Exception { // 替换为Dart端生成的CiphertextFinal decrypt("yjQrc7Old3EbC+Vn7EM6jN93bNZ4e1Pf5UFFGJhPpGXJgdkwY3BzXo5IMa9KgI8VLG0j7A=="); } public static void encrypt(String item) throws Exception { byte[] ivBytes; String password = "Hello"; SecureRandom random = new SecureRandom(); byte bytes[] = new byte[20]; random.nextBytes(bytes); byte[] saltBytes = bytes; SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), saltBytes, 65556, 256); SecretKey secretKey = factory.generateSecret(spec); SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES"); System.out.println("saltBytes : " + Arrays.toString(saltBytes)); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, secret); AlgorithmParameters params = cipher.getParameters(); ivBytes = params.getParameterSpec(IvParameterSpec.class).getIV(); System.out.println("ivBytes : " + Arrays.toString(ivBytes)); byte[] encryptedTextBytes = cipher.doFinal(item.getBytes("UTF-8")); byte[] buffer = new byte[saltBytes.length + ivBytes.length + encryptedTextBytes.length]; System.arraycopy(saltBytes, 0, buffer, 0, saltBytes.length); System.arraycopy(ivBytes, 0, buffer, saltBytes.length, ivBytes.length); System.arraycopy(encryptedTextBytes, 0, buffer, saltBytes.length + ivBytes.length, encryptedTextBytes.length); String val = new Base64().encodeToString(buffer); System.out.println(val); } public static String decrypt(String encrypted) { String password = "Hello"; // 与Dart端一致的密码 try { // 1. 解码Base64字符串 byte[] ciphertextBytes = Base64.decodeBase64(encrypted); // 2. 提取salt(前20字节)、IV(接下来16字节)、密文(剩余部分) byte[] salt = Arrays.copyOfRange(ciphertextBytes, 0, 20); byte[] iv = Arrays.copyOfRange(ciphertextBytes, 20, 20 + 16); byte[] encryptedData = Arrays.copyOfRange(ciphertextBytes, 20 + 16, ciphertextBytes.length); // 3. 用PBKDF2生成密钥,参数与Dart端完全一致 SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65556, 256); SecretKey secretKey = factory.generateSecret(spec); SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES"); // 4. 初始化AES CBC解密器 Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); IvParameterSpec ivSpec = new IvParameterSpec(iv); cipher.init(Cipher.DECRYPT_MODE, secret, ivSpec); // 5. 解密并转成UTF-8字符串 byte[] original = cipher.doFinal(encryptedData); return new String(original, "UTF-8"); } catch (Exception ex) { ex.printStackTrace(); return null; } } }
关键修正点
- 从加密字符串中正确拆分salt、IV和实际密文,仅对密文部分执行解密
- 使用与Dart端完全一致的PBKDF2参数生成密钥(密码、salt、65556次迭代、256位密钥长度)
- 用提取到的IV初始化解密器,而非硬编码的固定值
- 指定UTF-8编码处理字符串,避免乱码
内容的提问来源于stack exchange,提问作者random_student100
相关产品推荐
相关产品推荐

