在数据库安全防护里,敏感字段加密是绕不开的一环。抛开合规要求不谈,仅从风险控制角度看,把手机号、身份证号、银行卡号直接以明文存入表里,等于给拖库攻击、内部泄露、备份文件外泄都留了后门。SQL数据加密的实现并不只是套一个AES函数,而是一个需要结合数据库自身能力、密钥托管和查询模式来设计的工程问题。

一、SQL数据加密的三种主要层次
从加密发生的位置来看,SQL数据加密大致可以分成应用层加密、数据库列级加密和存储层透明加密三大类。应用层加密由业务代码调用密码库完成加解密,数据库里只保存密文,密钥完全由应用自己保管,数据库管理员即使查看表也无法还原明文。这种方式最灵活,支持任意算法和密钥轮换,但代价是所有读写操作都要经过应用层改造,历史系统迁移成本高,而且像报表平台、ETL任务等直接连库的应用会失去可读性。
数据库列级加密则把加解密动作下沉到SQL语句中,例如MySQL的AES_ENCRYPT、SQL Server的ENCRYPTBYKEY。它的优势是改动范围相对可控,通常只需要调整写入和查询语句,不需要重写整个业务服务。但缺点是密钥需要在数据库客户端或连接串中出现,数据库账号权限较高的用户依然可能接触密钥;同时加密字段无法直接走普通索引,LIKE、范围查询、排序都会失效,需要在设计阶段就预留盲索引或哈希列。
透明数据加密(TDE)位于最底层,由数据库引擎在写入磁盘前自动加密数据页和日志,读取时再自动解密。业务代码完全无感知,索引和查询逻辑也不受影响。TDE主要防止的是物理介质丢失——比如磁盘被盗、备份文件泄露,而不能阻止拥有数据库权限的用户读取明文。三种方案不是互斥的,高安全场景可以叠加使用,例如应用层加密核心字段,数据库TDE保护全库文件。
二、MySQL与PostgreSQL列级加密实战
先看MySQL。MySQL从5.6版本开始提供了AES_ENCRYPT和AES_DECRYPT函数,但需要注意默认实现使用ECB模式,没有初始化向量,同样的明文加密后密文一致,存在模式泄露风险;函数返回的是二进制数据,通常用TO_BASE64或HEX转成可见文本再存入VARCHAR或VARBINARY列。使用时密钥长度建议为16、24或32字节,对应AES-128、AES-192、AES-256。下面是一个写入示例。
-- MySQL:写入时加密手机号
INSERT INTO user_info (user_id, mobile_enc)
VALUES (1001, TO_BASE64(AES_ENCRYPT('13800138000', '0123456789abcdef0123456789abcdef')));
查询时使用FROM_BASE64和AES_DECRYPT还原,但要小心编码问题,AES_DECRYPT返回二进制,必要时用CONVERT或CAST转成CHAR。需要注意的是,AES_ENCRYPT加密后的结果无法直接比较,如果你要按加密后的值定位记录,必须使用密文等值查询;如果要按明文手机号查询,只能把整列的解密结果与输入比较,这在数据量稍大时会退化成全表扫描。比如下面的查询只能精确匹配user_id,再解密输出。
-- MySQL:按主键读取并解密
SELECT user_id,
CAST(AES_DECRYPT(FROM_BASE64(mobile_enc), '0123456789abcdef0123456789abcdef') AS CHAR) AS mobile
FROM user_info
WHERE user_id = 1001;
PostgreSQL可以通过pgcrypto扩展实现列级加密。启用扩展后使用pgp_sym_encrypt比老的encrypt函数更合适,因为PGP对称加密带有完整性保护,能发现密文被篡改。写入时用armor把二进制转成文本,查询时用dearmor还原。示例:
-- PostgreSQL:启用扩展并写入加密数据
CREATE EXTENSION IF NOT EXISTS pgcrypto;
INSERT INTO user_info (user_id, mobile_enc)
VALUES (1001, armor(pgp_sym_encrypt('13800138000', 'strong-passphrase')));
-- PostgreSQL:读取并解密
SELECT user_id,
pgp_sym_decrypt(dearmor(mobile_enc), 'strong-passphrase') AS mobile
FROM user_info
WHERE user_id = 1001;
无论是MySQL还是PostgreSQL,列级加密的密钥都直接写在SQL里,连接日志、慢查询日志、审计插件都有可能记录SQL文本,从而泄露密钥。因此生产环境最好把密钥放在环境变量或密钥管理服务中,通过脚本拼接SQL时再注入,或者干脆改用应用层加密。
三、SQL Server列级加密与Always Encrypted
SQL Server的列级加密体系相对完整,涉及服务主密钥、数据库主密钥、证书和对称密钥四层结构。对称密钥不直接以明文保存,而是由证书加密后存放,证书又由数据库主密钥保护,主密钥最终受服务主密钥保护。这个链条的好处是即使备份文件泄露,没有主密钥密码也无法解开对称密钥。先创建密钥对象:
-- SQL Server:创建列级加密所需的密钥对象 CREATE MASTER KEY ENCRYPTION BY PASSWORD = 'StrongMasterKey!123'; CREATE CERTIFICATE MyEncryptionCert WITH SUBJECT = 'Column Encryption Certificate'; CREATE SYMMETRIC KEY MySymmetricKey WITH ALGORITHM = AES_256 ENCRYPTION BY CERTIFICATE MyEncryptionCert;
写入数据前需要打开对称密钥,然后使用ENCRYPTBYKEY,解密使用DECRYPTBYKEY。ENCRYPTBYKEY返回varbinary,需要转成可读格式或直接存入varbinary列。下面的例子展示插入和查询:
-- 写入加密数据
OPEN SYMMETRIC KEY MySymmetricKey
DECRYPTION BY CERTIFICATE MyEncryptionCert;
INSERT INTO user_info (user_id, mobile_enc)
VALUES (1001, ENCRYPTBYKEY(KEY_GUID('MySymmetricKey'), '13800138000'));
CLOSE SYMMETRIC KEY MySymmetricKey;
-- 查询并解密
OPEN SYMMETRIC KEY MySymmetricKey
DECRYPTION BY CERTIFICATE MyEncryptionCert;
SELECT user_id,
CONVERT(varchar(20), DECRYPTBYKEY(mobile_enc)) AS mobile
FROM user_info
WHERE user_id = 1001;
CLOSE SYMMETRIC KEY MySymmetricKey;
SQL Server还提供了Always Encrypted功能,这是比手工列级加密更安全的方案:密钥保存在客户端驱动或密钥库中,数据库引擎永远看不到明文密钥,甚至无法解密数据。在启用Always Encrypted的列上,SQL Server只能处理密文,等值查询通过确定性加密实现。代价是所有加解密发生在客户端,存储过程、触发器和数据库内部计算无法直接处理这些列,模糊查询和范围索引不可用。对于新项目或高标准合规要求,Always Encrypted往往比手工加解密更合适。
四、透明数据加密(TDE)的开启与盲区
如果业务系统不想改动任何SQL,又担心备份文件或磁盘丢失,透明数据加密是成本最低的兜底方案。以SQL Server为例,TDE会在页写入磁盘前加密,日志文件同样被加密。开启TDE需要先创建数据库主密钥和证书,再用证书加密数据库加密密钥,最后把数据库设为加密状态。示例:
-- SQL Server:开启TDE USE master; CREATE MASTER KEY ENCRYPTION BY PASSWORD = 'StrongMasterKey!123'; CREATE CERTIFICATE TdeCert WITH SUBJECT = 'TDE Certificate'; USE target_db; CREATE DATABASE ENCRYPTION KEY WITH ALGORITHM = AES_256 ENCRYPTION BY SERVER CERTIFICATE TdeCert; ALTER DATABASE target_db SET ENCRYPTION ON;
MySQL从8.0开始对InnoDB表空间加密提供了较好支持,但通常需要企业版或配置keyring插件。社区版可以借助Percona Server或文件系统级加密。配置keyring_file后,可以设置innodb_encrypt_tables启用表加密。配置片段如下:
[mysqld] early-plugin-load=keyring_file.so keyring_file_data=/var/lib/mysql-keyring/keyring innodb_encrypt_tables=ON innodb_encrypt_log=ON
TDE的盲区必须认清:它保护的是静态数据文件,而不是数据库里的明文视图。任何拥有SELECT权限的用户查询数据时,看到的内容仍然是解密后的明文;DBA通过SQL查询也能直接读取敏感字段。因此TDE不能替代列级加密或应用层加密,它的定位是防止存储介质失窃、备份文件被拖走,以及满足部分等保和数据安全规范中对静态加密的要求。同时TDE会对写入性能产生一定损耗,通常在5%到10%左右,开启前建议在测试环境做基准测试。
五、密钥管理与加密字段查询优化建议
密钥管理往往比加密算法本身更容易出问题。实践中常见错误包括:密钥和密文放在同一张表或同一个库、密钥硬编码在源码仓库、所有环境共用一把密钥、从不做轮换。合理的做法是把主密钥放入云厂商的KMS、Vault或硬件安全模块,数据库或应用只获取短期加密密钥;列级加密可以配合盲索引,例如将手机号后四位单独存为哈希值,用于快速检索,完整号码仍以密文存储。这样既能支持常见查询,又避免全表解密。
加密字段的查询限制需要提前规划。等值查询可以采用确定性加密,比如Always Encrypted的确定性模式,或者额外存一列HMAC-SHA256摘要;模糊查询通常建议拆分字段,例如单独存储手机号后四位明文或哈希,或者使用数据库支持的加密索引技术,但大多数数据库并不原生支持,需要应用层自己维护。范围查询和排序在加密列上几乎无法直接实现,如果业务确实需要,可以考虑先缩小候选集,再在应用层解密后排序。
最后要强调的是,SQL数据加密不能孤立实施,它应当与最小权限原则、审计日志、传输加密(TLS)和备份加密配合。数据库账号权限过高时,攻击者可以通过查询直接读取明文,此时任何列级加密都可能被绕过;备份文件如果不加密,TDE的作用也会打折扣。建议在方案设计阶段列出敏感字段清单,明确哪些需要应用层加密、哪些使用列级加密、哪些只依赖TDE,再按风险等级逐步落地。这样既控制了性能开销,也能让安全投入真正发挥效果。