F.28. pgcrypto — 加密函数#
pgcrypto
模块为PostgreSQL提供加密函数。
此模块被认为是“受信任的”,也就是说,它可以由在当前数据库上拥有CREATE
权限的非超级用户安装。
pgcrypto
需要 OpenSSL,如果在构建 PostgreSQL 时未选择 OpenSSL 支持,则不会安装它。
F.28.1. 通用哈希函数#
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. 密码哈希函数#
函数crypt()
和gen_salt()
专门设计用于哈希密码。crypt()
执行哈希,gen_salt()
为其准备算法参数。
crypt()
中的算法与通常的 MD5 或 SHA1 哈希算法在以下方面有所不同
它们很慢。由于数据量很小,这是使暴力破解密码变得困难的唯一方法。
它们使用一个称为 盐 的随机值,以便拥有相同密码的用户将具有不同的加密密码。这也是防止算法逆转的另一道防线。
它们在结果中包含算法类型,因此可以使用不同算法哈希的密码可以共存。
其中一些是自适应的——这意味着当计算机变快时,你可以调整算法以使其变慢,而不会与现有密码不兼容。
表 F.18列出了crypt()
函数支持的算法。
表 F.18.crypt()
支持的算法
算法 | 最大密码长度 | 自适应? | 盐位 | 输出长度 | 说明 |
---|---|---|---|---|---|
bf | 72 | 是 | 128 | 60 | 基于 Blowfish,变体 2a |
md5 | 无限制 | 否 | 48 | 34 | 基于 MD5 的 crypt |
xdes | 8 | 是 | 24 | 20 | 扩展 DES |
des | 8 | 否 | 12 | 13 | 原始 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
为在crypt()
中使用生成新的随机盐字符串。盐字符串还告诉crypt()
使用哪种算法。
*type
*参数指定哈希算法。可接受的类型有:des
、xdes
、md5
和bf
。
*iter_count
参数允许用户指定迭代次数,适用于具有迭代次数的算法。次数越高,对密码进行哈希所需的时间就越长,因此破解密码所需的时间就越长。虽然次数过高,计算哈希所需的时间可能长达数年,这有些不切实际。如果省略iter_count
参数,则使用默认迭代次数。iter_count
*的允许值取决于算法,并在表 F.19中显示。
表 F.19.crypt()
的迭代次数
算法 | 默认 | 最小值 | 最大值 |
---|---|---|---|
xdes | 725 | 1 | 16777215 |
bf | 6 | 4 | 31 |
对于xdes
,还有一个附加限制,即迭代次数必须为奇数。
要选择合适的迭代次数,请考虑原始 DES crypt 设计为在当时的硬件上每秒进行 4 次哈希。每秒低于 4 次哈希可能会降低可用性。每秒超过 100 次哈希可能太快。
表 F.20概述了不同哈希算法的相对速度。该表显示了尝试 8 个字符密码中的所有字符组合需要多长时间,假设密码仅包含小写字母或大写和小写字母和数字。在crypt-bf
条目中,斜杠后的数字是gen_salt
的*iter_count
*参数。
表 F.20. 哈希算法速度
算法 | 哈希/秒 | 对于 [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 |
注释
使用的机器是英特尔移动酷睿 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 个字符的非单词式密码可能会逃脱破解。或者不会。
F.28.3. PGP 加密函数#
此处函数实现 OpenPGP(RFC 4880)标准的加密部分。支持对称密钥和公钥加密。
加密的 PGP 消息包含 2 个部分或数据包
包含会话密钥的数据包 — 对称密钥或公钥加密。
包含使用会话密钥加密的数据的数据包。
使用对称密钥(即密码)加密时
使用 String2Key (S2K) 算法对给定的密码进行哈希处理。这与
crypt()
算法非常相似 — 故意缓慢且带有随机盐 — 但它会产生一个全长的二进制密钥。如果请求单独的会话密钥,将生成一个新的随机密钥。否则,S2K 密钥将直接用作会话密钥。
如果要直接使用 S2K 密钥,则只会将 S2K 设置放入会话密钥数据包中。否则,会话密钥将使用 S2K 密钥加密并放入会话密钥数据包中。
使用公钥加密时
生成一个新的随机会话密钥。
使用公钥加密该密钥并将其放入会话密钥数据包中。
在任何情况下,要加密的数据都将按如下方式处理
可选数据操作:压缩、转换为 UTF-8 和/或转换行尾。
数据前缀为随机字节块。这相当于使用随机 IV。
附加随机前缀和数据的 SHA1 哈希值。
使用会话密钥加密所有这些内容并将其放入数据包中。
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 密钥*psw
加密data
。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
SYMKEY
消息使用对称密钥加密。
ANYKEY
消息使用公钥加密,但密钥 ID 已被移除。这意味着您需要对其尝试所有私钥,以查看哪个密钥可以解密它。
pgcrypto
本身不会生成此类消息。
请注意,不同的密钥可能具有相同的 ID。这种情况很少见,但属于正常情况。然后,客户端应用程序应尝试使用每个密钥进行解密,以查看哪个密钥合适——就像处理ANYKEY
一样。
F.28.3.6.armor()
,dearmor()
#
armor(data bytea [ , keys text[], values text[] ]) returns text
dearmor(data text) returns bytea
这些函数将二进制数据包装/解包到 PGP ASCII-armor 格式中,该格式基本上是带有 CRC 和附加格式的 Base64。
如果指定了*keys
和values
数组,则会为每个键/值对向装甲格式添加一个装甲头*。这两个数组都必须是单维的,并且它们必须具有相同的长度。键和值不能包含任何非 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. PGP 函数的选项#
选项的命名类似于 GnuPG。选项的值应在等号后给出;用逗号将选项彼此分隔。例如
pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
除了convert-crlf
之外,所有选项都仅适用于加密函数。解密函数从 PGP 数据中获取参数。
最有趣的选项可能是compress-algo
和unicode-mode
。其余选项应具有合理的默认值。
F.28.3.8.1. cipher-algo#
要使用的密码算法。
值: bf、aes128、aes192、aes256、3des、cast5
默认: aes128
适用于: pgp_sym_encrypt、pgp_pub_encrypt
F.28.3.8.2. compress-algo#
使用哪种压缩算法。仅当PostgreSQL使用 zlib 构建时才可用。
值
0 - 不压缩
1 - ZIP 压缩
2 - ZLIB 压缩 (= ZIP 加元数据和块 CRC)
默认: 0
适用于: pgp_sym_encrypt、pgp_pub_encrypt
F.28.3.8.3. compress-level#
压缩程度。级别越高,压缩越小,但速度越慢。0 禁用压缩。
值: 0、1-9
默认: 6
适用于: pgp_sym_encrypt、pgp_pub_encrypt
F.28.3.8.4. convert-crlf#
加密时是否将\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
F.28.3.8.5. disable-mdc#
不使用 SHA-1 保护数据。使用此选项的唯一充分理由是与旧版 PGP 产品兼容,这些产品早于RFC4880 中添加 SHA-1 保护数据包。
值: 0、1
默认: 0
适用于: pgp_sym_encrypt、pgp_pub_encrypt
F.28.3.8.6. sess-key#
使用单独的会话密钥。公钥加密始终使用单独的会话密钥;此选项用于对称密钥加密,默认情况下直接使用 S2K 密钥。
值: 0、1
默认: 0
适用于: pgp_sym_encrypt
F.28.3.8.7. s2k-mode#
使用哪种 S2K 算法。
值
0 - 无盐。 危险!
1 - 带盐但具有固定迭代次数。
3 - 可变迭代次数。
默认: 3
适用于: pgp_sym_encrypt
F.28.3.8.8. s2k-count#
要使用的 S2K 算法的迭代次数。它必须是介于 1024 和 65011712 之间的数值(含)。
默认: 65536 到 253952 之间的随机值
适用于: pgp_sym_encrypt,仅当 s2k-mode=3
F.28.3.8.9. s2k-digest-algo#
在 S2K 计算中使用哪种摘要算法。
值: md5、sha1
默认: sha1
适用于: pgp_sym_encrypt
F.28.3.8.10. s2k-cipher-algo#
用于加密单独会话密钥的密码。
值: bf、aes、aes128、aes192、aes256
默认:使用 cipher-algo
适用于: pgp_sym_encrypt
F.28.3.8.11. unicode-mode#
是否将文本数据从数据库内部编码转换为 UTF-8,然后再转换回来。如果您的数据库已经是 UTF-8,则不会执行转换,但消息将标记为 UTF-8。如果不使用此选项,则不会标记为 UTF-8。
值: 0、1
默认: 0
适用于: pgp_sym_encrypt、pgp_pub_encrypt
F.28.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 gpg
、GNU 隐私手册和https://www.gnupg.org/上的其他文档。
F.28.3.10. PGP 代码的限制#
不支持签名。这也意味着不会检查加密子密钥是否属于主密钥。
不支持加密密钥作为主密钥。由于这种做法通常不鼓励,因此这不应该成为问题。
不支持多个子密钥。这似乎是个问题,因为这是常见的做法。另一方面,您不应该将常规 GPG/PGP 密钥与
pgcrypto
一起使用,而是创建新的密钥,因为使用场景完全不同。
F.28.4. 原始加密函数#
这些函数仅在数据上运行密码;它们没有任何 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
— Blowfishaes
— 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,它将被忽略。如果不完全是块大小,则会将其剪切或用零填充。在没有此参数的函数中,它默认为全部为零。
F.28.5. 随机数据函数#
gen_random_bytes(count integer) returns bytea
返回*count
*个加密强随机字节。一次最多可以提取 1024 个字节。这是为了避免耗尽随机数生成器池。
gen_random_uuid() returns uuid
返回版本 4(随机)UUID。(已过时,此函数在内部调用同名核心函数核心函数。)
F.28.6. 备注#
F.28.6.1. 配置#
pgcrypto
根据主 PostgreSQLconfigure
脚本的发现结果进行自身配置。影响它的选项为--with-zlib
和--with-ssl=openssl
。
使用 zlib 编译时,PGP 加密函数能够在加密之前压缩数据。
pgcrypto
需要OpenSSL。否则,它将不会被构建或安装。
针对OpenSSL3.0.0 及更高版本编译时,必须在openssl.cnf
配置文件中激活旧版提供程序,才能使用 DES 或 Blowfish 等较旧的密码。
F.28.6.2. NULL 处理#
与 SQL 中的标准一样,如果任何参数为 NULL,则所有函数都返回 NULL。这可能会在使用不当时造成安全风险。
F.28.6.3. 安全限制#
所有pgcrypto
函数都在数据库服务器内运行。这意味着所有数据和密码都在pgcrypto
和客户端应用程序之间以明文形式传输。因此,您必须
在本地连接或使用 SSL 连接。
信任系统和数据库管理员。
如果您无法做到,那么最好在客户端应用程序内进行加密。
该实现无法抵御旁路攻击。例如,pgcrypto
解密函数完成所需的时间因给定大小的密码文本而异。
F.28.6.4. 有用读物#
https://www.gnupg.org/gph/en/manual.html
GNU 隐私手册。
https://www.openwall.com/crypt/
描述 crypt-blowfish 算法。
https://www.iusmentis.com/security/passphrasefaq/
如何选择一个好密码。
http://world.std.com/~reinhold/diceware.html
选择密码的有趣想法。
http://www.interhack.net/people/cmcurtin/snake-oil-faq.html
描述好和坏的加密。
F.28.6.5. 技术参考#
https://tools.ietf.org/html/rfc4880
OpenPGP 消息格式。
https://tools.ietf.org/html/rfc1321
MD5 消息摘要算法。
https://tools.ietf.org/html/rfc2104
HMAC:用于消息认证的密钥散列。
https://www.usenix.org/legacy/events/usenix99/provos.html
crypt-des、crypt-md5 和 bcrypt 算法的比较。
https://en.wikipedia.org/wiki/Fortuna_(PRNG)
Fortuna CSPRNG 的描述。
Jean-Luc Cooke 基于 Fortuna 的 Linux
/dev/random
驱动程序。
F.28.7. 作者#
Marko Kreen<[[email protected]](/cdn-cgi/l/email-protection#f09d91829b9f9b82b0979d91999cde939f9d)>
pgcrypto
使用来自以下来源的代码
算法 | 作者 | 源代码 |
---|---|---|
DES crypt | David Burren 等人 | FreeBSD libcrypt |
MD5 crypt | Poul-Henning Kamp | FreeBSD libcrypt |
Blowfish crypt | Solar Designer | www.openwall.com |