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

含特殊字符时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加密解密逻辑

  1. 统一字符编码:确保所有字符串转字节数组操作都使用StandardCharsets.UTF_8,修改encrypt方法:
byte[] d = data.getBytes(StandardCharsets.UTF_8);
d = padding(d, blockSize); // 替换原padding(data.getBytes(), blockSize)
  1. 修复密钥合法性:将密钥中的W替换为合法十六进制字符(如4,需确认实际密钥):
private static final String KEY = "34753668217A25432A46244A614E6451";
  1. 添加去填充逻辑:解密后移除填充字节,修改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);
}
  1. 改用标准填充模式(推荐):移除自定义填充,使用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构造异常

  1. 使用标准JSON库生成JSON:用Jackson、Gson等序列化库自动处理特殊字符,确保格式合法,示例用Jackson:
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(yourDataObject);
  1. 清理非法字符:预处理输入字符串,移除或替换�这类编码错误的占位符,确保输入是合法UTF-8编码。

内容的提问来源于stack exchange,提问作者wepdev

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 08:49:56