数据库磁盘文件被偷走怎么办?这是很多合规审计场景下的硬问题。MySQL、SQL Server、Oracle 早就有透明数据加密(TDE)方案,而 PostgreSQL 长期以来只能靠文件系统层加密或自编译补丁来凑合——要么不够细粒度,要么维护成本太高。pg_tde 的出现填补了这个空白:它是第一个面向 PostgreSQL 的开源 TDE 扩展,以 extension 形式加载,不需要改应用代码,也不需要重新编译数据库。
透明数据加密到底加密了什么
"透明"的意思是:对上层 SQL 查询完全透明,应用不需要改一行代码;对底层存储则完全不透明——磁盘上的数据文件是加密的。即使有人直接拷走了 .dat 文件或整个数据目录,没有密钥就无法还原表内容。
pg_tde 当前加密的范围:
- 表数据文件(relation files)——这是最核心的目标,业务数据存在这里
- 临时文件——排序、哈希等操作产生的中间结果
- WAL 日志——正在逐步覆盖,确保事务日志也不裸露在磁盘上
它不加密的是:系统目录(pg_class 等元数据)、控制文件、配置文件。这意味着攻击者能看到表结构定义,但看不到实际行数据。对于大多数合规要求(如 PCI-DSS、HIPAA),这已经足够。
密钥管理:TDE 的命门
TDE 的安全性完全取决于密钥管理。pg_tde 采用两层密钥架构:
- 主密钥(Master Key):存在外部密钥管理服务(KMS)中,如 HashiCorp Vault、AWS KMS 等。数据库本身不持久保存主密钥。
- 表加密密钥(Table Key):每个被加密的表有一个独立密钥,用主密钥加密后存储在 PostgreSQL 的内部目录中。
启动数据库时,pg_tde 从 KMS 获取主密钥,解密各表的加密密钥,再用于读写加密。如果 KMS 不可达,数据库将无法启动或无法访问加密表——这正是期望的行为:密钥和密文不在同一个地方。
安装与启用:从零开始
以下示例基于 Ubuntu 22.04 + PostgreSQL 16 + HashiCorp Vault。如果你用其他平台或 KMS,替换对应步骤即可。
第一步:安装 pg_tde 扩展
# 从 Percona 仓库安装(以 Ubuntu/Debian 为例)
sudo apt update
sudo apt install -y percona-postgresql-16-pg-tde
# 或者从源码编译
git clone https://github.com/Percona/pg_tde.git
cd pg_tde
# 确保已安装 pg_config 对应版本的 dev 包
make
sudo make install
第二步:配置 PostgreSQL 加载扩展
修改 postgresql.conf:
# 在 shared_preload_libraries 中加入 pg_tde
# 注意:TDE 扩展必须在启动时加载,不能只靠 CREATE EXTENSION
sudo -u postgres sed -i \
"s/^#shared_preload_libraries = ''/shared_preload_libraries = 'pg_tde'/" \
/etc/postgresql/16/main/postgresql.conf
# 重启 PostgreSQL
sudo systemctl restart postgresql
第三步:配置密钥管理
pg_tde 需要知道主密钥从哪里取。创建密钥提供者配置:
-- 连接到目标数据库
psql -U postgres -d mydb
-- 创建扩展
CREATE EXTENSION pg_tde;
-- 注册 Vault 密钥提供者
-- 参数根据你的 Vault 实例调整
SELECT pg_tde_add_key_provider_vault(
'my_vault',
'http://127.0.0.1:8200', -- Vault 地址
'secret/data/pg_tde', -- 存储密钥的 Vault path
's.xxxxxxxx' -- Vault token
);
-- 设置默认主密钥
SELECT pg_tde_set_default_master_key(
'my_vault',
'pg_tde_master_key' -- Vault 中主密钥的名字
);
第四步:创建加密表
这才是关键一步——不是加密整个数据库,而是按表选择:
-- 创建加密表:在 CREATE TABLE 语句中加上 USING tde_am
-- tde_am 是 pg_tde 提供的专用访问方法(Access Method)
CREATE TABLE payment_cards (
id SERIAL PRIMARY KEY,
card_number TEXT NOT NULL,
holder_name TEXT NOT NULL,
expiry TEXT NOT NULL
) USING tde_am;
-- 普通表仍然用默认的 heap AM,不受影响
CREATE TABLE audit_log (
id SERIAL PRIMARY KEY,
event TEXT NOT NULL,
ts TIMESTAMP DEFAULT now()
);
-- 验证加密表
SELECT amname FROM pg_am WHERE amname = 'tde_am';
-- 应返回 tde_am
SELECT relname, relam
FROM pg_class
WHERE relname = 'payment_cards';
-- relam 应指向 tde_am 的 OID
插入和查询的 SQL 完全不变:
INSERT INTO payment_cards (card_number, holder_name, expiry)
VALUES ('4111111111111111', 'Zhang San', '12/26');
SELECT * FROM payment_cards;
-- 正常返回明文结果,应用无感知
验证磁盘上确实是密文
# 找到加密表的物理文件路径
psql -U postgres -d mydb -c \
"SELECT pg_relation_filepath('payment_cards');"
# 输出类似: base/16384/16390
# 用 hexdump 查看文件内容
sudo hexdump -C /var/lib/postgresql/16/main/base/16384/16390 | head -20
# 你应该看到乱码/不可读字节,而非明文 "4111111111111111"
按表加密 vs 全库加密:选哪个
pg_tde 采用按表加密策略,这是一个有意的设计选择:
| 维度 | 按表加密(pg_tde) | 全库加密(文件系统层) |
|---|---|---|
| 粒度 | 只加密敏感表,减少性能开销 | 所有文件一视同仁 |
| 密钥轮换 | 单表密钥可独立轮换 | 全库轮换需要重写所有数据 |
| 性能影响 | 非加密表零开销 | 所有表都有加密/解密开销 |
| 合规针对性 | 精确满足"敏感数据加密"条款 | 过度加密反而增加运维复杂度 |
如果你的合规要求是"所有客户数据必须加密存储",按表加密更精准;如果要求是"整个存储介质不可读",文件系统层 LUKS 加密可能更简单。两者也可以叠加使用。
上线前的检查清单
- KMS 高可用:Vault 或 KMS 必须比数据库更可靠。数据库重启时如果拿不到主密钥,整个加密表集合将不可访问。建议 KMS 前面放 HA,或使用缓存机制。
- 密钥轮换流程:提前演练。pg_tde 支持主密钥轮换,但需要停写或在线重加密,确认你的业务能接受哪种方式。
- 备份策略调整:
pg_dump导出的是解密后的明文,备份文件本身需要加密保护(GPG、Vault encrypt 等)。物理备份(pg_basebackup)则是密文,恢复时需要同样的 KMS 配置。 - 监控告警:KMS 连接失败必须立即告警,否则数据库重启后你会面临"数据还在但密钥拿不到"的窘境。
- 测试恢复:在 staging 环境完整演练一次"数据目录丢失 → 从备份恢复 → 从 KMS 取密钥 → 数据库上线"的全流程,不要等到生产出事才发现流程有断点。
pg_tde 目前仍在积极开发中,WAL 加密、索引加密等能力正在完善。如果你的场景是"敏感字段必须磁盘加密且应用不能改代码",它已经是 PostgreSQL 生态里最值得试的方案。先在非生产环境跑通上述流程,再决定是否推进。