博客正文,Markdown格式,承接上一篇MySQL加密文章,聚焦字段级加密的3种实现方式、代码示例、优缺点、生产坑点。
前言
字段级加密(列加密),就是只对表中指定敏感列 做加密,普通字段保持明文。常用于手机号、身份证、银行卡、客户隐私信息。
MySQL 字段加密一共3种主流实现方式:
- MySQL 内置
AES_ENCRYPT/AES_DECRYPT函数 - MySQL 8.0.16+ InnoDB 原生列加密(Column-Level Encryption)
- 应用层加密(生产最推荐):业务代码完成加解密,数据库只存密文
重点提醒:生产环境优先应用层加密,不推荐直接裸用MySQL内置AES函数,下文会说明原因。
一、方式1:MySQL内置AES_ENCRYPT / AES_DECRYPT
MySQL 内置AES,算法为AES-128(可调整),加密返回二进制,字段类型必须用 VARBINARY / BLOB,不能用VARCHAR。
1. 建表语句
sql
CREATE TABLE user_info (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
phone VARBINARY(256), -- 存储手机号密文
id_card VARBINARY(256) -- 身份证密文
);
2. 插入加密数据
sql
INSERT INTO user_info(username, phone, id_card)
VALUES (
'张三',
AES_ENCRYPT('13800138000', 'my-secret-key-123'),
AES_ENCRYPT('500101199001011234', 'my-secret-key-123')
);
3. 查询解密
sql
SELECT
id,
username,
AES_DECRYPT(phone, 'my-secret-key-123') AS phone,
AES_DECRYPT(id_card, 'my-secret-key-123') AS id_card
FROM user_info;
⚠️ 严重缺陷(生产慎用)
- 密钥写在SQL语句中:开启binlog、慢查询日志、通用日志时,SQL会被记录,密钥跟着泄露;
- 无法对密文字段做
LIKE模糊查询; - 密钥管理困难,密钥硬编码在SQL;
- 所有能执行SQL的账号都可以传入密钥解密;
- MySQL5.7 / 8.0 字符集差异容易出现解密乱码。
适用场景:仅本地测试、学习演示,禁止直接上生产。
二、方式2:MySQL 8.0.16+ InnoDB 原生列加密
MySQL 8.0.16 引入的原生列加密,加密密钥由MySQL密钥环(keyring)管理,密钥不会出现在SQL、binlog、慢日志,安全性远高于AES_ENCRYPT。
前置条件
- MySQL ≥ 8.0.16,InnoDB引擎;
- 提前加载密钥环插件(
keyring_file/keyring_aws); - 仅支持InnoDB表。
建表示例
sql
CREATE TABLE user_secret (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50),
phone VARCHAR(20) ENCRYPTED,
id_card VARCHAR(18) ENCRYPTED
) ENGINE=InnoDB;
插入、查询和普通SQL完全一样,自动加解密:
sql
INSERT INTO user_secret(username, phone, id_card)
VALUES ('张三', '13800138000', '500101199001011234');
SELECT * FROM user_secret;
数据库引擎自动完成解密,只有拥有解密权限的用户才能看到明文。
优缺点
✅ 优点
- 密钥不会出现在SQL日志;
- SQL无侵入,业务改动极小;
- 权限控制:可以限制数据库账号是否具备解密权限。
❌ 缺点
- 版本要求高,低版本MySQL无法使用;
- 密钥环组件部署复杂,高可用集群、主从同步要额外配置;
- 加密字段不能索引、不能模糊查询;
- 有权限的DBA仍然可以读取明文;
- 社区版密钥环
keyring_file仅适合测试,企业版才支持KMS对接。
适用场景:云数据库MySQL企业版,对数据库内部访问有权限隔离需求。
三、方式3:应用层加密(生产推荐方案)
应用层加密 = 在Python/Java/Go等业务代码完成AES加解密,数据库只接收密文,密钥全程不经过MySQL 。
MySQL只负责存储二进制密文,完全看不到密钥,binlog里也只有密文。
流程
- 用户提交手机号/身份证 → 应用拿到明文;
- 应用使用AES-GCM(推荐,带完整性校验)加密明文;
- 将密文存入MySQL
VARBINARY; - 查询时:取出密文 → 应用解密 → 返回前端;
- 密钥保存在KMS/密钥管理系统,禁止硬编码在代码配置文件。
Python极简示例(AES-GCM)
python
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
import os
# 密钥从KMS获取,禁止写死代码
key = os.urandom(32)
def encrypt(plain_text: str):
iv = os.urandom(12)
cipher = Cipher(algorithms.AES(key), modes.GCM(iv), backend=default_backend())
encryptor = cipher.encryptor()
ciphertext = encryptor.update(plain_text.encode("utf-8")) + encryptor.finalize()
return iv + ciphertext + encryptor.tag
def decrypt(raw_data):
iv = raw_data[:12]
tag = raw_data[-16:]
ciphertext = raw_data[12:-16]
cipher = Cipher(algorithms.AES(key), modes.GCM(iv), tag, backend=default_backend())
decryptor = cipher.decryptor()
return decryptor.update(ciphertext) + decryptor.finalize()
# 加密后bytes直接存入MySQL VARBINARY字段
cipher_data = encrypt("13800138000")
SQL建表:
sql
CREATE TABLE user_app_enc (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50),
phone VARBINARY(512),
id_card VARBINARY(512),
phone_hash CHAR(64) -- SHA256哈希,用于等值查询
);
等值查询配套方案:
新增 phone_hash 字段,存储手机号SHA256哈希。
查询时前端传入手机号,应用计算哈希,WHERE phone_hash = ? 匹配,不解密全表。
✅ 优点
- 密钥不进入数据库,binlog、慢日志只有密文;
- DBA即使拿到数据库全部数据,没有密钥无法解密;
- 算法自主可控,可以选择AES-GCM、SM4国密;
- 不受MySQL版本限制,5.7 / 8.0都可以使用。
❌ 缺点
- 需要在业务代码开发加解密逻辑;
- 加密字段无法模糊搜索;
- 密钥管理需要自建或者对接KMS。
这是互联网业务最主流方案。
四、字段加密后的检索问题(重点)
无论哪种字段加密,密文本身不能like模糊查询
- 等值查询:新增哈希摘要列 SHA256,仅用于匹配;不可逆,不能解密原文;
- 模糊查询 :
- 方案1:业务层解密全量数据过滤(大数据量性能灾难,慎用)
- 方案2:单独存储脱敏片段(手机号前3后4),用于检索,不存储完整明文
五、三种方案对比
| 方案 | 密钥是否进SQL | DBA能否看到明文 | MySQL版本 | 开发成本 | 推荐度 |
|---|---|---|---|---|---|
| AES_ENCRYPT函数 | 是,日志会记录 | 能 | 5.7+ | 低 | ⭐ 仅测试 |
| InnoDB原生列加密 | 否 | 有权限就能看到 | 8.0.16+ | 中 | ⭐⭐ 企业云库 |
| 应用层加密 | 完全不进数据库 | 无密钥看不到 | 无限制 | 中高 | ⭐⭐⭐⭐⭐ 生产首选 |
六、生产落地最佳实践
- 优先应用层AES-GCM/SM4加密,敏感字段存VARBINARY;
- 数据库连接强制开启SSL传输加密;
- 密钥统一KMS管理,禁止硬编码、禁止提交到代码仓库;
- 增加哈希字段,用于等值查询;
- 数据库账号最小权限;
- 备份文件也要加密,备份导出的密文文件,如果密钥泄露依然无法解密;
- 不要使用MD5做敏感数据加密(MD5不可逆,只能做哈希摘要)。
七、常见踩坑点
- 加密二进制数据存入VARCHAR:会发生字符集转义损坏,解密失败;必须VARBINARY;
- 密钥长度不符合AES规范:AES-128=16字节,AES-256=32字节;
- 只加密数据库,备份mysqldump导出密文但密钥和备份放一起,等于没加密;
- 混淆TDE透明加密和字段加密:TDE是整表空间磁盘加密,DBA查询依然返回明文,不能保护敏感字段防内部人员查看。
总结
MySQL字段级加密有三条路线:内置AES函数适合学习,InnoDB原生列加密适合企业云MySQL;绝大多数业务系统首选应用层字段加密 。
字段加密的核心不是"让MySQL帮你加密",而是控制密钥的流转范围,不让密钥进入数据库链路。