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

Java使用JCE做AES加解密时密文乱码、结果每次不同如何解决

AES加解密密文存储问题修复方案

问题根因

你遇到的两个问题本质是对AES加密输出特性、字符编码规则理解偏差导致的,具体说明:

  • 密文出现怪异乱码:AES加密输出的是无编码规则的随机二进制字节流,不属于任何合法字符集(UTF-8、GBK等)的可打印字符序列。直接调用new String(encryptedBytes)是强行用系统默认字符集转译随机二进制,必然出现不可识别符号,甚至会因为字符集不兼容丢失部分字节,后续再把这个乱码字符串转回字节数组时会损坏密文,导致解密失败。
  • 每次运行生成不同密文:你当前代码每次启动都会调用KeyGenerator.generateKey()随机生成全新的AES密钥,密钥不同密文自然不同;另外如果使用带随机初始化向量(IV)的安全加密模式(如CBC、GCM),即使密钥、明文完全相同,每次加密生成的密文也会不同,这是正常的安全设计,只要解密时拿到正确的密钥、对应IV就可以正常解密,不影响使用。

密文存储选型

密文存储二选一即可,禁止直接把二进制密文强转String存储:

  • 二进制存储:数据库对应字段使用BLOB/VARBINARY/BINARY类型,直接存储加密得到的原始byte[]数组,没有额外编码开销,读写效率最高。
  • 文本存储:如果业务场景必须用VARCHAR/TEXT类文本字段存储,需要先把二进制密文通过Base64或Hex编码转为全可打印的常规字符串,再入库;读取时先把字符串解码回原始byte[]数组,再执行解密操作。

代码修复要点

你当前的实现还有几个兼容性、安全性问题需要修正:

  1. 初始化Cipher时必须显式指定完整的「算法/工作模式/填充方式」,不能只写AES,否则不同JDK版本/厂商会使用不同默认值,加解密结果不兼容。推荐使用安全性更高的AES/CBC/PKCS5Padding或AES/GCM/NoPadding,不要用默认的ECB模式。
  2. 密钥不能每次运行随机生成,需要在首次生成后通过安全介质持久化存储(比如配置中心、密钥管理服务),后续加解密都使用同一把密钥,否则历史密文永远无法解密。
  3. 使用CBC/GCM模式时,每次加密需要生成随机IV,IV不需要保密,直接拼在密文前面/分字段和密文一起存储即可,解密时先从密文中拆分出IV,再初始化解密器。
  4. 字符串和字节数组互转时必须显式指定字符集(推荐UTF-8),避免不同系统默认编码不一致导致的乱码问题。

修正后参考代码

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import java.security.SecureRandom;
import java.util.Base64;

public class AesDemo {
    // 实际使用时从安全配置中读取持久化的密钥,不要每次运行重新生成
    private static SecretKey getPersistentKey() throws Exception {
        // 首次生成后可以把密钥编码后存到安全配置中,这里为示例简化
        KeyGenerator gen = KeyGenerator.getInstance("AES");
        gen.init(128); // AES推荐使用128/256位密钥
        return gen.generateKey();
    }

    // 加密方法,返回Base64编码的密文,可直接存入VARCHAR类型字段
    public static String encrypt(String plainText, SecretKey key) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        // 生成随机IV
        byte[] iv = new byte[cipher.getBlockSize()];
        new SecureRandom().nextBytes(iv);
        IvParameterSpec ivSpec = new IvParameterSpec(iv);
        
        cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
        byte[] encryptedBytes = cipher.doFinal(plainText.getBytes("UTF-8"));
        
        // 把IV和密文拼接后转Base64返回,无需单独存储IV
        byte[] combined = new byte[iv.length + encryptedBytes.length];
        System.arraycopy(iv, 0, combined, 0, iv.length);
        System.arraycopy(encryptedBytes, 0, combined, iv.length, encryptedBytes.length);
        return Base64.getEncoder().encodeToString(combined);
    }

    // 解密方法,传入Base64编码的密文
    public static String decrypt(String cipherTextBase64, SecretKey key) throws Exception {
        byte[] combined = Base64.getDecoder().decode(cipherTextBase64);
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        
        // 拆分出IV和真实密文
        byte[] iv = new byte[cipher.getBlockSize()];
        byte[] encryptedBytes = new byte[combined.length - iv.length];
        System.arraycopy(combined, 0, iv, 0, iv.length);
        System.arraycopy(combined, iv.length, encryptedBytes, 0, encryptedBytes.length);
        
        IvParameterSpec ivSpec = new IvParameterSpec(iv);
        cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
        byte[] plainBytes = cipher.doFinal(encryptedBytes);
        return new String(plainBytes, "UTF-8");
    }

    public static void main(String[] args) throws Exception {
        SecretKey key = getPersistentKey();
        String text1 = "welcome back";
        String text2 = "hello guys";

        String enc1 = encrypt(text1, key);
        String enc2 = encrypt(text2, key);
        // 输出的密文为常规Base64字符,无乱码
        System.out.println(enc1);
        System.out.println(enc2);
        System.out.println(decrypt(enc1, key));
        System.out.println(decrypt(enc2, key));
    }
}

补充说明:如果你的业务场景是存储用户登录密码,不要使用AES这类可逆加密算法,应当使用BCrypt、Argon2等带盐的慢哈希算法,避免密钥泄露后所有用户密码被还原。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 21:45:44