PostgreSQL 透明列加密:无需更改 SQL 即可实现安全
由 数据库技术与安全 发布于 2026 年 3 月 12 日
在许多数据库评审会议中,总有一个时刻,房间会变得异常安静。
有人问道:
"这个数据库中哪些列是加密的?"
起初,答案听起来令人放心。
"我们使用了 TLS。"
"磁盘是加密的。"
"应用程序处理敏感字段。"
然后,真实情况开始浮出水面。
有些值在一个服务中加密了,但在另一个服务中没有。
有些迁移记得应用了加密。
有些脚本没有。
有些备份理论上安全,但没人愿意通过事故来检验这个理论。
这就是数据库安全令人不安的真相:
加密常常存在,但并未在数据实际存放的地方得到强制执行。
这正是我通过 PostgreSQL 扩展 column_encrypt 想要探索的问题:
https://github.com/vibhorkum/column_encrypt
该扩展使用自定义的 PostgreSQL 数据类型提供透明的列级加密,使开发者无需更改 SQL 查询即可读写加密列。
而这个项目最人性化的部分或许是:
这个想法始于 2016 年。
多年来,它一直作为一个工程想法留在我脑海中------即 PostgreSQL 本身可以在列级强制执行加密。
现在我终于决定发布它。
这是第一个公开版本。它是一个起点------实用、可用,并且希望 PostgreSQL 社区能够探索并在此基础上构建。
为什么这很重要
关于加密的讨论通常首先关注基础设施。
我们加密磁盘。
我们使用 TLS 连接。
我们保护凭证。
所有这些都很重要。
但是,一旦数据进入数据库内部,另一个问题就变得至关重要:
如果有人获得了对数据库本身的访问权限,会发生什么?
这种访问可能来自:
- 泄露的备份
- 权限过高的账户
- 转储文件
- 受损的服务
- 操作失误
到那时,基础设施加密已经完成了它的使命。
真正的问题是:
最敏感的列仍然可读吗?
这就是列级加密变得至关重要的地方。
不是为了满足合规性检查。
而是为了减少爆炸半径。
因为安全不仅在于防止泄露------还在于在出现问题时限制损害。
应用程序级加密的问题
许多团队在应用层实现加密。
理论上这可行。
但在实践中,随着时间的推移,它往往会变得支离破碎。
不同的服务以不同的方式实现加密。
迁移脚本忘记加密步骤。
ETL 作业绕过了应用程序逻辑。
支持脚本意外插入了明文值。
结果是可预测的:
- 不一致的加密行为
- 分散的安全逻辑
- 难以审计
- 意外的明文存储
最大的限制很简单:
数据库本身无法强制执行一致的加密。
如果有人忘记加密某个值,PostgreSQL 将将其存储为明文。
这就是数据库级加密改变局面的地方。
PostgreSQL 中的透明列加密
column_encrypt 扩展将加密直接移入 PostgreSQL。
它引入了两种加密数据类型:
ENCRYPTED_TEXTENCRYPTED_BYTEA
这些类型在数据类型层面自动执行加密和解密。
这意味着:
- 在
INSERT或UPDATE时,明文值被加密 - 在
SELECT时,密文被解密 - SQL 查询保持不变
换句话说,开发者与加密列的交互就像与普通数据一样。
示例:使用加密列
创建一个包含加密列的表:
sql
CREATE TABLE secure_data (
id SERIAL,
ssn ENCRYPTED_TEXT
);
正常插入值:
sql
INSERT INTO secure_data(ssn) VALUES ('888-999-2045');
INSERT INTO secure_data(ssn) VALUES ('888-999-2046');
INSERT INTO secure_data(ssn) VALUES ('888-999-2047');
查询数据:
sql
SELECT * FROM secure_data;
在会话中加载了正确密钥的情况下,PostgreSQL 返回解密后的值。
id | ssn
----+-------------
1 | 888-999-2045
2 | 888-999-2046
3 | 888-999-2047
如果未加载密钥:
ERROR: cannot decrypt data, because key was not set
这种行为确保了加密列受到保护。
为什么数据库级加密可能更好
将加密移入 PostgreSQL 有几个优点。
加密成为模式的一部分
加密列在表定义中可见。安全成为数据库设计的一部分,而不是分散在应用程序代码中。
SQL 保持简单
开发者可以使用正常的 SQL 语句,而无需将每个查询都包装在加密函数中。
一致的强制执行
PostgreSQL 自身确保加密存储。开发者无法意外绕过加密。
更安全的备份和转储
即使数据库转储泄露,敏感列仍然保持加密状态。
更容易的安全审计
加密数据类型使得识别哪些列包含受保护数据变得容易。
内部工作原理
该扩展注册了两个由 bytea 支持的自定义 PostgreSQL 基本类型。
在 INSERT / UPDATE 时
类型输入函数 col_enc_text_in 和 col_enc_bytea_in 使用当前活动的数据加密密钥(DEK)加密明文值。
在 SELECT 时
类型输出函数 col_enc_text_out 和 col_enc_bytea_out 使用加载的密钥解密密文值。
加密和解密在类型边界透明地发生。
密钥管理模型
该扩展使用两层密钥架构。
数据加密密钥 (DEK)
用于加密列数据。在数据库中以加密形式存储。
密钥加密密钥 (KEK)
用于包裹 DEK 的主密码。KEK 从不存储在 PostgreSQL 内部。
每个会话在访问加密数据之前必须加载密钥。
扩展内置的安全特性
包含多项操作安全防护措施。
日志掩码
PostgreSQL 的 emit_log_hook 可防止敏感密钥材料出现在日志或 pg_stat_activity 中。
行级安全
内部 cipher_key_table 使用行级安全来限制访问。
安全内存清理
存储在会话内存中的密钥在被移除时会被安全擦除。
密钥版本头
每个密文包含一个密钥版本标识符,以支持密钥轮换。
密钥轮换支持
该扩展提供了一个辅助函数,用于使用新密钥重新加密现有数据。
sql
SELECT cipher_key_reencrypt_data(
'public',
'secure_data',
'ssn'
);
这允许将加密数据轮换到新密钥,而不会丢失对现有值的访问。
查询加密数据
该扩展支持用于等值比较的哈希索引。
示例:
sql
CREATE INDEX idx_ssn
ON secure_data USING hash(ssn);
这允许如下查询:
sql
SELECT * FROM secure_data
WHERE ssn = '888-999-2045';
┌─────────────────────────────────────────────────────────────────────────────┐
│ QUERY PLAN │
├─────────────────────────────────────────────────────────────────────────────┤
│ Index Scan using idx_ssn on secure_data (cost=0.00..12.02 rows=1 width=36) │
│ Index Cond: (ssn = '888-999-2045'::encrypted_text) │
└─────────────────────────────────────────────────────────────────────────────┘
(2 rows)
使用前应考虑什么
由于这是最初构思于 2016 年的项目的第一个公开版本,因此应谨慎对待。
在生产环境使用之前:
- 进行代码和安全审查
- 针对你的 PostgreSQL 版本进行验证
- 测试备份和故障转移行为
- 评估连接池场景
- 仔细管理会话密钥
- 实践密钥轮换流程
加密组件值得进行额外的审查。
列加密最适用的场景
这种方法非常适合敏感标识符,例如:
- 社会安全号码
- 金融账户号码
- API 令牌
- 医疗保健标识符
- 个人身份数据
这些字段通常需要强保护,但不需要大量索引。
可能不太适用的场景
在以下列上加密时要谨慎:
- 经常用于范围查询
- 参与大型连接
- 需要高级索引策略
加密最适合选择性地应用于高价值数据。
PostgreSQL 的可扩展性使之成为可能
PostgreSQL 的扩展架构允许开发者扩展数据库引擎本身。
众所周知的例子包括:
- PostGIS
- pgvector
- TimescaleDB
- pgcrypto
column_encrypt 探索了 PostgreSQL 的可扩展性如何也能增强数据库安全。
亲自尝试
克隆并安装扩展:
bash
git clone https://github.com/vibhorkum/column_encrypt.git
cd column_encrypt
make
make install
添加到 postgresql.conf:
ini
shared_preload_libraries = '$libdir/column_encrypt'
重启 PostgreSQL 并创建扩展:
sql
CREATE EXTENSION column_encrypt;
然后你就可以开始使用 ENCRYPTED_TEXT 或 ENCRYPTED_BYTEA 定义加密列了。
欢迎反馈
这个项目作为第一个版本发布,我真诚地希望得到 PostgreSQL 社区的反馈。
如果你尝试、审阅它,或有改进的想法,请在 GitHub 仓库上分享你的反馈:
https://github.com/vibhorkum/column_encrypt
反馈可以包括:
- 设计建议
- 安全观察
- 性能考虑
- 兼容性发现
- 操作经验
- 未来增强的想法
优秀的开源项目通过审查、讨论和实际使用而变得更好。这个项目也应如此。
最后的思考
有些想法需要时间。
column_encrypt 背后的概念始于 2016 年,而这次发布是其第一个公开版本。
它不是 PostgreSQL 列级加密的最终定论,但它探索了一个我仍然认为重要的设计方向:将加密推向数据实际存放的地方。
当安全依赖于每个开发者、每个脚本和每个服务都记得做正确的事情时,安全往往会失败。
当防护栏被构建到架构中时,系统会变得更加安全。
透明列加密是朝着这个目标前进的一种方式。
如果这个项目能引发社区的反馈、讨论或改进,那将是一个非常美好的下一篇章。