Skip to content

AES 加密工具

AesUtil 提供 AES 对称加密功能。默认使用 AES-GCM(认证加密),可同时保证机密性与完整性,防止密文被篡改。

类信息

  • 包名: com.molandev.core.util.encrypt
  • 类名: AesUtil
  • 类型: 静态工具类

使用场景

  • ✅ 敏感数据加密存储(如身份证号、手机号)
  • ✅ 数据传输加密
  • ✅ 配置文件加密
  • ✅ Token 加密
  • ✅ 需要高性能加密场景

为何默认 GCM

GCM 是认证加密(AEAD):解密时会校验 128 bit Tag,密文被篡改会直接失败。相比 ECB/CBC 仅加密,更适合传输与存储场景。

基础使用

简单加密解密

java
import com.molandev.core.util.encrypt.AesUtil;

public class AesExample {
    public static void main(String[] args) {
        String password = "mySecretKey";
        String content = "敏感数据";

        // 加密(默认 AES/GCM/NoPadding)
        String encrypted = AesUtil.encrypt(content, password);
        System.out.println("加密后: " + encrypted);
        // 输出: Base64(IV12 || ciphertext||tag)

        // 解密
        String decrypted = AesUtil.decrypt(encrypted, password);
        System.out.println("解密后: " + decrypted);
        // 输出: 敏感数据
    }
}

核心方法

encrypt

加密字符串内容(默认 GCM)。

java
public static String encrypt(String content, String password)

参数

参数名类型必填说明
contentString待加密的明文内容
passwordString密码/口令(任意非空字符串,内部会派生为 AES-128 密钥)

返回值String - Base64 编码的密文(含前置 IV)

异常

  • IllegalArgumentException - 密钥为空或加密失败

示例

java
String encrypted = AesUtil.encrypt("Hello World", "key123");

decrypt

解密字符串内容(默认 GCM)。

java
public static String decrypt(String content, String password)

参数

参数名类型必填说明
contentString待解密的密文(Base64,格式见下文)
passwordString解密口令(必须与加密时一致)

返回值String - 解密后的明文

异常

  • IllegalArgumentException - 密钥错误、密文格式错误或 Tag 校验失败

示例

java
String decrypted = AesUtil.decrypt(encrypted, "key123");

encrypt / decrypt(自定义算法)

java
public static String encrypt(String content, String password, String aesCipherAlgorithm)
public static String decrypt(String content, String password, String aesCipherAlgorithm)
参数名类型必填说明
aesCipherAlgorithmStringCipher 算法名;含 GCM(忽略大小写)走 GCM 路径,否则走无 IV 的旧 Cipher 路径

示例(推荐,与默认一致)

java
String encrypted = AesUtil.encrypt("data", "key", "AES/GCM/NoPadding");
String decrypted = AesUtil.decrypt(encrypted, "key", "AES/GCM/NoPadding");

示例(非默认,兼容旧路径)

java
// 算法名不含 GCM 时:无随机 IV,直接 Cipher.init(ENCRYPT_MODE, key)
// 一般不推荐新业务使用
String encrypted = AesUtil.encrypt("data", "key", "AES/ECB/PKCS5Padding");

完整示例

示例 1:敏感字段加解密

java
import com.molandev.core.util.encrypt.AesUtil;

public class FieldEncryption {

    private static final String AES_PASSWORD = "MyApp2024Secret!";

    public String encryptField(String raw) {
        return AesUtil.encrypt(raw, AES_PASSWORD);
    }

    public String decryptField(String encrypted) {
        return AesUtil.decrypt(encrypted, AES_PASSWORD);
    }
}

注意

登录密码哈希请使用 PasswordEncoder(BCrypt),不要用 AES 可逆加密存密码。

示例 2:配置文件敏感信息加密

java
import com.molandev.core.util.encrypt.AesUtil;

public class ConfigEncryption {

    private static final String KEY = "ConfigKey123456";

    public static String encryptConfig(String value) {
        return "ENC(" + AesUtil.encrypt(value, KEY) + ")";
    }

    public static String decryptConfig(String encryptedValue) {
        if (encryptedValue.startsWith("ENC(") && encryptedValue.endsWith(")")) {
            String encrypted = encryptedValue.substring(4, encryptedValue.length() - 1);
            return AesUtil.decrypt(encrypted, KEY);
        }
        return encryptedValue;
    }
}

示例 3:数据传输加密

java
import com.molandev.core.util.encrypt.AesUtil;
import com.molandev.core.util.DateUtils;

public class DataTransfer {

    private static final String TRANSFER_KEY = "TransferKey@2024";

    public static String encryptData(String data) {
        String timestamp = DateUtils.now();
        String content = data + "|" + timestamp;
        return AesUtil.encrypt(content, TRANSFER_KEY);
    }

    public static String decryptData(String encrypted) {
        String decrypted = AesUtil.decrypt(encrypted, TRANSFER_KEY);
        String[] parts = decrypted.split("\\|");
        if (parts.length == 2) {
            return parts[0];
        }
        throw new IllegalArgumentException("数据格式错误");
    }
}

技术细节

默认加密算法

说明
默认算法AES/GCM/NoPadding
GCM IV12 字节随机,前置拼到密文前
GCM Tag128 bit(由 Cipher 输出附在 ciphertext 后)
输出格式Base64(IV || ciphertext||tag)
明文编码UTF-8
AES 密钥长度128 bit(16 字节)

密钥派生

passwordSHA-256,取摘要的前 16 字节作为 AES-128 密钥:

text
AES_KEY = SHA-256(UTF-8(password))[0..15]
  • 口令可为任意非空字符串,不再做「不足 16 右侧补 0 / 超过 16 抛异常」
  • 空口令仍抛 IllegalArgumentException
java
// 任意长度口令均可(非空)
AesUtil.encrypt("data", "short");
AesUtil.encrypt("data", "thisKeyIsLongerThan16Characters");

自定义算法分支

  • 算法名包含 GCM(不区分大小写)→ GCM 路径:随机 IV + Tag,输出 Base64(IV\|\|cipher+tag)
  • 否则 → 旧路径:无 IV,Cipher.init(mode, key),输出 Base64(ciphertext)

前端对齐(Web Crypto)

与后端默认 GCM 对接时,密钥派生与密文格式必须一致:

javascript
/**
 * 与 AesUtil 对齐:
 * - 密钥:SHA-256(password) 前 16 字节 → AES-128
 * - 算法:AES-GCM,IV 12 字节前置,Tag 128 bit
 * - 输出:Base64(IV || ciphertext||tag)
 */
async function deriveAesKey(password) {
  const hash = await crypto.subtle.digest(
    'SHA-256',
    new TextEncoder().encode(password)
  );
  const keyBytes = new Uint8Array(hash).slice(0, 16);
  return crypto.subtle.importKey(
    'raw', keyBytes, { name: 'AES-GCM' }, false, ['encrypt', 'decrypt']
  );
}

function bytesToBase64(bytes) {
  let s = '';
  bytes.forEach((b) => { s += String.fromCharCode(b); });
  return btoa(s);
}

function base64ToBytes(b64) {
  const bin = atob(b64);
  const out = new Uint8Array(bin.length);
  for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
  return out;
}

async function aesGcmEncrypt(plaintext, password) {
  const key = await deriveAesKey(password);
  const iv = crypto.getRandomValues(new Uint8Array(12));
  const cipherBuf = await crypto.subtle.encrypt(
    { name: 'AES-GCM', iv, tagLength: 128 },
    key,
    new TextEncoder().encode(plaintext)
  );
  const combined = new Uint8Array(12 + cipherBuf.byteLength);
  combined.set(iv, 0);
  combined.set(new Uint8Array(cipherBuf), 12);
  return bytesToBase64(combined);
}

async function aesGcmDecrypt(base64Cipher, password) {
  const key = await deriveAesKey(password);
  const data = base64ToBytes(base64Cipher);
  const iv = data.slice(0, 12);
  const cipher = data.slice(12);
  const plainBuf = await crypto.subtle.decrypt(
    { name: 'AES-GCM', iv, tagLength: 128 },
    key,
    cipher
  );
  return new TextDecoder().decode(plainBuf);
}

注意事项

⚠️ 密钥管理

java
// ❌ 不要在代码中硬编码密钥
public static final String KEY = "mykey123";

// ✅ 推荐:从配置文件或环境变量读取
String key = System.getenv("AES_KEY");

⚠️ 加解密一致性

java
// ❌ 口令不一致会解密失败(GCM 下 Tag 校验失败)
String encrypted = AesUtil.encrypt("data", "key1");
AesUtil.decrypt(encrypted, "key2");

// ✅ 使用相同口令
String decrypted = AesUtil.decrypt(encrypted, "key1");

⚠️ 异常处理

java
try {
    String encrypted = AesUtil.encrypt("data", "key");
} catch (IllegalArgumentException e) {
    logger.error("加密失败", e);
}

性能说明

  • 加密速度: 约 100MB/s(取决于硬件)
  • 内存占用: 低,无状态设计
  • 线程安全: 是(每次调用独立 Cipher / IV)

安全建议

  1. 定期更换密钥: 建议定期更换加密口令
  2. 密钥存储: 口令/密钥应存储在安全处,不要提交到版本控制
  3. 使用强口令: 口令应足够长且不可预测(派生后仍为 AES-128)
  4. 优先默认 GCM: 新业务不要再选用 ECB

常见问题

Q: AES 和 RSA 如何选择?

A:

  • AES: 对称加密,性能高,适合大量数据加密
  • RSA: 非对称加密,适合密钥交换和小数据加密(如登录密码参数)

Q: 口令长度有限制吗?

A: 没有 16 字符上限。任意非空口令都会经 SHA-256 派生为 16 字节 AES-128 密钥。

Q: 为什么每次加密同一明文结果不同?

A: 默认 GCM 每次使用随机 12 字节 IV,密文会变化;解密时从密文前 12 字节取出 IV,结果仍正确。这是正常且期望的行为。

Q: 加密后的数据可以存储到数据库吗?

A: 可以。结果是 Base64 字符串;GCM 相对 ECB 会多约 12 字节 IV + 16 字节 Tag,字段长度请预留余量。

相关工具