Java与MySQL AES加密解密实战指南
Java 与 MySQL 中的 AES 加密解密实践
本指南提供了两种在 MySQL 数据库中处理敏感数据 AES 加密的核心方法:
- 数据库层:直接在 MySQL 中使用原生 SQL 函数进行加解密。
- 应用层:在 Java 应用程序中完成加解密,数据库仅负责存储。
一、MySQL 原生 AES 加解密 (SQL 实现)
这种方法利用 MySQL 内置的 AES_ENCRYPT()
和 AES_DECRYPT()
函数,直接在数据库层面完成加解密操作。我们使用 dual
虚表进行演示,它不需要实际创建表。
核心函数
AES_ENCRYPT(str, key_str)
: 使用密钥key_str
加密字符串str
,返回二进制结果。AES_DECRYPT(crypt_str, key_str)
: 使用密钥key_str
解密二进制密文crypt_str
。HEX(binary_str)
: 将二进制字符串转换为十六进制格式,方便存储和查看。UNHEX(hex_str)
:HEX()
的逆操作,将十六进制字符串转回二进制。
SQL 操作示例
-- 1. 加密: 将字符串 'data' 用密钥 'myKey' 加密,并转换为十六进制字符串
SELECT HEX(AES_ENCRYPT('data', 'myKey')) FROM dual;-- 2. 解密: 直接解密 AES_ENCRYPT 的结果。
-- 注意:AES_DECRYPT 返回的是二进制,需要 CAST 转换为可读字符。
SELECT CAST(AES_DECRYPT(AES_ENCRYPT('data', 'myKey'), 'myKey') AS CHAR) FROM dual;-- 3. 解密存储的十六进制字符串:
-- 假设 '4E4632...' 是从数据库字段中读取的加密后的十六进制值
-- 需要先用 UNHEX 转回二进制,再进行解密。
-- SELECT CAST(AES_DECRYPT(UNHEX('your_encrypted_hex_string'), 'myKey') AS CHAR) FROM dual;
二、Java 中实现
方式一:在 Java 中调用 SQL 加解密函数
这种方式下,Java 代码负责构建和执行带有 AES_ENCRYPT
/AES_DECRYPT
函数的 SQL 语句,将加解密工作委托给 MySQL 数据库处理。
- 加密:Java 程序执行
SELECT HEX(AES_ENCRYPT(?, ?))
,将明文和密钥作为参数传入,从数据库获取加密后的十六进制字符串。 - 解密:Java 程序执行
SELECT CAST(AES_DECRYPT(UNHEX(?), ?) AS CHAR)
,将密文和密钥作为参数传入,从数据库获取解密后的明文。
方式二:使用 Java 工具类进行加解密
这种方式将加解密逻辑完全封装在 Java 应用中,与数据库解耦。数据库仅作为加密字符串的存储介质。
- 加密:在 Java 代码中调用工具类方法(如
MySqlAesUtils.encrypt
)将明文加密成 Base64 或十六进制字符串,然后通过标准的INSERT
/UPDATE
语句存入数据库。 - 解密:先从数据库查出加密字符串,然后在 Java 代码中调用工具类方法(如
MySqlAesUtils.decrypt
)将其解密为明文。
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.Key;public class MySqlAesUtils {private static final String ALGORITHM = "AES";private static final String TRANSFORMATION = "AES/ECB/PKCS5Padding";private static Key secretKey;/*** 加密,对应 MySQL 的 SELECT hex(AES_ENCRYPT(data, key))** @param data 待加密的数据* @return 加密后的十六进制字符串*/public static String encrypt(String data) {if (data == null || data.isEmpty()) {return data;}try {Cipher cipher = Cipher.getInstance(TRANSFORMATION);cipher.init(Cipher.ENCRYPT_MODE, secretKey);byte[] encryptedBytes = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));return bytesToHex(encryptedBytes);} catch (Exception e) {// 在实际项目中,这里应该记录日志并抛出自定义异常throw new RuntimeException("AES 加密失败", e);}}/*** 解密,对应 MySQL 的 SELECT AES_DECRYPT(UNHEX(data), key)** @param hexData 十六进制表示的加密数据* @return 解密后的原始字符串*/public static String decrypt(String hexData) {if (hexData == null || hexData.isEmpty()) {return hexData;}try {Cipher cipher = Cipher.getInstance(TRANSFORMATION);cipher.init(Cipher.DECRYPT_MODE, secretKey);byte[] decryptedBytes = cipher.doFinal(hexToBytes(hexData));return new String(decryptedBytes, StandardCharsets.UTF_8);} catch (Exception e) {// 在实际项目中,这里应该记录日志并抛出自定义异常throw new RuntimeException("AES 解密失败", e);}}/*** 生成与 MySQL 兼容的 AES 密钥。*/private static Key generateMySQLKey(String key) {final byte[] keyBytes = new byte[16];final byte[] providedKeyBytes = key.getBytes(StandardCharsets.UTF_8);System.arraycopy(providedKeyBytes, 0, keyBytes, 0, Math.min(providedKeyBytes.length, keyBytes.length));if (providedKeyBytes.length > 16) {for (int i = 16; i < providedKeyBytes.length; i++) {keyBytes[i % 16] ^= providedKeyBytes[i];}}return new SecretKeySpec(keyBytes, ALGORITHM);}/*** 字节数组转十六进制字符串*/private static String bytesToHex(byte[] bytes) {StringBuilder sb = new StringBuilder();for (byte b : bytes) {sb.append(String.format("%02X", b));}return sb.toString();}/*** 十六进制字符串转字节数组*/private static byte[] hexToBytes(String hex) {int len = hex.length();byte[] data = new byte[len / 2];for (int i = 0; i < len; i += 2) {data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4)+ Character.digit(hex.charAt(i + 1), 16));}return data;}public static void main(String[] args) {// 设置密钥secretKey = generateMySQLKey("mySecretKey");// 测试数据String originalData = "Hello, World!";System.out.println("原始数据: " + originalData);// 加密String encryptedData = encrypt(originalData);System.out.println("加密后数据: " + encryptedData);// 解密String decryptedData = decrypt(encryptedData);System.out.println("解密后数据: " + decryptedData);// 验证if (originalData.equals(decryptedData)) {System.out.println("测试通过:原始数据与解密后数据一致。");} else {System.out.println("测试失败:原始数据与解密后数据不一致。");}}
}
三、关键注意事项:密钥管理
无论使用哪种方法,密钥的安全都是最重要的。
绝对不要将密钥硬编码在代码或 SQL 中!
推荐的密钥管理方式
- 配置文件: 将密钥存储在项目外部的配置文件中(如
.properties
,.yml
)。 - 环境变量: 将密钥存储在服务器的环境变量中,程序启动时读取。
- 密钥管理服务 (KMS): 使用云服务商提供的专业密钥管理服务。