Postgresql 中文操作指南
F.28. pgcrypto — cryptographic functions #
pgcrypto 模块为 PostgreSQL 提供加密功能。
此模块被认为是“受信任的”,也就是说,它可以由在当前数据库上具有 CREATE 权限的非超级用户安装。
pgcrypto 需要 OpenSSL,如果 PostgreSQL 构建时未选择 OpenSSL 支持,则不会安装。
F.28.1. General Hashing Functions #
F.28.1.1. digest() #
digest(data text, type text) returns bytea
digest(data bytea, type text) returns bytea
计算给定 data 的二进制哈希。 type 是要使用的算法。标准算法是 md5、sha1、sha224、sha256、sha384 和 sha512。此外,OpenSSL 支持的任何摘要算法都会自动选择。
如果您希望摘要为十六进制字符串,请对结果使用 encode()。例如:
CREATE OR REPLACE FUNCTION sha1(bytea) returns text AS $$
SELECT encode(digest($1, 'sha1'), 'hex')
$$ LANGUAGE SQL STRICT IMMUTABLE;
F.28.1.2. hmac() #
hmac(data text, key text, type text) returns bytea
hmac(data bytea, key bytea, type text) returns bytea
使用密钥 key 计算 data 的哈希 MAC。type 与 digest() 中相同。
这与 digest() 类似,但只有知道密钥才能重新计算哈希。这可以防止有人更改数据并更改哈希以匹配的情况。
如果密钥大于哈希块大小,则会先对其进行哈希处理,并将结果用作密钥。
F.28.2. Password Hashing Functions #
函数 crypt() 和 gen_salt() 是专门为散列密码而设计的。crypt() 执行散列处理,gen_salt() 为其准备算法参数。
crypt() 中的算法与通常的 MD5 或 SHA1 哈希算法在以下方面有所不同:
Table F.18 列出 crypt() 函数支持的算法。
Table F.18. Supported Algorithms for crypt()
Algorithm |
Max Password Length |
Adaptive? |
Salt Bits |
Output Length |
Description |
bf |
72 |
yes |
128 |
60 |
Blowfish-based, variant 2a |
md5 |
unlimited |
no |
48 |
34 |
MD5-based crypt |
xdes |
8 |
yes |
24 |
20 |
Extended DES |
des |
8 |
no |
12 |
13 |
Original UNIX crypt |
F.28.2.1. crypt() #
crypt(password text, salt text) returns text
计算 password 的 crypt(3) 风格哈希。在存储新密码时,您需要使用 gen_salt() 生成新的 salt 值。要检查密码,请将存储的哈希值传递为 salt,并测试结果是否与存储的值匹配。
设置新密码的示例:
UPDATE ... SET pswhash = crypt('new password', gen_salt('md5'));
验证的示例:
SELECT (pswhash = crypt('entered password', pswhash)) AS pswmatch FROM ... ;
如果输入的密码正确,则会返回 true。
F.28.2.2. gen_salt() #
gen_salt(type text [, iter_count integer ]) returns text
生成新的随机 salt 字符串,以便在 crypt() 中使用。salt 字符串也告诉 crypt() 使用哪种算法。
type 参数指定哈希算法。可接受的算法类型有:des、xdes、md5 和 bf。
对于拥有算法的算法,iter_count 参数允许用户指定迭代计数。计数越高,散列密码所需时间就越长,因此破解密码所需时间也越长。尽管如果计数太高,则计算哈希值所需时间可能长达数年——这有点不切实际。如果省略 iter_count 参数,则使用默认迭代计数。iter_count 的允许值取决于算法,并在 Table F.19 中显示。
Table F.19. Iteration Counts for crypt()
Algorithm |
Default |
Min |
Max |
xdes |
725 |
1 |
16777215 |
bf |
6 |
4 |
31 |
对 xdes 还有一个附加限制,即迭代次数必须为奇数。
要挑选合适的迭代次数,请考虑最初的 DES 加密设计是为了在当时的硬件上每秒哈希处理 4 次。每秒哈希处理速度低于 4 次可能会影响易用性。每秒哈希处理速度快于 100 次可能太快。
Table F.20 给出了不同哈希算法相对慢速的概览。该表格显示了尝试密码中所有字符组合所需的时间,假设密码仅包含小写字母,或大写和小写字母和数字。在 crypt-bf 条目中,斜杠后面的数字是 gen_salt 中的 iter_count 参数。
Table F.20. Hash Algorithm Speeds
Algorithm |
Hashes/sec |
For [a-z] |
For [A-Za-z0-9] |
相对于 md5 hash 的持续时间 |
crypt-bf/8 |
1792 |
4 years |
3927 years |
100k |
crypt-bf/7 |
3648 |
2 years |
1929 years |
50k |
crypt-bf/6 |
7168 |
1 year |
982 years |
25k |
crypt-bf/5 |
13504 |
188 days |
521 years |
12.5k |
crypt-md5 |
171584 |
15 days |
41 years |
1k |
crypt-des |
23221568 |
157.5 minutes |
108 days |
7 |
sha1 |
37774272 |
90 minutes |
68 days |
4 |
md5 (hash) |
150085504 |
22.5 minutes |
17 days |
1 |
备注:
请注意,“尝试所有组合”并不是一项现实的操作。通常使用字典来破解密码,字典中包含常规词语以及各种变体。因此,甚至一些单词般的密码也可以比上述数字表明的更快地破解,而 6 个字符的非单词般的密码可能无法破解。有时可以破解。
F.28.3. PGP Encryption Functions #
此处的函数实现 OpenPGP ( RFC 4880) 标准的加密部分。支持对称密钥和公钥加密。
加密的 PGP 消息包含 2 部分或 packets:
使用对称密钥(即密码)加密时:
使用公钥加密时:
在任何情况下,要加密的数据都将经过以下处理:
F.28.3.1. pgp_sym_encrypt() #
pgp_sym_encrypt(data text, psw text [, options text ]) returns bytea
pgp_sym_encrypt_bytea(data bytea, psw text [, options text ]) returns bytea
使用对称 PGP 密钥对 data 进行加密 psw。options 参数可以包含选项设置,如下所述。
F.28.3.2. pgp_sym_decrypt() #
pgp_sym_decrypt(msg bytea, psw text [, options text ]) returns text
pgp_sym_decrypt_bytea(msg bytea, psw text [, options text ]) returns bytea
对使用对称密钥加密的 PGP 消息进行解密。
不允许使用 pgp_sym_decrypt 对 bytea 数据进行解密。这是为了避免输出无效字符数据。可以使用 pgp_sym_decrypt_bytea 对原始文本数据进行解密。
options 参数可以包含选项设置,如下所述。
F.28.3.3. pgp_pub_encrypt() #
pgp_pub_encrypt(data text, key bytea [, options text ]) returns bytea
pgp_pub_encrypt_bytea(data bytea, key bytea [, options text ]) returns bytea
使用公用 PGP 密钥 key 对 data 进行加密。将密钥函数应用于私有密钥会导致错误。
options 参数可以包含选项设置,如下所述。
F.28.3.4. pgp_pub_decrypt() #
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
对使用公钥加密的消息进行解密。key 必须是对应于用于加密的公钥的私有密钥。如果私有密钥受密码保护,则必须在 psw 中提供密码。如果没有密码,但需要指定选项,则需要提供空密码。
不允许使用 pgp_pub_decrypt 解密 bytea 数据。这是为了避免输出无效的字符数据。使用 pgp_pub_decrypt_bytea 解密最初的文本数据是正常的。
options 参数可以包含选项设置,如下所述。
F.28.3.5. pgp_key_id() #
pgp_key_id(bytea) returns text
pgp_key_id 提取 PGP 公钥或私钥的密钥 ID。如果给定加密的消息,它还会给出自该加密消息的密钥 ID。
它可以返回 2 个特殊密钥 ID:
请注意,不同的密钥可能有相同的 ID。这种情况较少见,但很正常。然后,客户端应用程序应尝试使用每个密钥进行解密,以查看哪个密钥合适——就像处理 ANYKEY 一样。
F.28.3.6. armor(), dearmor() #
armor(data bytea [ , keys text[], values text[] ]) returns text
dearmor(data text) returns bytea
这些函数将二进制数据包装/解包到 PGP ASCII 盔甲格式中,这基本上是带有 CRC 和附加格式的 Base64。
如果指定了 keys 和 values 数组,则会将 armor header 添加到每个键/值对的装甲格式中。两个数组都必须是单维的,而且长度必须相同。键和值不能包含任何非 ASCII 字符。
F.28.3.7. pgp_armor_headers #
pgp_armor_headers(data text, key out text, value out text) returns setof record
pgp_armor_headers() 从 data 提取盔甲头。返回值是一组包含两列(键和值)的行。如果键或值包含任何非 ASCII 字符,则它们将被视为 UTF-8。
F.28.3.8. Options for PGP Functions #
选项的命名与 GnuPG 类似。选项的值应在等号后面给出;用逗号将选项相互分开。例如:
pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
除 convert-crlf 之外的所有选项仅适用于加密函数。解密函数从 PGP 数据获取参数。
最有趣的选项可能是 compress-algo 和 unicode-mode。其余的选项应具有合理的默认值。
要使用的密码算法。
值:bf、aes128、aes192、aes256、3des、cast5 默认值:aes128 适用于:pgp_sym_encrypt、pgp_pub_encrypt
要使用的压缩算法。仅当 PostgreSQL 是使用 zlib 构建时才可用。
值: 0 - 无压缩 1 - ZIP 压缩 2 - ZLIB 压缩(= ZIP 加上元数据和块 CRC) 默认值:0 适用于:pgp_sym_encrypt、pgp_pub_encrypt
要压缩的程度。压缩等级越高,压缩后体积越小,但速度也越慢。0 表示禁用压缩。
值:0、1-9 默认值:6 适用于:pgp_sym_encrypt、pgp_pub_encrypt
在加密时将 \n 转换为 \r\n,在解密时将 \r\n 转换为 \n。RFC 4880 规定应使用 \r\n 行尾来存储文本数据。使用此项以获得完全符合 RFC 的行为。
值:0、1 默认值:0 适用于:pgp_sym_encrypt、pgp_pub_encrypt、pgp_sym_decrypt、pgp_pub_decrypt
不使用 SHA-1 来保护数据。使用此选项的唯一充分理由是与早期 PGP 产品兼容,那些产品在 RFC 4880 中添加受 SHA-1 保护的数据包之前就已上市。最近的 gnupg.org 和 pgp.com 软件完全支持此项。
值:0、1 默认值:0 适用于:pgp_sym_encrypt、pgp_pub_encrypt
使用独立会话密钥。公钥加密始终使用独立会话密钥;此选项适用于对称密钥加密,默认情况下直接使用 S2K 密钥。
值: 0、1 默认值: 0 适用于: pgp_sym_encrypt
要使用的 S2K 算法。
值: 0 - 无盐。 危险! 1 - 有盐,但具有固定的迭代次数。 3 - 可变迭代次数。 默认值: 3 适用于: pgp_sym_encrypt
要使用的 S2K 算法的迭代次数。它必须是 1024 到 65011712(含)之间的值。
默认值: 65536 到 253952 之间的随机值 仅适用于: pgp_sym_encrypt,且 s2k-mode=3
在 S2K 计算中要使用的摘要算法。
值: md5、sha1 默认值: sha1 适用于: pgp_sym_encrypt
用于加密独立的会话密钥的密码。
值: bf、aes、aes128、aes192、aes256 默认值: 使用加密算法 适用于: pgp_sym_encrypt
是否要将文本数据从数据库内部编码转换为 UTF-8 然后再转换回来。如果你的数据库已经是 UTF-8,则不会进行转换,但消息将被标记为 UTF-8。如果没有此选项,则它将不会被标记。
值:0、1 默认值:0 适用于:pgp_sym_encrypt、pgp_pub_encrypt
F.28.3.9. Generating PGP Keys with GnuPG #
要生成新密钥:
gpg --gen-key
首选的密钥类型是“DSA 和 Elgamal”。
对于 RSA 加密,你必须创建 DSA 或 RSA 单向签名密钥作为主密钥,然后使用 gpg --edit-key 添加 RSA 加密子密钥。
要列出密钥:
gpg --list-secret-keys
要以 ASCII 盔甲格式导出公钥:
gpg -a --export KEYID > public.key
要以 ASCII 盔甲格式导出私钥:
gpg -a --export-secret-keys KEYID > secret.key
在将这些密钥提供给 PGP 函数之前,你需要使用 dearmor() 对这些密钥进行处理。或者,如果你可以处理二进制数据,你可以从该命令中删除 -a。
更多详细信息,请参阅 man gpg、 The GNU Privacy Handbook 和 https://www.gnupg.org/ 中的其他文档。
F.28.3.10. Limitations of PGP Code #
F.28.4. Raw Encryption Functions #
这些函数仅在数据上运行一次密码,它们没有任何高级 PGP 加密功能。因此,它们有一些主要问题:
因此,随着 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
使用 type 指定的密码方法对数据进行加密/解密。type 字符串的语法为:
algorithm [ - mode ] [ /pad: padding ]
其中 algorithm 为以下之一:
且 mode 为以下之一:
且 padding 为以下之一:
因此,例如,以下内容是等效的:
encrypt(data, 'fooz', 'bf')
encrypt(data, 'fooz', 'bf-cbc/pad:pkcs')
在 encrypt_iv 和 decrypt_iv 中,iv 参数是 CBC 模式的初始值;对于 ECB,它将被忽略。如果不完全等于块大小,则使用零将其截取或填充。在没有此参数的函数中,它的默认值为全零。
F.28.5. Random-Data Functions #
gen_random_bytes(count integer) returns bytea
返回 count 个密码学上强大的随机字节。一次最多可以提取 1024 个字节。这是为了避免耗尽随机数生成器池。
gen_random_uuid() returns uuid
返回一个 4 版本(随机)的 UUID。(已过时,此函数内部调用同名的 core function 。)
F.28.6. Notes #
F.28.6.1. Configuration #
pgcrypto 根据主 PostgreSQL configure 脚本的结果配置自身。影响它的选项是 —with-zlib 和 —with-ssl=openssl。
使用 zlib 编译时,PGP 加密函数可以在加密前压缩数据。
pgcrypto 需要 OpenSSL。否则,它将不会被构建或安装。
当针对 OpenSSL 3.0.0 和更高版本编译时,必须在 openssl.cnf 配置文件中激活旧版提供程序才能使用 DES 或 Blowfish 等较旧的密码。
F.28.6.2. NULL Handling #
在 SQL 中是标准的,如果任何参数为 NULL,则所有函数将返回 NULL。在使用不当时,这可能会产生安全风险。
F.28.6.3. Security Limitations #
所有 pgcrypto 函数都在数据库服务器内部运行。这意味着所有数据和密码都在 pgcrypto 和客户端应用程序之间以明文传输。因此,您必须:
如果您不能,那么最好在客户端应用程序中进行加密。
该实现不抵抗 side-channel attacks. 例如, pgcrypto 解密函数完成所需时间随给定大小的密文而变化。