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

F.26. pgcrypto — 加密函数 #

F.26.1. 一般哈希函数
F.26.2. 密码哈希函数
F.26.3. PGP 加密函数
F.26.4. 原始加密函数
F.26.5. 随机数据函数
F.26.6. 备注
F.26.7. 作者

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

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

这类似于 digest(),但只能在知道密钥的情况下重新计算哈希。这可以防止有人篡改数据,并更改哈希以匹配的情况发生。

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

F.26.2. 密码散列函数 #

函数 crypt()gen_salt() 专门设计用于对密码散列。 crypt() 执行散列,而 gen_salt() 为其准备算法参数。

crypt() 中的算法与常见的 MD5 或 SHA1 散列算法在以下方面有所不同

  1. 它们的执行速度慢。由于数据的量非常小,这是唯一能使暴力破解密码困难的方法。

  2. 它们使用了一个称为 的随机值,这样,拥有相同密码的用户将具有不同的加密密码。这也是对抗逆向算法的额外防御措施。

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

  4. 其中一些具有自适应性,这意味着,当计算机运行速度提高时,您可以在不与现有密码引入不兼容性的情况下,调整算法以便更慢。

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

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

算法 最大密码长度 自适应? 盐位 输出长度 说明
bf 72 128 60 基于 Blowfish,变体 2a
md5 无限制 48 34 基于 MD5 的 crypt
xdes 8 24 20 扩展的 DES
des 8 12 13 最初的 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

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

type 参数指定哈希算法。可接受的类型有: desxdesmd5bf

对于具有一个 iteration 的算法,iter_count 参数允许用户指定 iteration 数。iteration 数越高,哈希化密码所花费的时间以及破解密码所需的时间就越长。虽然 iteration 数太高,计算哈希值可能需要几年时间 — 这有点不切实际。如果省略 iter_count 参数,将使用默认的 iteration 数。 iter_count 的允许值取决于算法,并显示在 crypt() 的 iteration 数">表 F.18 中。

表 F.18 crypt() 的 iteration 数

算法 默认值 最小值 最大值
xdes 725 1 16777215
bf 6 4 31

对于 xdes,还有一个附加限制,即 iteration 数必须是奇数。

要选取合适的 iteration 数,请考虑最初的 DES crypt 设计为每秒有 4 个哈希值的速度,利用当时的硬件。每秒低于 4 个哈希值可能会降低可用性。每秒高于 100 个哈希值可能太快了。

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

表 F.19. 哈希算法速度

算法 哈希数/秒 对于 [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 (哈希) 150085504 22.5 分钟 17 天 1

注释

  • 使用的机器是 Intel Mobile Core i3。

  • crypt-descrypt-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 个字符的非词状密码可能无法进行破译。或者有可能破译。

F.26.3. PGP 加密函数 #

此处功能实现 OpenPGP (RFC 4880) 标准的加密部分。支持对称密钥和公钥加密。

加密的 PGP 消息由 2 部分(或数据包)组成

  • 包含会话密钥(对称密钥或公钥加密)的数据包。

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

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

  1. 使用 String2Key (S2K) 算法对给定的密码进行哈希。这与crypt() 算法有点类似,故意放慢速度并添加随机盐,但它会生成全长二进制密钥。

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

  3. 如果 S2K 密钥要直接使用,那么只有 S2K 设置将放入会话密钥数据包中。否则,将使用 S2K 密钥加密会话密钥并将其放入会话密钥数据包中。

使用公钥加密时

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

  2. 使用公钥加密,并将加密结果放入会话密钥数据包中。

在任何情况下,加密的数据都将按如下方式处理

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

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

  3. 将随机前缀和数据的 SHA1 哈希附加到末尾。

  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 密钥 pswdata进行加密。 options 参数可以包含选项设置,如下所述。

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_decryptbytea 数据进行解密。这是为了避免输出无效的字符数据。使用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 密钥 keydata进行加密。给此函数一个私钥会产生一个错误。

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 铠甲格式中,该格式基本上是带有 CRC 和其他格式的 Base64。

如果指定了 keysvalues 数组,则为每个键/值对在铠甲格式中添加一个 铠甲头部。这两个数组必须是单维的,并且其长度必须相同。键和值不得包含任何非 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 中提取铠甲头部。返回值是一组包含两列(键和值)的行。如果键或值包含任何非 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 #

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



  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 产品实现兼容,在向 4880 中添加 SHA-1 保护的数据包之前。RFC最新的 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 gpgGNU 隐私手册,以及其他文档 https://www.gnupg.org/

F.26.3.10. PGP 代码的限制 #

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

  • 不支持加密密钥作为主密钥。由于这种做法通常不受鼓励,因此它不会成为问题。

  • 不支持多个子密钥。这似乎是一个问题,因为这是常见的做法。另一方面,您不应将常规 GPG/PGP 密钥与 pgcrypto 一起使用,而应创建新的密钥,因为使用场景相当不同。

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

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

并且 padding 之一是

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

  • none - 数据必须是加密块大小的倍数

所以,例如,这些是等价的

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

encrypt_ivdecrypt_iv 中,iv 参数是 CBC 模式的初始值;对于 ECB,它会被忽略。如果它不是完全的块大小,则会使用零对其或填充。在没有此参数的函数中,默认为全零。

F.26.5. 随机数据函数 #

gen_random_bytes(count integer) returns bytea

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

gen_random_uuid() returns uuid

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

F.26.6. 注释 #

F.26.6.1. 配置 #

pgcrypto 根据主 PostgreSQL configure 脚本的结果进行配置。影响它的选项为 --with-zlib--with-ssl=openssl

使用 zlib 编译时,PGP 加密函数可以在加密之前压缩数据。

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

在针对 OpenSSL 3.0.0 和更高版本编译时,必须在 openssl.cnf 配置文件中激活旧版提供程序,以便使用更旧的加密算法,如 DES 或 Blowfish。

F.26.6.2. NULL 处理 #

根据 SQL 标准,如果任何参数为 NULL,所有函数将返回 NULL。在粗心使用时,这可能会造成安全风险。

F.26.6.3. 安全性限制 #

所有 pgcrypto 函数都在数据库服务器内部运行。这意味着所有数据和密码都在 pgcrypto 和客户端应用程序之间以明文移动。因此,你必须

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

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

如果你不能,那么最好在客户端应用程序中进行加密。

该实现不会抵御 侧信道攻击。例如,pgcrypto 解密函数完成给定大小密文所需的时间是不同的。

F.26.7. 作者 #

Marko Kreen

pgcrypto 使用以下来源中的代码

算法 作者 源代码
DES 加密 David Burren 及其他人 FreeBSD libcrypt
MD5 加密 Poul-Henning Kamp FreeBSD libcrypt
Blowfish 加密 Solar Designer www.openwall.com