AES CBC加解密:Angular与Java填充模式不兼容问题求助
Angular与Java AES-CBC加密解密兼容问题
问题背景
Angular端使用CryptoJS实现AES-CBC加密,采用PKCS7Padding填充模式,将密钥和密文传输至Java后端解密时遇到以下问题:
- Java端使用
"AES/CBC/PKCS7Padding"实例化Cipher时报错 - 改用
"AES/CBC/PKCS5Padding"后Java可正常运行,但Angular端切换为PKCS5Padding填充时会报错
原始代码
Angular端加密代码
import { Injectable } from '@angular/core'; import * as CryptoJS from 'crypto-js'; @Injectable({ providedIn: 'root', }) export class CryptoService { encrypt(message: string, clef: string): string { const salt = CryptoJS.SHA256("123456789123"); const key = CryptoJS.PBKDF2(clef, salt, { keySize: 128 / 32, iterations: 1000 }); let iv = CryptoJS.enc.Utf8.parse(clef); let encrypted = CryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse(message.toString()), key, { keySize: 128 / 8, iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }); return encrypted.toString(); } generateKey(): string { let result = ''; const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; const charactersLength = characters.length; let counter = 0; while (counter < 16) { result += characters.charAt(Math.floor(Math.random() * charactersLength)); counter += 1; } return result; } }
Java端解密代码
import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; import java.security.spec.KeySpec; import java.util.Base64; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEKeySpec; public class AESUtilService { protected static final String salt = "123456789123"; public SecretKey getKeyFromPassword(String password) throws NoSuchAlgorithmException, InvalidKeySpecException { SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); KeySpec spec = new PBEKeySpec(password.toCharArray(), salt.getBytes(), 1000, 128/32); SecretKey secret = factory.generateSecret(spec); return secret; } public String decrypt(String cipherText, SecretKey key) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException { String algorithm = "AES/CBC/PKCS7Padding"; IvParameterSpec iv = new IvParameterSpec("MnTQLHcWumIKTXpQ".getBytes()); Cipher cipher = Cipher.getInstance(algorithm); cipher.init(Cipher.DECRYPT_MODE, key, iv); byte[] plainText = cipher.doFinal(Base64.getDecoder().decode(cipherText)); return new String(plainText); } }
兼容解决方案
1. 填充模式兼容逻辑
对于AES算法(块大小固定为16字节),PKCS5Padding与PKCS7Padding实际效果完全等价:
- PKCS5Padding仅针对8字节块(如DES)定义,Java标准库中当块大小为16字节时,
PKCS5Padding的实现会自动适配PKCS7的填充逻辑 - Angular端必须保留
CryptoJS.pad.Pkcs7(CryptoJS的Pkcs5Padding仅支持8字节块,用于AES会报错) - Java端改用
"AES/CBC/PKCS5Padding"即可与Angular的PKCS7Padding兼容
2. 修正密钥生成的一致性问题
原始代码中PBKDF2密钥生成存在两处不一致:
- 盐值处理:Angular对盐字符串做了SHA256哈希,Java端需同步该逻辑
- 密钥长度参数:Java的
PBEKeySpec密钥长度单位是位,应设为128,而非128/32(错误值4)
3. 统一IV生成逻辑
Angular使用密码(clef)的UTF8字节作为IV,Java端不能硬编码IV,必须使用相同的密码生成IV(注意密码长度需为16字节,与AES块大小一致)
修正后的Java代码
import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.MessageDigest; import java.security.spec.InvalidKeySpecException; import java.security.spec.KeySpec; import java.util.Base64; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEKeySpec; public class AESUtilService { protected static final String SALT_STR = "123456789123"; public SecretKey getKeyFromPassword(String password) throws NoSuchAlgorithmException, InvalidKeySpecException { // 同步Angular的盐处理逻辑:对盐字符串做SHA256哈希 MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); byte[] salt = sha256.digest(SALT_STR.getBytes()); SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); // 密钥长度设为128位,与Angular一致 KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 1000, 128); SecretKey secret = factory.generateSecret(spec); return secret; } public String decrypt(String cipherText, SecretKey key, String password) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException { // 使用与Angular一致的IV:密码的UTF8字节(密码长度需为16字节) IvParameterSpec iv = new IvParameterSpec(password.getBytes()); // 使用PKCS5Padding与Angular的PKCS7Padding兼容 String algorithm = "AES/CBC/PKCS5Padding"; Cipher cipher = Cipher.getInstance(algorithm); cipher.init(Cipher.DECRYPT_MODE, key, iv); byte[] plainText = cipher.doFinal(Base64.getDecoder().decode(cipherText)); return new String(plainText); } }
注意事项
- 确保密码(clef)长度为16字节:因为AES的IV长度必须等于块大小(16字节),若密码长度不符,建议随机生成IV并与密文一起传输(如拼接在密文前),避免安全风险
- 统一字符编码:两端均使用UTF-8处理字符串与字节数组的转换
- 避免用密码直接作为IV:随机生成IV是更安全的实践,可将IV和密文打包后传输
内容的提问来源于stack exchange,提问作者Antoine Dargouge
相关产品推荐
相关产品推荐

