pgcrypto扩展(加密解密支持)
pgcrypto¶
pgcrypto 扩展模块用于 SeaboxSQL/SeaboxMPP中实现加密和解密功能。pgcrypto 属于“可信”模块;只要用户拥有当前数据库上的 CREATE 权限就可以安装该模块,不再需要超级用户权限。
pgcrypto 提供了两类加密算法:单向加密和双向加密。
单向加密属于不可逆加密,无法根据密文解密出明文,适用于数据的验证,例如登录密码验证。常用的单向加密算法有 MD5、SHA、HMAC 等。双向加密属于可逆加密,根据密文和密钥可解密出明文,适用于数据的安全传输,例如电子支付、数字签名等。常用的双向加密算法有 AES、DES、RSA、ECC 等。
单向加密—通用哈希函数¶
digest()
¶
- 语法
sql digest(data text, type text) returns bytea digest(data bytea, type text) returns bytea
- 返回值
- bytea
- 描述
digest()
函数可以根据不同的算法生成数据的二进制哈希值. 上述语法中,data 是原始数据;type 是加密算法,包括 md5、sha1、sha224、sha256、sha384 以及 sha512;函数的返回结果为二进制字符串。- 示例
-
假如存在以下用户表:
seaboxsql=# CREATE TABLE users ( id SERIAL PRIMARY KEY, username varchar(20) NOT NULL, password text NOT NULL ); CREATE TABLE
创建新用户时,可以使用 digest() 函数对密码进行加密存储:
seaboxsql=# INSERT INTO users(username, password) VALUES ('tony', encode(digest('123456','md5'), 'hex')); INSERT 0 1 seaboxsql=# INSERT INTO users(username, password) VALUES ('anne', encode(digest('123456','md5'), 'hex')); INSERT 0 1 seaboxsql=# INSERT INTO users(username, password) VALUES ('tony', encode(digest('123456','md5'), 'hex')); INSERT 0 1 seaboxsql=# INSERT INTO users(username, password) VALUES ('anne', encode(digest('123456','md5'), 'hex')); INSERT 0 1 seaboxsql=# INSERT INTO users(username, password) VALUES ('tony', encode(digest('123456','md5'), 'hex')); INSERT 0 1 seaboxsql=# INSERT INTO users(username, password) VALUES ('anne', encode(digest('123456','md5'), 'hex')); INSERT 0 1 seaboxsql=# seaboxsql=# SELECT * FROM users; id | username | password ----+----------+---------------------------------- 3 | tony | e10adc3949ba59abbe56e057f20f883e 4 | anne | e10adc3949ba59abbe56e057f20f883e 1 | tony | e10adc3949ba59abbe56e057f20f883e 2 | anne | e10adc3949ba59abbe56e057f20f883e 5 | tony | e10adc3949ba59abbe56e057f20f883e 6 | anne | e10adc3949ba59abbe56e057f20f883e (6 rows)
其中,encode 函数用于将二进制字符串转换为十六进制的文本。
当用户登录时,使用同样的方法加密输入的密码参数:
seaboxsql=# -- 输入正确密码时 seaboxsql=# SELECT id FROM users WHERE username = 'tony' AND password = encode(digest('123456','md5'), 'hex'); id ---- 3 5 1 (3 rows) seaboxsql=# -- 输入错误密码时 seaboxsql=# SELECT id FROM users WHERE username = 'tony' AND password = encode(digest('abc123','md5'), 'hex'); id ---- (0 rows)
这类加密算法的主要问题是相同的数据经过加密之后的结果相同。因此。在实际应用中可以将用户名和密码字符串连接之后再进行加密。
hmac()
¶
- 语法
sql hmac(data text, key text, type text) returns bytea hmac(data bytea, key bytea, type text) returns bytea
- 返回值
- bytea
- 描述
hmac()
函数可以根据不同的算法生成数据的二进制哈希值. 上述语法中,data 是原始数据;key 是加密密钥;type 是加密算法,包括 md5、sha1、sha224、sha256、sha384 以及 sha512;函数的返回结果为二进制字符串。- 示例
-
以下语句使用 hmac() 函数重新设置了用户的密码:
seaboxsql=# UPDATE users seaboxsql-# SET password = encode(hmac('123456', username, 'md5'), 'hex'); UPDATE 6 seaboxsql=# select * from users; id | username | password ----+----------+---------------------------------- 2 | anne | 9079d683b5fc5033427c2af2b6de4d01 1 | tony | 7a86cd4a12d7a54d65a4fe5854aaf41f 3 | tony | 7a86cd4a12d7a54d65a4fe5854aaf41f 4 | anne | 9079d683b5fc5033427c2af2b6de4d01 5 | tony | 7a86cd4a12d7a54d65a4fe5854aaf41f 6 | anne | 9079d683b5fc5033427c2af2b6de4d01 (6 rows)
其中,encode 函数用于将二进制字符串转换为十六进制的文本。该示例中,使用 username 作为密钥,相同的密码加密之后的结果不同。
对于 digest() 函数,如果同时被修改了原始数据和加密结果,无法进行识别;hmac() 函数只要密钥没有泄露的话,可以发现被篡改的数据。
单向加密—密码哈希函数¶
crypt()
和 gen_salt()
函数专用于密码加密,其中 crypt()
用于加密数据,gen_salt()
用于生成 salt(加盐)。
crypt()
¶
- 语法
sql crypt(password text, salt text) returns text
- 返回值
- text
- 描述
-
该函数返回 password 字符串 crypt(3) 格式的哈希值,salt 参数由 gen_salt() 函数生成。
crypt()
中的算法和普通的 MD5 或者 SHA1 哈希算法存在以下不同之处:-
crypt()
中的算法它们更慢。由于密码包含的数据量很小,这是增加暴力破解难度的唯一方法 -
它们使用了一个随机值(称为盐值),因此密码的用户加密后的密码不同。这也可以针对破解算法提供一种额外的安全保护。
-
它们的结果中包括了算法类型,因此可以针对不同用户使用不同的算法对密码进行加密。
-
其中一些算法具有自适应性,意味着当计算机性能变得更快时,可以调整算法使其变得更慢,而不会产生与已有密码的不兼容性。
下表列出了
crypt()
函数支持的算法:算法 密码最大长度 自适应性 盐值比特位数 输出结果长度 描述 bf 72 YES 128 60 基于 Blowfish 的 2a 变种算法 md5 无限 NO 48 34 基于 MD5 的加密算法 xdes 8 YES 24 20 扩展 DES des 8 NO 12 13 原始 UNIX 加密算法 -
- 示例
-
以下语句使用
crypt()
函数重新设置了用户的密码:seaboxsql=# UPDATE users seaboxsql-# SET password = crypt('123456', gen_salt('md5')); UPDATE 6 seaboxsql=# select * from users; id | username | password ----+----------+------------------------------------ 3 | tony | $1$HpD13yT7$1MMQP7BK1oErbRHrovUBB1 4 | anne | $1$AntKXnNp$eMXf9Txryu9jhP/mTwxsY/ 5 | tony | $1$1lRLUeKb$hC9JN9JN56oXsT3SsTGFd1 6 | anne | $1$/Pt7jFRw$3i81/byMiq0RbxQIDAjqY0 2 | anne | $1$VB3DSija$s53Jy1JTKwjvcszHqu5mm. 1 | tony | $1$MtT1/gF5$GucU5TbvtUOBxw2/HsNTS. (6 rows)
对于相同的密码,crypt() 函数每次也会返回不同的结果,因为 gen_salt() 函数每次都会生成不同的 salt。校验密码时可以将之前生成的哈希结果作为 salt:
seaboxsql=# SELECT id seaboxsql-# FROM users seaboxsql-# WHERE username = 'tony' seaboxsql-# AND password = crypt('123456', password); id ---- 3 1 5 (3 rows)
gen_salt()
¶
- 语法
sql gen_salt(type text [, iter_count integer ]) returns text
- 返回值
- text
- 描述
-
gen_salt()
函数用于生成盐值 salt。该函数每次都会生成一个随机的盐值字符串,该字符串同时决定了 crypt() 函数使用的算法;type 参数用于指定一个生成字符串的哈希算法,可能的取值包括 des、xdes、md5 以及 bf。
对于 xdes 和 bf 算法,iter_count 参数用于指定迭代的次数。迭代次数越多,计算的时间越长,破解所需的时间也越长。过高的迭代次数可能使得计算一个哈希值需要几年的时间,但是这并没有什么实际用途。如果忽略 iter_count,将会使用默认的迭代次数。
算法 默认次数 最小次数 最大次数 xdes 725 1 16777215 bf 6 4 31 对于 xdes 算法,迭代次数必须是一个奇数。
如果想要选择一个合适的迭代次数,可以参考原始 DES 加密算法设计时的性能是在当时的硬件上每秒执行 4 次加密。每秒少于 4 次加密可能会降低可用性,每秒多于 100 次加密又可能太快了。
下表给出了不同哈希算法的相对性能比较。表中还列出了它们遍历所有由 8 字符组成的密码所需的时间,密码只包含小写字母、或者大小写字母及数字。 对于 crypt-bf 算法,斜杠后面的数字代表了 gen_salt() 函数中的 iter_count 参数。
算法 哈希次数/秒 [a-z] [A-Za-z0-9] 相当于 MD5 消耗的时间倍数 crypt-bf/8 1792 4 年 3927 年 100k crypt-bf/7 3648 2 年 1929 年 50k crypt-bf/6 7168 1 年 982 年 25k crypt-bf/5 13504 188 天 521 年 12.5k crypt-md5 171584 15 天 41 年 1k crypt-des 23221568 157.5 分钟 108 天 7 sha1 37774272 90 分钟 68 天 4 md5 (hash) 150085504 22.5 分钟 17 天 1 备注:
-
以上测试使用的机器是 Intel Mobile Core i3。
-
crypt-des 和 crypt-md5 算法的结果来自 John the Ripper v1.6.38 -test 结果。
-
md5 哈希的结果来自 mdcrack 1.2。
-
sha1 的结果来自 lcrack-20031130-beta。
-
crypt-bf 的结果通过简单遍历 1000 次 8 字符组成的密码得到。这种方式可以比较不同迭代次数的性能。以下结果可以作为参考:john -test 显示 crypt-bf/5 每秒循环 13506 次(结果中的细微差异说明 pgcrypto 中的 crypt-bf 实现和 John the Ripper 相同)。
-
- 示例
-
以下是使用
gen_salt()
函数不同算法的输出对比:seaboxsql=# SELECT gen_salt('des'), gen_salt('xdes'), gen_salt('md5'), gen_salt('bf'); gen_salt | gen_salt | gen_salt | gen_salt ----------+-----------+-------------+------------------------------- aK | _J9..iz8C | $1$bHg.9MOF | $2a$06$ZZUVLgMTsARiL4bEza/GdO (1 row)
每种算法生成的 salt 拥有固定的格式,例如 bf 算法结果中的 2a06$,2a 表示 Blowfish 的 2a 变种算法,06 表示迭代的次数。
双向加密—PGP 加密函数¶
PGP 加密函数实现了 OpenPGP(RFC 4880)标准中的加密功能,包括对称密钥加密(私钥加密)和非对称密钥加密(公钥加密)。
一个加密后的 PGP 消息由 2 部分(包)组成:
-
一个包含会话密钥(加密后的对称密钥或者公钥)的包;
-
一个使用会话密钥对数据加密后的包。
对于对称密钥(也就是口令)加密:
-
使用 String2Key(S2K)算法对密钥进行加密,类似于执行一个特意减慢并且包含随机 salt 的 crypt() 算法,生成一个完整长度的二进制密钥。
-
如果要求使用一个单独的会话密钥,生成一个随机的密钥;否则,使用上面的 S2K 密钥直接作为会话密钥。
-
如果直接使用 S2K 密钥,只将 S2K 设置加入会话密钥包中;否则,使用 S2K 密钥对会话密钥进行加密,然后放入会话密码包中。
对于公钥加密:
-
生成一个随机的会话密钥。
-
使用公钥对其进行加密后放入会话密钥包中。
无论哪种情况,对于数据的加密过程如下:
-
执行可选的数据操作:压缩、转换为 UTF-8 以及/或者换行符的转换。
-
在数据前面增加一个随机字节组成的块,相当于使用了一个随机的初始值(IV)。
-
计算随机前缀和数据的 SHA1 哈希值,追加到数据的后面。
-
将所有内容使用会话密钥进行加密后放入数据包中。
pgp_sym_encrypt()
¶
- 语法
sql pgp_sym_encrypt(data text, psw text [, options text ]) returns bytea pgp_sym_encrypt_bytea(data bytea, psw text [, options text ]) returns bytea
- 返回值
- bytea
- 描述
-
pgp_sym_encrypt()
函数用于对称密钥加密。其中,data 是要加密的数据;psw 是 PGP 对称密钥;options 参数用于设置选项,参考下文。
pgp_sym_decrypt()
¶
- 语法
sql pgp_sym_decrypt(msg bytea, psw text [, options text ]) returns text pgp_sym_decrypt_bytea(msg bytea, psw text [, options text ]) returns bytea
- 返回值
- bytea
- 描述
-
pgp_sym_decrypt()
函数用于解密 PGP 对称密钥加密后的消息。其中,msg 是要解密的消息;psw 是 PGP 对称密钥;options 参数用于设置选项,参考下文。为了避免输出无效的字符,不允许使用 pgp_sym_decrypt 函数对 bytea 数据进行解密;可以使用 pgp_sym_decrypt_bytea 对原始文本数据进行解密。
pgp_pub_encrypt()
¶
- 语法
sql pgp_pub_encrypt(data text, key bytea [, options text ]) returns bytea pgp_pub_encrypt_bytea(data bytea, key bytea [, options text ]) returns bytea
- 返回值
- bytea
- 描述
-
pgp_pub_encrypt()
函数用于公共密钥加密。其中,data 是要加密的数据;key 是 PGP 公钥,如果传入一个私钥将会返回错误;options 参数用于设置选项,参考下文。
pgp_pub_decrypt()
¶
- 语法
sql pgp_pub_decrypt(msg bytea, key bytea [, psw text [, options text ]]) returns text pgp_pub_decrypt_bytea(msg bytea, key bytea [, psw text [, options text ]]) returns bytea
- 返回值
text|bytea
- 描述
-
pgp_pub_decrypt()
函数用于解密 PGP 公共密钥加密后的消息.其中,key 是公共密钥对应的私钥;如果私钥使用了密码保护功能,必须在 psw 参数中指定密码;如果没有使用密码保护,想要指定 options 参数时必须指定一个空的 psw。options 参数用于设置选项,参考下文。为了避免输出无效的字符,不允许使用 pgp_pub_decrypt 函数对 bytea 数据进行解密;可以使用 pgp_pub_decrypt_bytea 对原始文本数据进行解密。
pgp_key_id()
¶
- 语法
sql pgp_key_id(bytea) returns text
- 返回值
- text
- 描述
-
pgp_key_id()
函数用于提取 PGP 公钥或者私钥的密钥 ID;如果传入一个加密后的消息,将会返回加密该消息使用的密钥 ID。该函数可能返回 2 个特殊的密钥 ID:
-
SYMKEY,表明该消息使用对称密钥进行加密。
-
ANYKEY,表明该消息使用公共密钥进行加密,但是密钥 ID 已经被删除。这也意味着需要尝试所有的私钥,查找可以解密该消息的私钥。pgcrypto 不会产生这种加密消息。
注意,不同的密钥可能拥有相同的 ID,这种情况很少见但可能存在。客户端应用程序需要自己尝试使用不同的密钥进行解密,就像处理 ANYKEY 一样。
-
armor()
¶
- 语法
-
``` sql armor(data bytea [ , keys text[], values text[] ]) returns text
```
- 返回值
- text
- 描述
-
armor()
函数用于将二进制数据转换为 PGP ASCII-armor 格式,相当于 Base64 加上 CRC 以及额外的格式化。其中,data 是需要转换的数据;如果指定了 keys 和 values 数值,每个 key/value 对都会生成一个 armor header 并添加到编码格式中;两个数组都是一维数组,长度相同,并且不能包含非 ASCII 字符。
dearmor()
¶
- 语法
sql dearmor(data text) returns bytea
- 返回值
- bytea
- 描述
dearmor()
函数是armor()
的逆转换。
pgp_armor_headers()
¶
- 语法
sql pgp_armor_headers(data text, key out text, value out text) returns setof record
- 返回值
- setof record
- 描述
pgp_armor_headers()
函数用于返回数据中的 armor header。返回结果是一个包含 key 和 value 两个字段的数据行集,如果其中包含任何非 ASCII 字符,都会被看作 UTF-8 字符。
PGP 加密函数使用示例¶
下面我们来看一个实例:
- 首先为 users 表增加一个信用卡字段:
seaboxsql=# ALTER TABLE users ADD COLUMN card bytea;
ALTER TABLE
seaboxsql=# select * from users;
id | username | password | card
----+----------+------------------------------------+------
4 | anne | $1$AntKXnNp$eMXf9Txryu9jhP/mTwxsY/ |
2 | anne | $1$VB3DSija$s53Jy1JTKwjvcszHqu5mm. |
6 | anne | $1$/Pt7jFRw$3i81/byMiq0RbxQIDAjqY0 |
3 | tony | $1$HpD13yT7$1MMQP7BK1oErbRHrovUBB1 |
1 | tony | $1$MtT1/gF5$GucU5TbvtUOBxw2/HsNTS. |
5 | tony | $1$1lRLUeKb$hC9JN9JN56oXsT3SsTGFd1 |
(6 rows)
- 然后我们需要生成 PGP 密钥,对于 Linux 操作系统可以使用 gpg 工具。执行以下命令创建一个新的密钥:
[root@mpp1 ~]# gpg --gen-key
gpg (GnuPG) 2.0.22; Copyright (C) 2013 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
gpg: 已创建目录‘/root/.gnupg’
gpg: 新的配置文件‘/root/.gnupg/gpg.conf’已建立
gpg: 警告:在‘/root/.gnupg/gpg.conf’里的选项于此次运行期间未被使用
gpg: 钥匙环‘/root/.gnupg/secring.gpg’已建立
gpg: 钥匙环‘/root/.gnupg/pubring.gpg’已建立
请选择您要使用的密钥种类:
(1) RSA and RSA (default)
(2) DSA and Elgamal
(3) DSA (仅用于签名)
(4) RSA (仅用于签名)
您的选择? 2
DSA 密钥长度应在 1024 位与 3072 位之间。
您想要用多大的密钥尺寸?(2048)
您所要求的密钥尺寸是 2048 位
请设定这把密钥的有效期限。
0 = 密钥永不过期
<n> = 密钥在 n 天后过期
<n>w = 密钥在 n 周后过期
<n>m = 密钥在 n 月后过期
<n>y = 密钥在 n 年后过期
密钥的有效期限是?(0) 0
密钥永远不会过期
以上正确吗?(y/n)y
You need a user ID to identify your key; the software constructs the user ID
from the Real Name, Comment and Email Address in this form:
"Heinrich Heine (Der Dichter) <heinrichh@duesseldorf.de>"
真实姓名:boush.wang
电子邮件地址:boush.wang@seaboxdata.com
注释:wbxian
您选定了这个用户标识:
“boush.wang (wbxian) <boush.wang@seaboxdata.com>”
更改姓名(N)、注释(C)、电子邮件地址(E)或确定(O)/退出(Q)?O
您需要一个密码来保护您的私钥。
我们需要生成大量的随机字节。这个时候您可以多做些琐事(像是敲打键盘、移动
鼠标、读写硬盘之类的),这会让随机数字发生器有更好的机会获得足够的熵数。
gpg: WARNING: some OpenPGP programs can't handle a DSA key with this digest size
我们需要生成大量的随机字节。这个时候您可以多做些琐事(像是敲打键盘、移动
鼠标、读写硬盘之类的),这会让随机数字发生器有更好的机会获得足够的熵数。
gpg: /root/.gnupg/trustdb.gpg:建立了信任度数据库
gpg: 密钥 8D01FD90 被标记为绝对信任
公钥和私钥已经生成并经签名。
gpg: 正在检查信任度数据库
gpg: 需要 3 份勉强信任和 1 份完全信任,PGP 信任模型
gpg: 深度:0 有效性: 1 已签名: 0 信任度:0-,0q,0n,0m,0f,1u
pub 2048D/8D01FD90 2023-03-06
密钥指纹 = DBC1 3F84 4D17 C352 D4BE E5CD 73E4 ED94 8D01 FD90
uid boush.wang (wbxian) <boush.wang@seaboxdata.com>
sub 2048g/1F64AFA0 2023-03-06
然后按照提示输入相关信息。推荐使用 DSA and Elgamal 密钥;对于 RSA 加密,必须创建一个仅用于签名的 DSA 或者 RSA 密钥作为主控密钥,然后使用 gpg –edit-key 增加一个 RSA 加密子密钥。
注意:请牢记此处输入的密码。
- 使用 gpg –list-secret-keys 查看创建的密钥
[root@mpp1 ~]# gpg --list-secret-keys
/root/.gnupg/secring.gpg
------------------------
sec 2048D/8D01FD90 2023-03-06
uid boush.wang (wbxian) <boush.wang@seaboxdata.com>
ssb 2048g/1F64AFA0 2023-03-06
其中,2048D 是密钥的比特长度, 8D01FD90 是私钥,1F64AFA0 是公钥。
- 将公钥和私钥转换为 ASCII-armor 格式
[root@mpp1 ~]# gpg -a --export 1F64AFA0 >public.key
[root@mpp1 ~]# gpg -a --export-secret-keys 8D01FD90 >secret.key
其中,-a 表示 armour 格式;默认的密钥是二进制格式,不方便处理。在使用 pgcrypto PGP 加密/解密函数时需要利用 dearmor() 函数将密钥转换为二进制再传入参数;如果可以直接处理二进制数据,也可以去掉 -a 选项。
- 直接查看公钥 public.key和私钥secret.key 的内容如下
[root@mpp1 ~]# cat public.key
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v2.0.22 (GNU/Linux)
... ...
-----END PGP PUBLIC KEY BLOCK-----
[root@mpp1 ~]# cat secret.key
-----BEGIN PGP PRIVATE KEY BLOCK-----
Version: GnuPG v2.0.22 (GNU/Linux)
... ...
-----END PGP PRIVATE KEY BLOCK-----
- 创建一个存储公钥的表 keys以方便处理,把 public.key 的内容插入到keys表中。
seaboxsql=# CREATE TABLE keys(v text);
CREATE TABLE
seaboxsql=#
seaboxsql=#
seaboxsql=#
seaboxsql=#
seaboxsql=# INSERT INTO keys VALUES ('-----BEGIN PGP PUBLIC KEY BLOCK-----
seaboxsql'# Version: GnuPG v2.0.22 (GNU/Linux)
seaboxsql'#
seaboxsql'# ... ...
seaboxsql'# -----END PGP PUBLIC KEY BLOCK-----');
INSERT 0 1
- 将信用卡号进行加密存储
seaboxsql=# UPDATE users SET card = pgp_pub_encrypt('62220001', dearmor(keys.v)) FROM keys WHERE username = 'tony';
UPDATE 3
seaboxsql=# SELECT card FROM users WHERE username = 'tony';
card
-----------------------------
\xc1c14e03 ... ... 5cbfb14917c
\xc1c14e03 ... ... 8657c9098c1
\xc1c14e03 ... ... 9bdfbf14860
(3 rows)
查询结果显示 card 字段已经被加密存储。
- 可以使用 pgp_key_id() 函数验证加密使用的公钥.
seaboxsql=# SELECT pgp_key_id(card) FROM users WHERE username = 'tony';
pgp_key_id
------------------
EFF48C221F64AFA0
EFF48C221F64AFA0
EFF48C221F64AFA0
(3 rows)
seaboxsql=# SELECT pgp_key_id(dearmor(v)) FROM keys;
pgp_key_id
------------------
EFF48C221F64AFA0
(1 row)
- 应用程序可以通过私钥 secret.key 解密信用卡号
seaboxsql=# SELECT pgp_pub_decrypt(card, dearmor('-----BEGIN PGP PRIVATE KEY BLOCK-----
seaboxsql'# Version: GnuPG v2.0.22 (GNU/Linux)
seaboxsql'#
seaboxsql'# ... ...
seaboxsql'# -----END PGP PRIVATE KEY BLOCK-----
seaboxsql'# '), '******')
seaboxsql-# FROM users
seaboxsql-# WHERE username = 'tony';
pgp_pub_decrypt
-----------------
62220001
62220001
62220001
(3 rows)
其中'******'
是步骤2创建密钥时输入的口令。
注意,PGP 代码存在以下限制:
-
不支持签名。这也意味着它不会检查加密子密钥是否属于主控密钥。
-
不支持加密密钥作为主控密钥。
-
不支持多个子密钥。因此,不要使用常规 GPG/PGP 密钥作为 pgcrypto 加密密钥,而应该创建新的密钥。
PGP 函数选项¶
pgcrypto 函数中的选项名称和 GnuPG 类似,选项的值使用等号设置,每个选项使用逗号进行分隔。例如:
pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
除了 convert-crlf 之外,其他选项仅适用于加密函数。解密函数从 PGP 数据中获取参数。
最常设置的选项包括 compress-algo 和 unicode-mode,其他选项通常使用默认值。
cipher-algo
-
使用的密码算法。
- 取值
- bf、aes128(默认值)、aes192、aes256;使用 OpenSSL 时还支持:3des、cast5
- 适用函数
- pgp_sym_encrypt、pgp_pub_encrypt
compress-algo
-
使用的压缩算法。
- 取值
- 0,不压缩,默认值;1,ZIP 压缩;2,ZLIB 压缩(ZIP 加上元数据和 CRC)
- 适用函数
- pgp_sym_encrypt、pgp_pub_encrypt
compress-level
-
压缩级别,级别越高结果越小但速度更慢,0 表示不压缩。
- 取值
- 0、1-9,默认为 6
- 适用函数
- pgp_sym_encrypt、pgp_pub_encrypt
convert-crlf
-
加密时是否将
\n
转换为\r\n
并且解密时执行相反的转换,RFC 4880 指定文本数据需要使用\r\n
作为换行符。- 取值
- 0(默认值)、1
- 适用函数
- pgp_sym_encrypt、pgp_pub_encrypt、pgp_sym_decrypt、pgp_pub_decrypt
disable-mdc
-
不使用 SHA-1 保护数据,仅用于兼容古老的 PGP 产品。
- 取值
- 0(默认值)、1
- 适用函数
- pgp_sym_encrypt、pgp_pub_encrypt
sess-key
-
使用单独的会话密钥。公钥加密总是使用单独的会话密钥;该选项用于对称密钥加密,因为它默认直接使用 S2K 密钥。
- 取值
- 0、1-9,默认为 6
- 适用函数
- pgp_sym_encrypt
s2k-mode
-
使用的 S2K 算法。
- 取值
- 0,不使用 salt,危险;1,使用 salt 但是迭代固定次数;3(默认值),使用 salt 同时迭代次数可变。
- 适用函数
- pgp_sym_encrypt
s2k-count
-
S2K 算法的迭代次数。
- 取值
- 大于等于 1024 并且小于等于 65011712,默认为 65536 到 253952 之间的随机数。
- 适用函数
- pgp_sym_encrypt 并且 s2k-mode=3
s2k-digest-algo
-
S2K 计算时的摘要算法。
- 取值
- md5、sha1(默认值)
- 适用函数
- pgp_sym_encrypt
s2k-cipher-algo
-
加密单独会话密钥时使用的密码。
- 取值
- bf、aes、aes128、aes192、aes256,默认使用 cipher-algo 的算法。
- 适用函数
- pgp_sym_encrypt
unicode-mode
-
是否将文本数据在数据库内部编码和 UTF-8 之间来回转换。如果数据库已经是 UTF-8、不会执行转换,但是消息将被标记为 UTF-8;如果没有指定这个选项就不会被标记。
- 取值
- 0(默认值)、1
- 适用函数
- pgp_sym_encrypt、pgp_pub_encrypt
双向加密—原始加密函数¶
原始加密函数仅仅会对数据进行一次加密,不支持 PGP 加密的任何高级功能,因此存在以下主要问题:
-
直接将用户密钥作为加密密钥。
-
不提供任何完整性检查校验加密后的数据是否被修改。
-
需要用户自己关联所有的加密参数,包括初始值(IV)。
-
不支持文本数据。
因此,在引入了 PGP 加密之后,不建议使用这些原始加密函数:
encrypt(data bytea, key bytea, type text) returns bytea
decrypt(data bytea, key bytea, type text) returns bytea
encrypt_iv(data bytea, key bytea, iv bytea, type text) returns bytea
decrypt_iv(data bytea, key bytea, iv bytea, type text) returns bytea
其中,data 是需要加密的数据;type 用于指定加密方法。type 参数的语法如下:
algorithm [ - mode ] [ /pad: padding ]
其中 algorithm 的可能取值如下:
-
bf,Blowfish 算法
-
aes,AES 算法(Rijndael-128、-192 或者-256)
mode 的可能取值如下:
-
cbc,下一个块依赖于前一个块(默认值)
-
ecb,每个块独立加密(仅用于测试)
padding 的可能取值如下:
- pkcs,数据可以是任意长度(默认值)
- none,数据长度必须是密码块大小的倍数
例如,以下函数的加密结果相同:
encrypt(data, 'fooz', 'bf')
encrypt(data, 'fooz', 'bf-cbc/pad:pkcs')
对于函数 encrypt_iv 和 decrypt_iv,参数 iv 表示 CBC模式的初始值,ECB 模式忽略该参数。如果它的长度不是准确的块大小,可能会被截断或者使用 0 进行填充。对于没有该参数的两个函数,默认全部使用 0 填充。
双向加密—随机数据函数¶
gen_random_bytes()
¶
- 语法
sql gen_random_bytes(count integer) returns bytea
- 返回值
- bytea
- 描述
gen_random_bytes()
函数用于生成具有强加密性的随机字节. 其中,count 表示返回的字节数,取值从 1 到 1024。- 示例
- ``` sql
seaboxsql=# SELECT encode(gen_random_bytes(16), 'hex');
encode
0d292ec638c03bc6a92999aafc67ffb5 (1 row) ```
gen_random_bytes()
¶
- 语法
sql gen_random_uuid() returns uuid
- 返回值
- uuid
- 描述
gen_random_uuid()
函数用于返回一个 version 4 的随机 UUID。- 示例
- ``` sql
seaboxsql=# SELECT gen_random_uuid();
gen_random_uuid
6791aa0d-39d1-4bb9-a4b8-63ced465139b (1 row) ```
其他事项¶
pgcrypto 配置¶
SeaboxSQL数据库编译时使用了 OpenSSL 选项,PGP 加密函数可以支持更多的算法;同时公钥加密函数速度会更快,因为 OpenSSL 提供了优化的 BIGNUM 函数。下表比较了使用或者不使用 OpenSSL 时支持的功能:
支持功能 | 内置 | OpenSSL |
---|---|---|
MD5 | YES | YES |
SHA1 | YES | YES |
SHA224/256/384/512 | YES | YES |
其他摘要算法 | NO | YES |
Blowfish | YES | YES |
AES | YES | YES |
DES/3DES/CAST5 | NO | YES |
原始加密 | YES | YES |
PGP 对称加密 | YES | YES |
PGP 公钥加密 | YES | YES |
NULL 处理¶
所有函数都遵循 SQL 表中,如果任何参数为 NULL,结果返回 NULL。如果使用时不小心,可能会造成安全风险。
安全限制¶
所有的 pgcrypto 函数都在数据库服务器中运行,意味着数据和密码在客户端和 pgcrypto 之间使用明文进行传输。因此必须:
-
使用本地连接或者 SSL 连接;
-
信任系统管理员和数据库管理员。
如果无法做到以上两点,一个更好的方式就是在客户端应用程序中完成加密/解密。
另外,pgcrypto 的实现无法抵抗旁路攻击(Side Channel Attacks)。例如,对于指定大小的不同密文,pgcrypto 解密函数所需的时间不同。