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

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端的加密格式,导致报错:

  1. 硬编码了固定的key和IV,未从加密字符串中提取Dart端生成的salt、IV和实际密文
  2. 直接对包含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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 14:03:12