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

使用javax.crypto加密AES令牌通过HTTP头传递时遇填充异常的解决

解决Java 1.7 AES加密令牌传递的BadPaddingException问题

嘿,我一眼就瞅出你这个问题的症结了——你在接收端处理加密数据时,犯了个二进制数据处理的典型错误:把加密后的随机二进制字节转成普通String再转回字节数组。加密后的字节是完全无规则的二进制流,根本不符合UTF-8这类字符编码的规则,当你用new String(Base64.getDecoder().decode(header))的时候,JVM会把那些识别不了的字节直接替换成默认的占位符(就是你看到的字节63,对应字符?),这直接把原始加密数据给改坏了,解密时自然过不了填充校验,报BadPaddingException。

正确的处理流程(完全兼容Java 1.7)

Java 1.7还没内置java.util.Base64,所以咱们用JDK自带的javax.xml.bind.DatatypeConverter来处理Base64编解码(这个类在1.7里肯定能用),全程记住一个原则:只有Base64编码后的字符串是用来传输的文本,加密后的二进制数据绝对不能转成普通String。

发送端:加密生成可传输的令牌

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

public class TokenSender {
    public static void main(String[] args) throws Exception {
        String apiKeyHex = "02E30E6BE24BF1EA"; // 你的十六进制格式密钥
        String applicationId = "your-app-id-123"; // 要识别的请求方ID

        // 重点:把十六进制密钥转成字节数组,而不是直接getBytes()
        byte[] rawKeyBytes = DatatypeConverter.parseHexBinary(apiKeyHex);
        // AES要求密钥长度是16/24/32字节,这里把8字节的密钥补成16字节(AES-128)
        byte[] aesKeyBytes = new byte[16];
        System.arraycopy(rawKeyBytes, 0, aesKeyBytes, 0, rawKeyBytes.length);

        SecretKeySpec aesKey = new SecretKeySpec(aesKeyBytes, "AES");
        // 明确指定模式和填充,别依赖JVM默认值,避免跨环境坑
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, aesKey);

        // 加密applicationId,指定UTF-8编码避免乱码
        byte[] encryptedData = cipher.doFinal(applicationId.getBytes("UTF-8"));

        // 转成Base64字符串,这才是能安全放到HTTP头里的内容
        String token = DatatypeConverter.printBase64Binary(encryptedData);
        System.out.println("生成的授权令牌:" + token);
    }
}

接收端:解析令牌并解密

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

public class TokenReceiver {
    public static void main(String[] args) throws Exception {
        String apiKeyHex = "02E30E6BE24BF1EA";
        String receivedToken = "从HTTP请求头里拿到的Base64字符串";

        // 和发送端一样处理密钥
        byte[] rawKeyBytes = DatatypeConverter.parseHexBinary(apiKeyHex);
        byte[] aesKeyBytes = new byte[16];
        System.arraycopy(rawKeyBytes, 0, aesKeyBytes, 0, rawKeyBytes.length);
        SecretKeySpec aesKey = new SecretKeySpec(aesKeyBytes, "AES");

        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, aesKey);

        // 直接把Base64字符串转成字节数组,绝对不要转成String!
        byte[] encryptedData = DatatypeConverter.parseBase64Binary(receivedToken);

        // 解密后转成原始的applicationId,同样指定UTF-8
        byte[] decryptedBytes = cipher.doFinal(encryptedData);
        String applicationId = new String(decryptedBytes, "UTF-8");
        System.out.println("解密得到的请求方ID:" + applicationId);
    }
}

几个必须纠正的关键点

  • 别碰二进制转String的操作:加密后的字节是纯二进制,不是文本,只有Base64编码后才是合法的可传输文本,直接在字节数组和Base64字符串之间转换就好,跳过中间的String转字节步骤。
  • 密钥要正确转换:你的API密钥是十六进制格式的,不能直接用getBytes()拿字节,得用parseHexBinary()把十六进制字符串转成真实的字节数组,不然密钥完全不对,解密肯定失败。
  • 明确AES的模式和填充:原来的Cipher.getInstance("AES")会用JVM默认的模式和填充,不同环境可能不一样,显式写AES/ECB/PKCS5Padding能避免兼容性问题。
  • AES密钥长度合规:AES只认16、24、32字节的密钥,你的原始密钥是8字节,所以得补成16字节(示例里用0填充),要是追求更安全,可以用PBKDF2这类密钥派生函数生成合规长度的密钥。

额外的安全小提醒

  • ECB模式别长期用:ECB模式是确定性的,相同明文会生成相同密文,很容易被破解。要是能改,建议换成CBC或者GCM模式(需要加IV向量,增加随机性),Java 1.7完全支持这些模式。
  • 密钥别硬编码:把密钥写在代码里太危险了,最好从环境变量或者加密配置文件里读取。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 07:02:50