pgcrypto 模块为 PostgreSQL 提供了加密函数。
此模块被认为是“受信任的”,也就是说,拥有当前数据库的 CREATE 权限的非超级用户可以安装它。
pgcrypto 需要 OpenSSL,如果在构建 PostgreSQL 时未选择 OpenSSL 支持,则不会安装。
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;
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(),但哈希值只能在知道密钥的情况下重新计算。这防止了有人修改数据并更改哈希以匹配的情况。
如果密钥大于哈希块大小,它将首先被哈希,然后结果将用作密钥。
函数 crypt() 和 gen_salt() 专门用于哈希密码。crypt() 进行哈希处理,而 gen_salt() 为其准备算法参数。
crypt() 中的算法与通常的 MD5 或 SHA1 哈希算法在以下方面有所不同:
它们很慢。由于数据量非常小,这是使暴力破解密码变得困难的唯一方法。
它们使用一个随机值,称为盐,以便具有相同密码的用户将具有不同的加密密码。这也是一种额外的防御措施,可以防止算法被逆向。
它们在结果中包含算法类型,因此可以使用不同算法哈希的密码可以共存。
它们中的一些是自适应的 — 这意味着当计算机速度变快时,您可以调整算法使其变慢,而不会与现有密码不兼容。
表 F.17 列出了 crypt() 函数支持的算法。
表 F.17. crypt() 支持的算法
| 算法 | 最大密码长度 | 自适应? | 盐位 | 输出长度 | 描述 |
|---|---|---|---|---|---|
bf |
72 | 是 | 128 | 60 | 基于 Blowfish,变体 2a |
md5 |
无限 | 否 | 48 | 34 | 基于 MD5 的加密 |
xdes |
8 | 是 | 24 | 20 | 扩展 DES |
des |
8 | 否 | 12 | 13 | 原始 UNIX 加密 |
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。
gen_salt() #gen_salt(type text [, iter_count integer ]) returns text
生成一个新的随机盐字符串,用于 crypt()。盐字符串还告诉 crypt() 使用哪种算法。
type 参数指定哈希算法。接受的类型包括:des、xdes、md5 和 bf。
iter_count 参数允许用户指定迭代次数,适用于具有迭代次数的算法。计数越高,哈希密码所需的时间就越多,因此破解密码的时间就越多。虽然计数过高,计算哈希值的时间可能需要数年 — 这有点不切实际。如果省略 iter_count 参数,则使用默认迭代次数。iter_count 的允许值取决于算法,并在表 F.18 中显示。
表 F.18. crypt() 的迭代次数
| 算法 | 默认 | 最小 | 最大 |
|---|---|---|---|
xdes |
725 | 1 | 16777215 |
bf |
6 | 4 | 31 |
对于 xdes,还有一个额外的限制,即迭代次数必须是奇数。
要选择合适的迭代次数,请考虑原始 DES 加密设计为在当时的硬件上每秒有 4 个哈希值的速度。低于每秒 4 个哈希值的速度可能会降低可用性。高于每秒 100 个哈希值的速度可能太快。
表 F.19 概述了不同哈希算法的相对速度。该表显示了尝试 8 个字符密码中所有字符组合需要多少时间,假设密码仅包含小写字母或大小写字母和数字。在 crypt-bf 条目中,斜杠后的数字是 gen_salt 的 iter_count 参数。
表 F.19. 哈希算法速度
| 算法 | 哈希/秒 | 对于 [a-z] |
对于 [A-Za-z0-9] |
相对于 md5 哈希的持续时间 |
|---|---|---|---|---|
crypt-bf/8 |
1792 | 4 年 | 3927 年 | 10万 |
crypt-bf/7 |
3648 | 2 年 | 1929 年 | 5万 |
crypt-bf/6 |
7168 | 1 年 | 982 年 | 2.5万 |
crypt-bf/5 |
13504 | 188 天 | 521 年 | 1.25万 |
crypt-md5 |
171584 | 15 天 | 41 年 | 1千 |
crypt-des |
23221568 | 157.5 分钟 | 108 天 | 7 |
sha1 |
37774272 | 90 分钟 | 68 天 | 4 |
md5 (哈希) |
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 中使用的实现相同这一事实相符。)
请注意,“尝试所有组合”不是一个现实的练习。通常,密码破解是在字典的帮助下完成的,字典中包含常规单词和它们的各种变体。因此,即使是有点像单词的密码也可能比上述数字所暗示的破解速度快得多,而一个 6 个字符的非单词密码可能会逃脱破解。也可能不会。
此处的函数实现了 OpenPGP (RFC 4880) 标准的加密部分。支持对称密钥和公钥加密。
加密的 PGP 消息由 2 部分或数据包组成:
包含会话密钥的数据包 — 可以是对称密钥加密或公钥加密。
包含用会话密钥加密的数据的数据包。
当使用对称密钥(即密码)加密时
给定的密码使用 String2Key (S2K) 算法进行哈希处理。这与 crypt() 算法非常相似 — 有意地减慢速度并使用随机盐 — 但它会生成一个完整长度的二进制密钥。
如果请求单独的会话密钥,则会生成一个新的随机密钥。否则,S2K 密钥将直接用作会话密钥。
如果要直接使用 S2K 密钥,则只有 S2K 设置会被放入会话密钥数据包中。否则,会话密钥将使用 S2K 密钥加密并放入会话密钥数据包中。
当使用公钥加密时
会生成一个新的随机会话密钥。
它使用公钥加密并放入会话密钥数据包中。
在任何一种情况下,要加密的数据都按如下方式处理
可选的数据操作:压缩,转换为 UTF-8,和/或转换行尾符。
数据前缀有一个随机字节块。这等同于使用随机 IV。
附加随机前缀和数据的 SHA1 哈希值。
所有这些都用会话密钥加密并放入数据包中。
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 密钥 psw 加密 data。options 参数可以包含选项设置,如下所述。
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 参数可以包含选项设置,如下所述。
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 参数可以包含选项设置,如下所述。
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 参数可以包含选项设置,如下所述。
pgp_key_id() #pgp_key_id(bytea) returns text
pgp_key_id 提取 PGP 公钥或私钥的密钥 ID。 或者,如果给定加密消息,则返回用于加密数据的密钥 ID。
它可以返回 2 个特殊的密钥 ID
SYMKEY
该消息使用对称密钥加密。
ANYKEY
该消息是公钥加密的,但密钥 ID 已被删除。 这意味着您需要尝试使用所有私钥来查看哪个私钥可以解密它。 pgcrypto 本身不会生成此类消息。
请注意,不同的密钥可能具有相同的 ID。 这很罕见,但属于正常情况。 然后,客户端应用程序应尝试使用每个密钥进行解密,以查看哪个密钥匹配 — 就像处理 ANYKEY 一样。
armor(), dearmor() #armor(data bytea [ , keys text[], values text[] ]) returns text dearmor(data text) returns bytea
这些函数将二进制数据包装/解包为 PGP ASCII-armor 格式,这基本上是带有 CRC 和附加格式的 Base64。
如果指定了 keys 和 values 数组,则为每个键/值对向 armored 格式添加一个armor 标头。 两个数组都必须是一维的,并且它们的长度必须相同。 键和值不能包含任何非 ASCII 字符。
pgp_armor_headers #pgp_armor_headers(data text, key out text, value out text) returns setof record
pgp_armor_headers() 从 data 中提取 armor 标头。 返回值是具有两列(key 和 value)的行集。 如果键或值包含任何非 ASCII 字符,则将其视为 UTF-8。
选项的命名与 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。RFC4880 指定文本数据应使用 \r\n 换行符存储。 使用此设置可以获得完全符合 RFC 的行为。
值: 0, 1
默认值: 0
适用于: pgp_sym_encrypt, pgp_pub_encrypt, pgp_sym_decrypt, pgp_pub_decrypt
不使用 SHA-1 保护数据。 使用此选项的唯一合理理由是实现与旧版 PGP 产品的兼容性,这些产品早于将 SHA-1 保护的数据包添加到RFC4880。 最近的 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
用于加密单独会话密钥的密码。
值: bf, aes, aes128, aes192, aes256
默认值: 使用 cipher-algo
适用于: pgp_sym_encrypt
是否将文本数据从数据库内部编码转换为 UTF-8 再转换回来。 如果您的数据库已经是 UTF-8,则不会进行任何转换,但消息将被标记为 UTF-8。 如果没有此选项,则不会这样做。
值: 0, 1
默认值: 0
适用于: pgp_sym_encrypt, pgp_pub_encrypt
要生成新密钥
gpg --gen-key
首选的密钥类型是 “DSA 和 Elgamal”。
对于 RSA 加密,您必须创建 DSA 或仅 RSA 签名密钥作为主密钥,然后使用 gpg --edit-key 添加 RSA 加密子密钥。
要列出密钥
gpg --list-secret-keys
要以 ASCII-armor 格式导出公钥
gpg -a --export KEYID > public.key
要以 ASCII-armor 格式导出私钥
gpg -a --export-secret-keys KEYID > secret.key
您需要在将这些密钥提供给 PGP 函数之前,在这些密钥上使用 dearmor()。 或者,如果您可以处理二进制数据,则可以从命令中删除 -a。
有关更多详细信息,请参阅 man gpg、GNU 隐私手册以及 https://www.gnupg.org/ 上的其他文档。
不支持签名。 这也意味着不会检查加密子密钥是否属于主密钥。
不支持将加密密钥作为主密钥。 由于通常不鼓励这种做法,因此这应该不是问题。
不支持多个子密钥。 这可能看起来像是一个问题,因为这是常见的做法。 另一方面,您不应将常规 GPG/PGP 密钥与 pgcrypto 一起使用,而应创建新密钥,因为使用场景截然不同。
这些函数仅对数据执行加密操作,不具备 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
使用 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 模式,它会被忽略。如果不是块大小的精确倍数,它将被裁剪或用零填充。在没有此参数的函数中,默认为全零。
gen_random_bytes(count integer) returns bytea
返回 count 个加密强度高的随机字节。一次最多可提取 1024 个字节。这是为了避免耗尽随机数生成池。
gen_random_uuid() returns uuid
返回版本 4(随机)UUID。(已过时,此函数内部调用同名的核心函数。)
pgcrypto 会根据主 PostgreSQL configure 脚本的发现结果进行自我配置。影响它的选项是 --with-zlib 和 --with-ssl=openssl。
当使用 zlib 编译时,PGP 加密函数能够在加密之前压缩数据。
pgcrypto 需要 OpenSSL。否则,它将不会被构建或安装。
当针对 OpenSSL 3.0.0 及更高版本编译时,必须在 openssl.cnf 配置文件中激活旧版提供程序,才能使用较旧的密码(如 DES 或 Blowfish)。
按照 SQL 的标准,如果任何参数为 NULL,则所有函数都返回 NULL。这可能会在不小心使用时产生安全风险。
Marko Kreen <markokr@gmail.com>
pgcrypto 使用以下来源的代码:
| 算法 | 作者 | 来源 |
|---|---|---|
| DES crypt | David Burren 及其他 | FreeBSD libcrypt |
| MD5 crypt | Poul-Henning Kamp | FreeBSD libcrypt |
| Blowfish crypt | Solar Designer | www.openwall.com |
如果您在文档中发现任何不正确、与您特定功能的使用体验不符或需要进一步澄清的内容,请使用此表单报告文档问题。