必须先执行CREATE EXTENSION IF NOT EXISTS pgcrypto;启用扩展,否则encrypt()等函数报错;密钥需通过current_setting()动态获取并decode转bytea,触发器中须RETURN NEW且避免硬编码密钥。

pgcrypto扩展没启用,encrypt()直接报错
PostgreSQL默认不加载pgcrypto,调用encrypt()、gen_random_bytes()等函数会提示function encrypt() does not exist。必须先在目标数据库中显式启用扩展:
- 用超级用户或具有
CREATE权限的用户执行:CREATE EXTENSION IF NOT EXISTS pgcrypto; - 注意:扩展只对当前数据库生效,每个需要加密的数据库都要单独运行一次
- 如果提示
permission denied to create extension,说明用户缺少CREATE权限,不能仅靠GRANT EXECUTE绕过
触发器函数里硬编码密钥有严重安全风险
把密钥写死在encrypt()调用里(比如encrypt(new.password, 'my-secret-key', 'aes'))会导致密钥随SQL文本暴露在系统视图(如pg_proc.prosrc)、日志甚至备份中。正确做法是将密钥存在外部可控位置,并通过current_setting()动态读取:
- 启动PostgreSQL时设置
custom.crypto.key参数(需在postgresql.conf中添加custom.crypto.key = 'base64-encoded-key-here'),或运行SET custom.crypto.key = '...'; - 触发器函数内用
current_setting('custom.crypto.key', true)获取,第二个参数true表示缺失时不报错 - 密钥必须是
bytea类型,所以实际要配合decode()转换:decode(current_setting('custom.crypto.key', true), 'base64')
使用encrypt()/decrypt()时AES模式和填充必须严格匹配
pgcrypto的encrypt()默认使用AES-CBC且自动PKCS#5填充,但decrypt()不会自动去除填充——你得自己调用pgp_sym_decrypt()或手动处理。更稳妥的方式是统一走PGP封装:
- 存储时用
pgp_sym_encrypt(new.sensitive_data, key_str, 'cipher-algo=aes256, compress-algo=1') - 读取时用
pgp_sym_decrypt(stored_column, key_str) - 避免直接用
encrypt():它返回裸二进制,无完整性校验,且CBC模式下缺少IV管理逻辑 - 如果坚持用原生AES,必须显式生成并存储IV(例如用
gen_random_bytes(16)),并在encrypt()中传入,解密时再用同一IV
触发器中修改NEW字段后必须返回NEW
BEFORE触发器若想让加密值真正写入表,必须显式RETURN NEW;漏掉这句,PostgreSQL会按NULL插入所有字段(或报错,取决于严格模式)。典型错误写法:
CREATE OR REPLACE FUNCTION encrypt_ssn() RETURNS TRIGGER AS $$
BEGIN
NEW.ssn_encrypted := pgp_sym_encrypt(NEW.ssn, current_setting('custom.crypto.key', true));
-- 忘了RETURN NEW!数据全丢
END;
$$ LANGUAGE plpgsql;正确写法末尾加一句:RETURN NEW;。另外注意:触发器函数返回类型必须是TRIGGER,不能是void或text。
最常被忽略的是密钥轮换——一旦密钥写进postgresql.conf并重启,旧数据就无法解密,除非保留历史密钥版本并改造触发器做多版本路由。真要上生产,得提前设计密钥ID字段和解密路由逻辑。


















