选择 打开 改范围 完整检索页

pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。

受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
测试与开发版本: 19 / devel
不受支持的版本: 13 / 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0
历史版本PostgreSQL 9.0 已于 2015 年 10 月结束社区维护,本页译文保留供仍在使用旧版本的读者参考。新系统请看当前版本

F.25. pgcrypto #

pgcrypto模块为PostgreSQL提供密码学函数。

F.25.1. General hashing functions

F.25.1.1. digest()

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

计算给定data的二进制哈希值。type是要使用的算法。标准算法包括md5sha1sha224sha256sha384sha512。如果pgcrypto是在有 OpenSSL 的情况下构建的,则还有更多算法可用,详见表 F.18

如果想把摘要表示为十六进制字符串,可以对结果使用encode()。例如:

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

F.25.1.2. hmac()

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

使用密钥keydata计算基于散列的消息认证码(HMAC)。 typedigest()中的相同。

这与digest()类似,但只有知道密钥时才能重新计算该哈希。 这可以防止有人篡改数据后再同时修改哈希使之匹配。

如果密钥大于哈希块大小,则会先对其进行哈希,并将结果用作密钥。

F.25.2. Password hashing functions

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

crypt() 中的算法在以下方面不同于 MD5 或 SHA1 之类 通常的哈希算法:

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

  2. 它们使用一个称为salt的随机盐值,这样使用相同密码的用户也会得到不同的哈希结果。 这也为逆向求解该算法增加了一层额外防护。

  3. 它们会在结果中包含算法类型,这样用不同算法哈希的密码就能共存。

  4. 其中一些是自适应的 — 这意味着当计算机变快时,你可以把算法调得更慢, 而不引入与现有密码的不兼容性。

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

表 F.15. Supported algorithms for crypt()

算法 Max password length 是否自适应? Salt bits 描述
bf 72 128 基于 Blowfish,2a 变体
md5 无限制 48 基于 MD5 的 crypt
xdes 8 24 扩展 DES
des 8 12 原始 UNIX crypt

F.25.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'));

Example of authentication:

SELECT pswhash = crypt('entered password', pswhash) FROM ... ;

如果输入的密码正确,这会返回 true

F.25.2.2. gen_salt()

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

生成一个供crypt()使用的新随机盐值字符串。 该盐值字符串还会告诉crypt()应使用哪种算法。

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

iter_count参数允许用户为支持该参数的算法指定迭代次数。 次数越高,对密码进行 hash 所需的时间越长,从而破解它所需时间也越长。 不过,如果次数过高,计算一个 hash 可能需要数年时间 — 这显然不切实际。 若省略iter_count参数,则使用默认迭代次数。 允许的iter_count值取决于算法,如表 F.16所示。

表 F.16. Iteration counts for crypt()

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

xdes算法还有额外的限制:迭代计数必须是一个奇数。

为了选择合适的迭代次数,可以考虑原始 DES crypt 在当时硬件上的设计速度是每秒 4 次 hash。 低于每秒 4 次 hash 可能会影响可用性,而高于每秒 100 次 hash 则很可能过快。

表 F.17概述了不同 hash 算法之间的相对速度差异。 该表展示了在 8 字符密码上尝试所有字符组合所需的时间,假定密码只包含小写字母, 或者包含大小写字母和数字。在crypt-bf条目中, 斜杠后的数字是gen_saltiter_count参数值。

表 F.17. Hash algorithm speeds

算法 每秒 hash 次数 针对[a-z] 针对[A-Za-z0-9]
crypt-bf/8 28 246 years 251322 years
crypt-bf/7 57 121 years 123457 years
crypt-bf/6 112 62 years 62831 years
crypt-bf/5 211 33 years 33351 years
crypt-md5 2681 2.6 years 2625 years
crypt-des 362837 7 days 19 years
sha1 590223 4 days 12 years
md5 2345086 1 day 3 years

注意:

  • 所用机器为 1.5GHz Pentium 4。

  • crypt-descrypt-md5算法的数字取自 John the Ripper v1.6.38 的 -test 输出。

  • md5 hash的数字来自 mdcrack 1.2。

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

  • crypt-bf的数字是使用一个简单程序测得的,该程序循环处理 1000 个 8 字符密码。这样可以展示不同迭代次数下的速度。作为参考: john -testcrypt-bf/5 给出的结果是 213 次循环/秒。(结果上的极小差异与这样一个事实一致: crypt-bfpgcrypto中的实现与 John the Ripper 使用的是同一套实现。)

请注意,尝试所有组合并不是现实中的做法。 通常密码破解是借助词典完成的,其中包含常见单词及其各种变体。 因此,即使是稍微有点像单词的密码,被破解的速度也可能远快于上表所示; 而一个 6 字符、不像单词的密码则可能逃过破解,也可能不会。

F.25.3. PGP encryption functions

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

一个加密的 PGP 消息由两个部分,或称两个组成:

  • 包含会话密钥的包 — 该会话密钥要么由对称密钥加密,要么由公钥加密。

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

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

  1. 给定密码使用 String2Key (S2K) 算法进行哈希。 这与crypt()算法颇为类似 — 故意设计得较慢,并带有随机盐值 — 但它产生的是一个全长度的二进制密钥。

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

  3. 如果直接使用 S2K 密钥,那么会话密钥包中只写入 S2K 设置。 否则,会话密钥会先用 S2K 密钥加密,再放入会话密钥包。

当使用公钥加密时:

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

  2. 该密钥会用公钥加密,并放入会话密钥包中。

无论哪种情况,要加密的数据都会按如下步骤处理:

  1. 可选的数据处理包括:压缩、转换为 UTF-8 和转换行结束符,三者可任意组合。

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

  3. 追加对随机前缀和数据计算得到的 SHA-1 哈希值。

  4. 然后用会话密钥加密所有这些内容,并放入数据包。

F.25.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.25.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 消息。

bytea数据不能用pgp_sym_decrypt解密。 这是为了避免输出无效字符数据。若原始数据本来是文本, 则使用pgp_sym_decrypt_bytea解密也没有问题。

options参数可以包含下文所述的选项设置。

F.25.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.25.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中给出密码。 如果没有密码但想指定选项,则需要传入空密码。

bytea数据不能用pgp_pub_decrypt解密。 这是为了避免输出无效字符数据。若原始数据本来是文本, 则使用pgp_pub_decrypt_bytea解密也没有问题。

options参数可以包含下文所述的选项设置。

F.25.3.5. pgp_key_id()

pgp_key_id(bytea) returns text

pgp_key_id提取 PGP 公钥或私钥的密钥 ID。 如果传入的是加密消息,则返回用于加密该数据的密钥 ID。

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

  • SYMKEY

    该消息是用对称密钥加密的。

  • ANYKEY

    该消息是用公钥加密的,但密钥 ID 已被移除。 这意味着你需要尝试自己的所有私钥,看看哪一个能解密它。 pgcrypto本身不会生成这样的消息。

注意,不同的密钥可能具有相同的 ID。这种情况虽然罕见,但属于正常情况。 客户端应用此时应该尝试使用每一个密钥解密,以判断哪个匹配 — 就像处理ANYKEY时一样。

F.25.3.6. armor(), dearmor()

armor(data bytea) returns text
dearmor(data text) returns bytea

这些函数将二进制数据封装/解封装为 PGP ASCII-armor 格式, 它本质上就是带 CRC 和附加格式信息的 Base64。

F.25.3.7. Options for PGP functions

这些选项的命名方式与 GnuPG 类似。选项值应写在等号后面; 各选项之间用逗号分隔。例如:

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

convert-crlf外,所有选项都只适用于加密函数。 解密函数会从 PGP 数据中获取这些参数。

最值得关注的选项可能是compress-algounicode-mode。 其余选项应该都具有合理的默认值。

F.25.3.7.1. cipher-algo

使用哪种密码算法。

Values: bf, aes128, aes192, aes256 (OpenSSL-only: 3descast5)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt

F.25.3.7.2. compress-algo

使用哪种压缩算法。仅当PostgreSQL在编译时包含 zlib 时才可用。

Values:
  0 - no compression
  1 - ZIP compression
  2 - ZLIB compression (= ZIP plus meta-data and block CRCs)
Default: 0
Applies to: pgp_sym_encrypt, pgp_pub_encrypt

F.25.3.7.3. compress-level

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

Values: 0, 1-9
Default: 6
Applies to: pgp_sym_encrypt, pgp_pub_encrypt

F.25.3.7.4. convert-crlf

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

Values: 0, 1
Default: 0
Applies to: pgp_sym_encrypt, pgp_pub_encrypt, pgp_sym_decrypt, pgp_pub_decrypt

F.25.3.7.5. disable-mdc

不使用 SHA-1 保护数据。使用此选项唯一合理的理由是兼容早于 RFC 4880 加入 SHA-1 保护包的古老 PGP 产品。较新的 gnupg.org 和 pgp.com 软件都能很好地支持它。

Values: 0, 1
Default: 0
Applies to: pgp_sym_encrypt, pgp_pub_encrypt

F.25.3.7.6. sess-key

使用单独的会话密钥。公钥加密总是使用单独的会话密钥; 这个选项用于对称密钥加密,因为后者默认直接使用 S2K 密钥。

Values: 0, 1
Default: 0
Applies to: pgp_sym_encrypt

F.25.3.7.7. s2k-mode

使用哪一种 S2K 算法。

Values:
  0 - Without salt.  Dangerous!
  1 - With salt but with fixed iteration count.
  3 - Variable iteration count.
Default: 3
Applies to: pgp_sym_encrypt

F.25.3.7.8. s2k-digest-algo

要在 S2K 计算中使用哪种摘要算法。

Values: md5, sha1
Default: sha1
Applies to: pgp_sym_encrypt

F.25.3.7.9. s2k-cipher-algo

使用哪种密码算法来加密单独的会话密钥。

Values: bf, aes, aes128, aes192, aes256
Default: use cipher-algo
Applies to: pgp_sym_encrypt

F.25.3.7.10. unicode-mode

是否将文本数据在数据库内部编码和 UTF-8 之间相互转换。 如果数据库已经是 UTF-8,则不会发生转换,但消息会被标记为 UTF-8。 如果不启用该选项,则不会进行这种标记。

Values: 0, 1
Default: 0
Applies to: pgp_sym_encrypt, pgp_pub_encrypt

F.25.3.8. Generating PGP keys with GnuPG

要生成新密钥:

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

F.25.3.9. Limitations of PGP code

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

  • 不支持使用加密密钥作为主密钥。由于通常不鼓励这种做法,这不应成为问题。

  • 不支持多个子密钥。这看起来可能是个问题,因为这在实践中相当常见。 另一方面,你不应将常规的 GPG/PGP 密钥用于pgcrypto, 而应新建一套密钥,因为其使用场景相当不同。

F.25.4. Raw encryption functions

这些函数只是对数据应用密码算法;它们不具备 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)

mode 是以下之一:

  • cbc — 下一个块依赖于前一个块(默认)

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

padding 是以下之一:

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

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

因此,例如下面两种写法是等价的:

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

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

F.25.5. Random-data functions

gen_random_bytes(count integer) returns bytea

返回count个具有密码学安全强度的随机字节。 一次最多可提取 1024 个字节,以避免耗尽随机数生成器池。

F.25.6. 注意事项

F.25.6.1. 配置

pgcrypto会根据 PostgreSQL 主configure脚本的探测结果自行配置。影响它的选项有--with-zlib--with-openssl

如果编译时包含 zlib,PGP 加密函数就能够在加密前压缩数据。

如果编译时包含 OpenSSL,则会有更多算法可用。同时公钥加密函数也会更快,因为 OpenSSL 具有更加优化的 BIGNUM 函数。

表 F.18. 有无 OpenSSL 时的功能概要

功能 内置 包含 OpenSSL 时
MD5
SHA1
SHA224/256/384/512 是(注 1)
其他摘要算法 是(注 2)
Blowfish
AES 是(注 3)
DES/3DES/CAST5
原始加密
PGP 对称加密
PGP 公钥加密

注意:

  1. SHA2 算法是在 OpenSSL 0.9.8 版中加入的。对于更早的版本, pgcrypto将使用内建代码。

  2. OpenSSL 支持的任何摘要算法都会自动被采用。加密算法则无法如此,必须显式添加支持。

  3. AES 是在 OpenSSL 0.9.7 版中引入的。对于更早的版本, pgcrypto将使用内建代码。

F.25.6.2. NULL handling

按照 SQL 的标准,只要任一参数为 NULL,所有函数都返回 NULL。 这在使用不慎时可能带来安全风险。

F.25.6.3. Security limitations

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

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

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

如果做不到,最好在客户端应用内部执行密码学操作。

该实现无法抵御侧信道攻击。例如,对于给定大小的密文,pgcrypto解密函数完成所需的时间会因具体密文不同而变化。

F.25.6.4. Useful reading

F.25.6.5. Technical references

F.25.7. 作者

Marko Kreen

pgcrypto使用了来自以下来源的代码:

算法 作者 源代码来源
DES crypt David Burren 及其他人 FreeBSD libcrypt
MD5 crypt Poul-Henning Kamp FreeBSD libcrypt
Blowfish crypt Solar Designer www.openwall.com
Blowfish cipher Simon Tatham PuTTY
Rijndael cipher Brian Gladman OpenBSD sys/crypto
MD5 and SHA1 WIDE Project KAME kame/sys/crypto
SHA256/384/512 Aaron D. Gifford OpenBSD sys/crypto
BIGNUM math Michael J. Fromberger dartmouth.edu/~sting/sw/imath

提交更正

译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。