含特殊字符时AES加密解密异常问题排查
特殊字符引发AES解密失败与JSON构造异常的解决方案
问题现象
- 字符串包含💰、ღ、♉、✘、⊗、🆇、♤、⚅、♥、☻、¤、🤡这类特殊字符时,AES加密可正常执行,但解密无法还原原始内容。
- 含上述特殊字符的字段在生成JSON时,会出现引号、逗号缺失等格式错误。
用户提供的AES加密解密代码:
private static final String KEY = "34753668217A25432A462W4A614E6451"; private static final byte[] NULL_BYTES_16 = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; public static String encrypt(final String data) { final byte[] keyBytes = Hex.decode(KEY); String dataCrypto = ""; final SecretKeySpec key = new SecretKeySpec(keyBytes, "AES"); byte[] d = data.getBytes(StandardCharsets.UTF_8); final int blockSize = 16; d = padding(data.getBytes(), blockSize); dataCrypto = new String(Hex.encode(encrypt(key, d, NULL_BYTES_16, "AES/CBC/NoPadding"))); return dataCrypto; } public static String decrypt(final String data) { final byte[] keyBytes = Hex.decode(KEY); String dataDecrypto = ""; final SecretKeySpec key = new SecretKeySpec(keyBytes, "AES"); final byte[] d = Hex.decode(data); dataDecrypto = new String(decrypt(key, d, NULL_BYTES_16, "AES/CBC/NoPadding")); return dataDecrypto; } private static byte[] encrypt(final Key key, final byte[] text, final byte[] iv, final String encryptionAlgorithmValue) { try { final Provider provider = Security.getProvider("SunJCE"); final Cipher cipher = Cipher.getInstance(encryptionAlgorithmValue, provider); cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv)); final byte[] res = cipher.doFinal(text); return res; } catch (final Exception e) { throw new RuntimeException("MAC computation failed.", e); } } private static byte[] decrypt(final Key key, final byte[] text, final byte[] iv, final String encryptionAlgorithmValue) { try { final Provider provider = Security.getProvider("SunJCE"); final Cipher cipher = Cipher.getInstance(encryptionAlgorithmValue, provider); cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv)); final byte[] res = cipher.doFinal(text); return res; } catch (final Exception e) { throw new RuntimeException("MAC computation failed.", e); } } @SuppressWarnings("all") private static byte[] padding(byte[] data, final Integer blockSize) { // Blocksize de 16 para AES e de 8 para TDES data = concat(data, new byte[] { (byte) 0x80 }); if (data.length % blockSize == 0) { return data; } final byte[] paddedData = Arrays.copyOf(data, data.length + blockSize - data.length % blockSize); return paddedData; }
待加密JSON数据:
{ "requestId": "waldir_104-010102993851", "issuerId": "22", "tokenRequestorId": "12300000001", "schemeId": "Elo", "panHash": "AEF2397762DC8226FD4786E05A1392D47FA621345C3E4BE674B61400C23F5609", "cardEncryptionInfo": { "cardSensitiveData": "3772D0FCCD1CE987872F7B7D41F2F3CF1C4C9F0A9F1A6F5C2C6C6B7B05DDEBDA13CAE351234C4DD69C317D278219D1403D1B350673D73E62D9D5418167BC7AF1157B928AC1780FCE406AA2D9C815E35B2A844F0D9EAD82C9EEECC9C315C688CD675A7D1D6CFF7FA51EBB63A7C86DDB137C7F66FEDA55AF969158D90023FFB0BF", "cardSensitiveDataFormat": "CardData", "encryptionReference": "BRADESCO.TRANS.02", "encryptionIV": "6071656686361687" }, "accountRiskData": { "clientType": "76", "accountScore": 1, "accountScoringAlgorithm": "10", "accountScoringAlgoVersion": "10", "accountCreationPeriod": 0, "cpf": "1234567890111", "userEmail": "user@domain.com.br", "address": "TESTEChacára Praça Estação Área Colônia, 1200 - Núcleo '''''' 💰 ღ♉✘⊗🆇♤⚅♥☻�¤🤡", "zip": "06455-914", "accountId": "fc440120-6f0b-11ea-bdb8-398790b53374", "simSwapTimePeriodIndication": "A" }, "deviceRiskData": { "phoneNumberScore": "5", "phoneNumber": "+55119876543210", "fullDeviceNumber": "119123412342", "color": "green2", "deviceScore": 1, "deviceScoringAlgorithm": "10", "deviceScoringAlgoVersion": "10", "deviceCountry": "BR", "deviceID": "1234567891", "ipv4": "10.82.2.222", "manufacturer": "Apple", "brand": "Apple", "model": "A1549", "name":"Paiva du corte ߒ蜢,", "networkOperator": "testeMaia", "networkType": "GSM", "osType": "Android", "osVersion": "10", "timeZoneManager": "CARRIER", "deviceType": "SMARTPHONE" }, "walletRiskData": { "recommendation": "aprovar2", "walletId": "1fc3f7a0-3176-11ea-b2e4-15074b0e5031", "cardTenurePeriod": 0, "walletCreationPeriod": 0, "walletCardholderNameMatches": true, "tokensOnDevice": 1, "cardInputMethod": "teste", "flowIndication": "GREEN", "flowAlgorithm": "10", "flowAlgorithmVersion": "10" } }
问题根源分析
1. AES解密失败的原因
- 编码不一致:加密时先通过
data.getBytes(StandardCharsets.UTF_8)转字节数组,但调用padding方法时又用了data.getBytes()(未指定编码,会使用系统默认编码),若系统默认编码不是UTF-8,会导致加密的字节数组和原始字符串不匹配,解密后无法还原。 - 自定义填充无对应去填充逻辑:手动实现的
padding方法添加了0x80及后续空字节,但解密时未移除这些填充字节,特殊字符的多字节编码会和填充字节混淆,破坏字符串结构,导致解析错误。 - 密钥非法:密钥中的
W不是合法十六进制字符(十六进制仅支持0-9、A-F/a-f),Hex解码时会出错,实际使用的密钥不符合预期,导致加密解密不匹配。
2. JSON构造异常的原因
- 特殊字符中包含JSON不兼容的非法字符(比如示例中的
�是UTF-8编码错误占位符),手动拼接JSON时这类字符会破坏语法结构;部分特殊字符未正确转义,也会导致格式错误。
修复方案
一、修复AES加密解密逻辑
- 统一字符编码:确保所有字符串转字节数组操作都使用
StandardCharsets.UTF_8,修改encrypt方法:
byte[] d = data.getBytes(StandardCharsets.UTF_8); d = padding(d, blockSize); // 替换原padding(data.getBytes(), blockSize)
- 修复密钥合法性:将密钥中的
W替换为合法十六进制字符(如4,需确认实际密钥):
private static final String KEY = "34753668217A25432A46244A614E6451";
- 添加去填充逻辑:解密后移除填充字节,修改
decrypt方法:
public static String decrypt(final String data) { final byte[] keyBytes = Hex.decode(KEY); final SecretKeySpec key = new SecretKeySpec(keyBytes, "AES"); final byte[] d = Hex.decode(data); byte[] decryptedBytes = decrypt(key, d, NULL_BYTES_16, "AES/CBC/NoPadding"); // 找到第一个0x80的位置,截断填充内容 int paddingIndex = -1; for (int i = decryptedBytes.length - 1; i >= 0; i--) { if (decryptedBytes[i] == (byte) 0x80) { paddingIndex = i; break; } } if (paddingIndex != -1) { decryptedBytes = Arrays.copyOf(decryptedBytes, paddingIndex); } return new String(decryptedBytes, StandardCharsets.UTF_8); }
- 改用标准填充模式(推荐):移除自定义填充,使用AES内置的
PKCS5Padding,简化代码并避免自定义错误:
// 修改加密方法 public static String encrypt(final String data) { final byte[] keyBytes = Hex.decode(KEY); final SecretKeySpec key = new SecretKeySpec(keyBytes, "AES"); byte[] d = data.getBytes(StandardCharsets.UTF_8); byte[] encryptedBytes = encrypt(key, d, NULL_BYTES_16, "AES/CBC/PKCS5Padding"); return new String(Hex.encode(encryptedBytes)); } // 修改解密方法 public static String decrypt(final String data) { final byte[] keyBytes = Hex.decode(KEY); final SecretKeySpec key = new SecretKeySpec(keyBytes, "AES"); final byte[] d = Hex.decode(data); byte[] decryptedBytes = decrypt(key, d, NULL_BYTES_16, "AES/CBC/PKCS5Padding"); return new String(decryptedBytes, StandardCharsets.UTF_8); } // 移除自定义的padding方法
二、修复JSON构造异常
- 使用标准JSON库生成JSON:用Jackson、Gson等序列化库自动处理特殊字符,确保格式合法,示例用Jackson:
ObjectMapper mapper = new ObjectMapper(); String json = mapper.writeValueAsString(yourDataObject);
- 清理非法字符:预处理输入字符串,移除或替换
�这类编码错误的占位符,确保输入是合法UTF-8编码。
内容的提问来源于stack exchange,提问作者wepdev
相关产品推荐
相关产品推荐

