如何在MySQL中实现字段级加密

博客正文,Markdown格式,承接上一篇MySQL加密文章,聚焦字段级加密的3种实现方式、代码示例、优缺点、生产坑点。

前言

字段级加密(列加密),就是只对表中指定敏感列 做加密,普通字段保持明文。常用于手机号、身份证、银行卡、客户隐私信息。

MySQL 字段加密一共3种主流实现方式:

  1. MySQL 内置 AES_ENCRYPT / AES_DECRYPT 函数
  2. MySQL 8.0.16+ InnoDB 原生列加密(Column-Level Encryption)
  3. 应用层加密(生产最推荐):业务代码完成加解密,数据库只存密文

重点提醒:生产环境优先应用层加密,不推荐直接裸用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;

⚠️ 严重缺陷(生产慎用)

  1. 密钥写在SQL语句中:开启binlog、慢查询日志、通用日志时,SQL会被记录,密钥跟着泄露;
  2. 无法对密文字段做 LIKE 模糊查询;
  3. 密钥管理困难,密钥硬编码在SQL;
  4. 所有能执行SQL的账号都可以传入密钥解密;
  5. MySQL5.7 / 8.0 字符集差异容易出现解密乱码。

适用场景:仅本地测试、学习演示,禁止直接上生产。

二、方式2:MySQL 8.0.16+ InnoDB 原生列加密

MySQL 8.0.16 引入的原生列加密,加密密钥由MySQL密钥环(keyring)管理,密钥不会出现在SQL、binlog、慢日志,安全性远高于AES_ENCRYPT。

前置条件

  1. MySQL ≥ 8.0.16,InnoDB引擎;
  2. 提前加载密钥环插件(keyring_file / keyring_aws);
  3. 仅支持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里也只有密文。

流程

  1. 用户提交手机号/身份证 → 应用拿到明文;
  2. 应用使用AES-GCM(推荐,带完整性校验)加密明文;
  3. 将密文存入MySQL VARBINARY;
  4. 查询时:取出密文 → 应用解密 → 返回前端;
  5. 密钥保存在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 = ? 匹配,不解密全表。

✅ 优点

  1. 密钥不进入数据库,binlog、慢日志只有密文;
  2. DBA即使拿到数据库全部数据,没有密钥无法解密;
  3. 算法自主可控,可以选择AES-GCM、SM4国密;
  4. 不受MySQL版本限制,5.7 / 8.0都可以使用。

❌ 缺点

  • 需要在业务代码开发加解密逻辑;
  • 加密字段无法模糊搜索;
  • 密钥管理需要自建或者对接KMS。

这是互联网业务最主流方案。

四、字段加密后的检索问题(重点)

无论哪种字段加密,密文本身不能like模糊查询

  1. 等值查询:新增哈希摘要列 SHA256,仅用于匹配;不可逆,不能解密原文;
  2. 模糊查询 :
    • 方案1:业务层解密全量数据过滤(大数据量性能灾难,慎用)
    • 方案2:单独存储脱敏片段(手机号前3后4),用于检索,不存储完整明文

五、三种方案对比

方案 密钥是否进SQL DBA能否看到明文 MySQL版本 开发成本 推荐度
AES_ENCRYPT函数 是,日志会记录 能 5.7+ 低 ⭐ 仅测试
InnoDB原生列加密 否 有权限就能看到 8.0.16+ 中 ⭐⭐ 企业云库
应用层加密 完全不进数据库 无密钥看不到 无限制 中高 ⭐⭐⭐⭐⭐ 生产首选

六、生产落地最佳实践

  1. 优先应用层AES-GCM/SM4加密,敏感字段存VARBINARY;
  2. 数据库连接强制开启SSL传输加密;
  3. 密钥统一KMS管理,禁止硬编码、禁止提交到代码仓库;
  4. 增加哈希字段,用于等值查询;
  5. 数据库账号最小权限;
  6. 备份文件也要加密,备份导出的密文文件,如果密钥泄露依然无法解密;
  7. 不要使用MD5做敏感数据加密(MD5不可逆,只能做哈希摘要)。

七、常见踩坑点

  1. 加密二进制数据存入VARCHAR:会发生字符集转义损坏,解密失败;必须VARBINARY;
  2. 密钥长度不符合AES规范:AES-128=16字节,AES-256=32字节;
  3. 只加密数据库,备份mysqldump导出密文但密钥和备份放一起,等于没加密;
  4. 混淆TDE透明加密和字段加密:TDE是整表空间磁盘加密,DBA查询依然返回明文,不能保护敏感字段防内部人员查看。

总结

MySQL字段级加密有三条路线:内置AES函数适合学习,InnoDB原生列加密适合企业云MySQL;绝大多数业务系统首选应用层字段加密 。

字段加密的核心不是"让MySQL帮你加密",而是控制密钥的流转范围,不让密钥进入数据库链路。

相关推荐
夜雪一千1 小时前
MySQL 如何存储经纬度坐标:选型、建表、空间查询实战
android·mysql·adb
CJi0NG1 小时前
【自用】MySQL-存储引擎
数据库·mysql
weixin_460443561 小时前
在线考试人脸识别如何合规?本地活体检测、人脸模板加密与替代认证的技术架构设计
大数据·数据库·煤矿培训考试系统·煤矿在线考试系统·煤矿安全培训·煤矿考试系统
高级程序源1 小时前
django房屋租赁系统28636-计算机课程设计、毕业设计
python·mysql·elasticsearch·小程序·django·flask·课程设计
deepseek231 小时前
SQLite WAL 开了仍报 database is locked?Python 后台任务的事务与幂等修复
数据库·python·sqlite·并发编程·wal
JMchen1 小时前
集成第三方 C/C++ 库到 Android NDK 项目:OpenCV 与 FFmpeg 实战指南
android·opencv·cmake
SelectDB1 小时前
冷热分层速查手册:参数、命令、生效判据与常见失效原因一页看完
大数据·数据库·数据分析
雨落在了我的手上1 小时前
MySQL数据库基础(9):数据库设计
数据库·mysql