Redrock Postgres 搜索 英文
版本: 9.3 / 9.4 / 9.5 / 9.6 / 10 / 11 / 12 / 13 / 14 / 15 / 16 / 17 / 18

F.26. pgcrypto — 加密函数 #

F.26.1. 通用哈希函数
F.26.2. 密码哈希函数
F.26.3. PGP 加密函数
F.26.4. 原始加密函数
F.26.5. 随机数据函数
F.26.6. OpenSSL 支持函数
F.26.7. 配置参数
F.26.8. 注意事项
F.26.9. 作者

pgcrypto 模块为 PostgreSQL 提供了加密函数。

此模块被认为是受信任的,这意味着非超级用户也可以在其拥有的数据库上安装它,前提是他们具有 CREATE 权限。

pgcrypto 需要 OpenSSL,如果 PostgreSQL 在构建时未选择 OpenSSL 支持,则不会安装它。

F.26.1. 通用哈希函数 #

F.26.1.1. digest() #

digest(data text, type text) returns bytea
digest(data bytea, type text) returns bytea

计算给定 data 的二进制哈希。 type 是要使用的算法。标准算法有 md5sha1sha224sha256sha384sha512。此外,OpenSSL 支持的任何摘要算法都会自动被拾取。

如果你想要十六进制字符串形式的摘要,请在结果上使用 encode()。例如:

CREATE OR REPLACE FUNCTION sha1(bytea) returns text AS $$
    SELECT encode(digest($1, 'sha1'), 'hex')
$$ LANGUAGE SQL STRICT IMMUTABLE;

F.26.1.2. hmac() #

hmac(data text, key text, type text) returns bytea
hmac(data bytea, key bytea, type text) returns bytea

使用密钥 key 计算 data 的哈希 MAC。 typedigest() 中的相同。

这与 digest() 类似,但如果不知道密钥,则无法重新计算哈希。这可以防止有人篡改数据并更改哈希以匹配的情况。

如果密钥大于哈希块大小,它将首先被哈希,然后结果将用作密钥。

F.26.2. 密码哈希函数 #

函数 crypt()gen_salt() 是专门为哈希密码设计的。 crypt() 执行哈希,而 gen_salt() 为其准备算法参数。

crypt() 中的算法与普通的 MD5 或 SHA-1 哈希算法在以下方面有所不同:

  1. 它们很慢。由于数据量很小,这是使密码暴力破解变得困难的唯一方法。

  2. 它们使用一个称为 salt 的随机值,以便具有相同密码的用户将具有不同的加密密码。这也是防止算法反转的额外安全措施。

  3. 它们将算法类型包含在结果中,因此可以用不同算法哈希的密码可以共存。

  4. 其中一些是自适应的 — 这意味着当计算机速度变快时,你可以调整算法使其变慢,而不会与现有密码不兼容。

表 F.18 列出了 crypt() 函数支持的算法。

表 F.18. crypt() 支持的算法

算法 最大密码长度 自适应? Salt 位数 输出长度 描述
bf 72 128 60 基于 Blowfish 的变体 2a
md5 无限制 48 34 基于 MD5 的 crypt
xdes 8 24 20 扩展 DES
des 8 12 13 原始 UNIX crypt
sha256crypt 无限制 最多 32 80 改编自公开可用的参考实现 使用 SHA-256 和 SHA-512 的 Unix crypt
sha512crypt 无限制 最多 32 123 改编自公开可用的参考实现 使用 SHA-256 和 SHA-512 的 Unix crypt

F.26.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.26.2.2. gen_salt() #

gen_salt(type text [, iter_count integer ]) returns text

crypt() 生成一个新的随机 salt 字符串。 salt 字符串还告诉 crypt() 使用哪种算法。

参数 type 指定哈希算法。可接受的类型有:desxdesmd5bfsha256cryptsha512crypt。后两种 sha256cryptsha512crypt 是基于 SHA-2 的现代密码哈希。

参数 iter_count 允许用户指定迭代次数(对于具有该参数的算法)。计数越高,哈希密码所需的时间就越长,因此破解密码所需的时间也越长。尽管计数过高可能需要数年时间才能计算出哈希值 — 这不太实用。如果省略 iter_count 参数,则使用默认的迭代次数。 iter_count 的允许值取决于算法,并在 表 F.19 中显示。

表 F.19. crypt() 的迭代次数

算法 默认 最小 最大
xdes 725 1 16777215
bf 6 4 31
sha256crypt, sha512crypt 5000 1000 999999999

对于 xdes,还有一个额外的限制,即迭代次数必须是奇数。

为了选择合适的迭代次数,请注意,原始 DES crypt 被设计成在当时硬件上每秒能进行 4 次哈希。每秒慢于 4 次哈希可能会影响可用性。每秒快于 100 次哈希可能太快了。

表 F.20 概述了不同哈希算法的相对速度。该表显示了尝试 8 个字符密码的所有字符组合所需的时间,假设密码只包含小写字母,或者包含大写字母、小写字母和数字。在 crypt-bf 条目中,斜杠后的数字是 gen_saltiter_count 参数。

sha256cryptsha512crypt 的默认 iter_count5000,这对于现代硬件来说太低了,但可以调整以生成更强的密码哈希。否则,这两种哈希 sha256cryptsha512crypt 都被认为是安全的。

表 F.20. 哈希算法速度

算法 每秒哈希次数 对于 [a-z] 对于 [A-Za-z0-9] 相对于 md5 hash 的持续时间
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-descrypt-md5 算法数字来自 John the Ripper v1.6.38 -test 输出。

  • md5 hash 数字来自 mdcrack 1.2。

  • sha1 数字来自 lcrack-20031130-beta。

  • crypt-bf 数字是通过一个简单的程序获得的,该程序循环遍历 1000 个 8 个字符的密码。这样就可以显示不同迭代次数的速度。供参考:john -test 显示 crypt-bf/5 的速度为 13506 次/秒。(结果之间非常小的差异与 pgcrypto 中的 crypt-bf 实现与 John the Ripper 中使用的实现相同这一事实一致。)

请注意,尝试所有组合 并不是一个现实的练习。通常密码破解是借助字典进行的,这些字典包含常规单词和它们的各种变体。因此,即使是看起来像单词的密码,破解速度也可能比上述数字快得多,而一个 6 个字符的非单词类密码可能会逃脱破解。或者不一定。

F.26.3. PGP 加密函数 #

这里的函数实现了 OpenPGP(RFC 4880)标准的加密部分。支持对称密钥和公钥加密。

PGP 加密消息由 2 部分组成,或称为

  • 包含会话密钥的包 — 加密方式为对称密钥或公钥加密。

  • 包含使用会话密钥加密的数据的包。

使用对称密钥(即密码)加密时:

  1. 给定的密码使用 String2Key (S2K) 算法进行哈希。这与 crypt() 算法相当相似 — 故意做得慢并带有随机 salt — 但它会生成一个全长二进制密钥。

  2. 如果请求单独的会话密钥,则会生成一个新的随机密钥。否则,S2K 密钥将直接用作会话密钥。

  3. 如果 S2K 密钥要直接使用,则只有 S2K 设置会放入会话密钥包。否则,会话密钥将使用 S2K 密钥进行加密,并放入会话密钥包。

使用公钥加密时:

  1. 生成一个新的随机会话密钥。

  2. 它使用公钥进行加密,并放入会话密钥包。

在任何一种情况下,要加密的数据都按如下方式处理:

  1. 可选的数据处理:压缩、转换为 UTF-8 和/或行尾转换。

  2. 数据前面会加上一个随机字节块。这相当于使用随机 IV。

  3. 随机前缀和数据的 SHA-1 哈希会被附加。

  4. 所有这些都使用会话密钥进行加密,并放入数据包。

F.26.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 密钥 psw 加密 dataoptions 参数可以包含选项设置,如下所述。

F.26.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.26.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.26.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.26.3.5. pgp_key_id() #

pgp_key_id(bytea) returns text

pgp_key_id 提取 PGP 公钥或私钥的密钥 ID。或者,如果给定加密消息,它会给出用于加密数据的密钥 ID。

它可以返回 2 个特殊的密钥 ID:

  • SYMKEY

    消息使用对称密钥加密。

  • ANYKEY

    消息是公钥加密的,但密钥 ID 已被移除。这意味着您需要尝试使用您所有的私钥来解密它,看看哪个有效。 pgcrypto 本身不会生成此类消息。

请注意,不同的密钥可能具有相同的 ID。这种情况很少见,但很正常。客户端应用程序应然后尝试用每个密钥解密,以查看哪个适合 — 就像处理 ANYKEY 一样。

F.26.3.6. armor(), dearmor() #

armor(data bytea [ , keys text[], values text[] ]) returns text
dearmor(data text) returns bytea

这些函数将二进制数据包装/解包到 PGP ASCII-armor 格式,它基本上是带有 CRC 和附加格式的 Base64。

如果指定了 keysvalues 数组,则会在 armor 格式中为每个键/值对添加一个 armor 头。两个数组都必须是单维的,并且必须具有相同的长度。键和值都不能包含任何非 ASCII 字符。

F.26.3.7. pgp_armor_headers #

pgp_armor_headers(data text, key out text, value out text) returns setof record

pgp_armor_headers()data 中提取 armor 头。返回值是一组行,包含两列:键和值。如果键或值包含任何非 ASCII 字符,则它们被视为 UTF-8。

F.26.3.8. PGP 函数的选项 #

选项的命名方式与 GnuPG 类似。选项的值应放在等号后面;使用逗号分隔选项。例如:

pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')

除了 convert-crlf 之外,所有选项仅适用于加密函数。解密函数从 PGP 数据中获取参数。

最有趣的选项可能是 compress-algounicode-mode。其余的应该有合理的默认值。

F.26.3.8.1. cipher-algo #

要使用的密码算法。


值: bf, aes128, aes192, aes256, 3des, cast5
默认值: aes128
适用于: pgp_sym_encrypt, pgp_pub_encrypt

F.26.3.8.2. compress-algo #

要使用的压缩算法。仅当 PostgreSQL 使用 zlib 构建时可用。



  0 - 不压缩
  1 - ZIP 压缩
  2 - ZLIB 压缩 (等于 ZIP 加上 元数据和 块 CRC)
默认值: 0
适用于: pgp_sym_encrypt, pgp_pub_encrypt

F.26.3.8.3. compress-level #

压缩程度。级别越高,压缩越小但速度越慢。0 禁用压缩。


值: 0, 1-9
默认值: 6
适用于: pgp_sym_encrypt, pgp_pub_encrypt

F.26.3.8.4. convert-crlf #

在加密时是否将 \n 转换为 \r\n,在解密时是否将 \r\n 转换为 \nRFC4880 规定文本数据应使用 \r\n 行尾符存储。使用此选项可获得完全符合 RFC 的行为。


值: 0, 1
默认值: 0
适用于: pgp_sym_encrypt, pgp_pub_encrypt, pgp_sym_decrypt, pgp_pub_decrypt

F.26.3.8.5. disable-mdc #

不要用 SHA-1 保护数据。使用此选项的唯一好理由是与 PGP 的旧版本兼容,这些版本在添加 SHA-1 保护的数据包之前。RFC4880。最近的 gnupg.org 和 pgp.com 软件可以很好地支持它。


值: 0, 1
默认值: 0
适用于: pgp_sym_encrypt, pgp_pub_encrypt

F.26.3.8.6. sess-key #

使用单独的会话密钥。公钥加密始终使用单独的会话密钥;此选项用于对称密钥加密,默认情况下它直接使用 S2K 密钥。


值: 0, 1
默认值: 0
适用于: pgp_sym_encrypt

F.26.3.8.7. s2k-mode #

要使用的 S2K 算法。



  0 - 无 salt。 危险!
  1 - 有 salt 但迭代次数固定。
  3 - 可变迭代次数。
默认值: 3
适用于: pgp_sym_encrypt

F.26.3.8.8. s2k-count #

要使用的 S2K 算法的迭代次数。它必须是介于 1024 和 65011712 之间的值(含)。


默认值: 介于 65536 和 253952 之间的随机值
适用于: pgp_sym_encrypt, 仅当 s2k-mode=3

F.26.3.8.9. s2k-digest-algo #

在 S2K 计算中要使用的摘要算法。


值: md5, sha1
默认值: sha1
适用于: pgp_sym_encrypt

F.26.3.8.10. s2k-cipher-algo #

用于加密单独的会话密钥的密码。


值: bf, aes, aes128, aes192, aes256
默认值: 使用 cipher-algo
适用于: pgp_sym_encrypt

F.26.3.8.11. unicode-mode #

是否将文本数据从数据库内部编码转换为 UTF-8,然后再转换回来。如果您的数据库已经是 UTF-8,则不会进行任何转换,但消息将被标记为 UTF-8。没有此选项则不会。


值: 0, 1
默认值: 0
适用于: pgp_sym_encrypt, pgp_pub_encrypt

F.26.3.9. 使用 GnuPG 生成 PGP 密钥 #

生成新密钥

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 gpgThe GNU Privacy Handbook 以及 https://www.gnupg.org/ 上的其他文档。

F.26.3.10. PGP 代码的局限性 #

  • 不支持签名。这也意味着不检查加密子密钥是否属于主密钥。

  • 不支持将加密密钥作为主密钥。由于这种做法通常不被推荐,所以这应该不是问题。

  • 不支持多个子密钥。这可能看起来像个问题,因为这是常见的做法。另一方面,您不应在 pgcrypto 中使用您的常规 GPG/PGP 密钥,而应创建新的密钥,因为使用场景大不相同。

F.26.4. 原始加密函数 #

这些函数仅对数据运行密码,它们不具备 PGP 加密的任何高级功能。因此,它们存在一些主要问题:

  1. 它们使用用户密钥直接作为密码密钥。

  2. 它们不提供任何完整性检查,以查看加密数据是否被修改。

  3. 它们期望用户自己管理所有加密参数,甚至包括 IV。

  4. 它们不处理文本。

因此,随着 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 — 下一个块取决于前一个块(默认)

  • cfb — 下一个块取决于前一个加密块

  • ecb — 每个块单独加密(仅用于测试)

padding 是以下之一:

  • pkcs — 数据可以是任何长度(默认)

  • none — 数据必须是密码块大小的倍数

所以,例如,以下是等效的:

encrypt(data, 'fooz', 'bf')
encrypt(data, 'fooz', 'bf-cbc/pad:pkcs')

encrypt_ivdecrypt_iv 中,iv 参数是 CBC 和 CFB 模式的初始值;对于 ECB,它被忽略。如果长度不正好是块大小,它会被截断或用零填充。在没有此参数的函数中,它默认为全零。

F.26.5. 随机数据函数 #

gen_random_bytes(count integer) returns bytea

返回 count 个加密强度高的随机字节。一次最多可以提取 1024 个字节。这是为了避免耗尽随机性生成器池。

gen_random_uuid() returns uuid

返回一个版本 4(随机)UUID。(已废弃,此函数内部调用同名的核心函数。)

F.26.6. OpenSSL 支持函数 #

fips_mode() returns boolean

如果 OpenSSL 以 FIPS 模式运行,则返回 true,否则返回 false

F.26.7. 配置参数 #

有一个配置参数控制 pgcrypto 的行为。

pgcrypto.builtin_crypto_enabled (enum) #

pgcrypto.builtin_crypto_enabled 确定内置加密函数 gen_salt()crypt() 是否可用。将其设置为 off 会禁用这些函数。 on (默认)使这些函数正常工作。 fips 会在检测到 OpenSSL 以 FIPS 模式运行时禁用这些函数。

在常规使用中,此参数在 postgresql.conf 中设置,尽管超级用户可以在其自己的会话中即时更改它。

F.26.8. 注意事项 #

F.26.8.1. 配置 #

pgcrypto 根据主 PostgreSQL configure 脚本的发现进行自我配置。影响它的选项是 --with-zlib--with-ssl=openssl

当使用 zlib 编译时,PGP 加密函数能够先压缩数据再加密。

pgcrypto 需要 OpenSSL。否则,它将不会被构建或安装。

当针对 OpenSSL 3.0.0 及更高版本进行编译时,为了使用 DES 或 Blowfish 等旧式密码,必须在 openssl.cnf 配置文件中激活旧式提供程序。

F.26.8.2. NULL 处理 #

按照 SQL 的标准,如果任何参数为 NULL,则所有函数都返回 NULL。这可能在粗心使用时产生安全风险。

F.26.8.3. 安全限制 #

所有 pgcrypto 函数都在数据库服务器内部运行。这意味着所有数据和密码都在明文中通过 pgcrypto 和客户端应用程序之间传输。因此,您必须:

  1. 本地连接或使用 SSL 连接。

  2. 信任系统和数据库管理员。

如果不能,则最好在客户端应用程序内部进行加密。

该实现不能抵抗侧信道攻击。例如,pgcrypto 解密函数完成所需的时间因给定大小的密文而异。

F.26.9. 作者 #

Marko Kreen

pgcrypto 使用以下来源的代码:

算法 作者 来源
DES crypt David Burren 和其他人 FreeBSD libcrypt
MD5 crypt Poul-Henning Kamp FreeBSD libcrypt
Blowfish crypt Solar Designer www.openwall.com