第 3 章:数据库内置加密函数——三剑客的实战用法
说到列级加密,最直接的办法就是利用数据库自己带的加密函数。我做过不少项目,早期团队总爱自己写加解密逻辑,结果维护起来头疼得很。后来我慢慢发现,用内置函数能省掉一大半的坑。
今天咱们就聊三个主流数据库:MySQL、PostgreSQL、SQL Server。它们各自有对应的函数,用法和坑点都不太一样。
3.1 MySQL:AES_ENCRYPT / AES_DECRYPT
MySQL 自带的 AES 加密函数,用起来极其简单。说白了就是两个函数:AES_ENCRYPT('明文', '密钥') 和 AES_DECRYPT('密文', '密钥')。
个人习惯:我一般在插入数据时直接加密,查询时实时解密。但注意,密钥别硬编码在 SQL 里,最好存到配置表或环境变量中。
先看一个例子:
-- 建表
CREATE TABLE user_credit (
id INT PRIMARY KEY,
card_no VARBINARY(255),
ssn VARBINARY(255)
);
-- 插入加密数据
INSERT INTO user_credit (id, card_no, ssn)
VALUES (1, AES_ENCRYPT('6222021234567890', 'my_secret_key'),
AES_ENCRYPT('123-45-6789', 'my_secret_key'));
-- 查询并解密
SELECT id,
AES_DECRYPT(card_no, 'my_secret_key') AS card_no_dec,
AES_DECRYPT(ssn, 'my_secret_key') AS ssn_dec
FROM user_credit;
有几个点你一定要注意:
- 加密后的数据用
VARBINARY存,别用VARCHAR。我见过有人用VARCHAR,结果乱码笑死人。 - AES 默认是 AES-128-ECB,不安全。建议用
AES_ENCRYPT('明文', '密钥', '密钥长度', '加密模式')指定 GCM 或 CBC。但注意 MySQL 8.0 才支持模式参数。 - 密钥长度:16、24、32 字节分别对应 128、192、256 位。
我踩过的坑:曾经有一个项目,同事把密钥直接写在 SQL 脚本里,然后脚本被误传到 GitHub……嗯,从那以后我强制要求密钥从外部配置读取,绝不硬编码。
3.2 PostgreSQL:pgcrypto 扩展
PostgreSQL 默认不带加密函数,你需要安装 pgcrypto 扩展。我个人特别喜欢它的灵活性——它支持多种算法,而且有 gen_salt() 这样的辅助函数。
安装很简单:
CREATE EXTENSION IF NOT EXISTS pgcrypto;
常用的加密函数就三个:encrypt()、decrypt()、gen_salt()。我们看一个实战例子:
-- 建表
CREATE TABLE user_pii (
id SERIAL PRIMARY KEY,
email TEXT,
phone_enc BYTEA,
address_enc BYTEA
);
-- 插入加密数据
INSERT INTO user_pii (id, email, phone_enc, address_enc)
VALUES (1, 'alice@example.com',
encrypt('13800138000', 'mykey1234567890ab', 'aes'),
encrypt('北京市朝阳区', 'mykey1234567890ab', 'aes'));
-- 查询解密
SELECT id, email,
decrypt(phone_enc, 'mykey1234567890ab', 'aes')::text AS phone,
decrypt(address_enc, 'mykey1234567890ab', 'aes')::text AS address
FROM user_pii;
注意几个点:
encrypt()第三个参数可以是'aes'、'bf'(Blowfish)等。默认使用 CBC 模式。- 解密结果需要
::text转换类型,否则返回的BYTEA你看不懂。 - 密钥长度:AES 要求 16 字节,否则自动填充或报错。我记得有一次密钥长了 1 字节,报错半天没找到原因。
避坑指南:如果你用 pgcrypto 做列级加密,密钥管理建议用 pgcrypto 的 pgp_sym_encrypt() 函数,它支持密码保护。但那样性能会差一些,适合低频访问的敏感字段。
3.3 SQL Server:EncryptByKey / DecryptByKey
SQL Server 的加密体系稍微复杂一些。它需要你先创建证书、创建对称密钥,然后再用 EncryptByKey() 和 DecryptByKey()。其实理解了就不难。
基本步骤是这样的:
- 创建主密钥(
CREATE MASTER KEY) - 创建证书(
CREATE CERTIFICATE) - 创建对称密钥(
CREATE SYMMETRIC KEY) - 打开密钥 → 加解密 → 关闭密钥
看代码:
-- 1. 创建主密钥
CREATE MASTER KEY ENCRYPTION BY PASSWORD = 'MasterKeyPass123!';
-- 2. 创建证书
CREATE CERTIFICATE MyCert
WITH SUBJECT = 'Column Encryption Cert';
-- 3. 创建对称密钥
CREATE SYMMETRIC KEY MySymKey
WITH ALGORITHM = AES_256
ENCRYPTION BY CERTIFICATE MyCert;
-- 4. 建表
CREATE TABLE Employee (
id INT PRIMARY KEY,
name NVARCHAR(100),
salary_enc VARBINARY(256),
ssn_enc VARBINARY(256)
);
-- 5. 插入加密数据
OPEN SYMMETRIC KEY MySymKey
DECRYPTION BY CERTIFICATE MyCert;
INSERT INTO Employee (id, name, salary_enc, ssn_enc)
VALUES (1, '张三',
EncryptByKey(Key_GUID('MySymKey'), '85000'),
EncryptByKey(Key_GUID('MySymKey'), '123-45-6789'));
CLOSE SYMMETRIC KEY MySymKey;
-- 6. 查询解密
OPEN SYMMETRIC KEY MySymKey
DECRYPTION BY CERTIFICATE MyCert;
SELECT id, name,
CONVERT(NVARCHAR, DecryptByKey(salary_enc)) AS salary,
CONVERT(NVARCHAR, DecryptByKey(ssn_enc)) AS ssn
FROM Employee;
CLOSE SYMMETRIC KEY MySymKey;
你看,步骤多了点,但安全性更高。我的建议是:
- 每个加密字段用不同的对称密钥?没必要,同一个密钥足够了,除非你需要做列级权限隔离。
- 记得每次用完要
CLOSE SYMMETRIC KEY,否则会话里一直打开,有安全隐患。 EncryptByKey()的返回值是VARBINARY,最大 256 字节。如果明文太长会报错。
我推荐的做法:在存储过程里封装加解密逻辑,应用层只调用存储过程,不直接操作密钥。这样密钥管理起来方便,也更容易做审计。
3.4 三者的对比
我整理了一张表,方便你快速选型:
| 对比项 | MySQL | PostgreSQL | SQL Server |
|---|---|---|---|
| 扩展/函数 | 内置函数(需注意版本) | pgcrypto 扩展 | 内置,需创建证书+密钥 |
| 算法支持 | AES-128/192/256 (ECB/CBC/GCM) | AES, Blowfish, PGP 等 | AES (128/192/256), TRIPLE_DES |
| 密钥管理 | 手动传密钥 | 手动传密钥或 pgp 密码 | 证书+对称密钥体系 |
| 性能 | 较高(纯函数调用) | 中(扩展加载开销) | 较低(需开闭密钥) |
| 适用场景 | 简单应用,快速加密 | 灵活多算法,安全要求中等 | 企业级,合规要求高 |
3.5 我的选型建议
你可能会问:到底选哪个?我根据项目经验总结几点:
- 原型或小项目:直接用 MySQL 的
AES_ENCRYPT,最快。但记得用 CBC 模式,且密钥别写死在代码里。 - 需要跨平台或算法灵活:PostgreSQL + pgcrypto。我上一个项目就用这个,因为要同时兼容 AES 和 PGP 两种加密方案给不同合规部门。
- 大型企业、有审计要求:SQL Server 的证书体系。虽然步骤多,但它的权限控制和密钥轮换做得最好。我曾在金融项目里用它加密身份证号,过了 SOC2 审计。
注意:不管用哪个数据库,千万别把加密密钥和明文数据放在同一张表或同一个备份文件里。我见过有人把密钥存到数据库的配置表里,然后整个库被人拖走……你想想看,加密还有啥意义?
好了,这一章我们讲了三个数据库内置加密函数的具体用法。你在实际项目中只要按这个思路选,基本不会跑偏。