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 哈希算法不同
它们很慢。由于数据量很小,这是使暴力破解密码变得困难的唯一方法。
它们使用一个随机值(称为 salt),因此具有相同密码的用户将具有不同的加密密码。这也是防止反向算法的额外防御措施。
它们在结果中包含算法类型,因此可以使用不同算法哈希的密码可以共存。
其中一些是自适应的——这意味着当计算机变得更快时,您可以调整算法使其变慢,而不会引入与现有密码的不兼容性。
表 F.17 列出了 crypt()
函数支持的算法。
表 F.17. crypt()
支持的算法
算法 | 最大密码长度 | 自适应? | Salt 位数 | 输出长度 | 描述 |
---|---|---|---|---|---|
bf |
72 | 是 | 128 | 60 | 基于 Blowfish,变体 2a |
md5 |
无限制 | 否 | 48 | 34 | 基于 MD5 的 crypt |
xdes |
8 | 是 | 24 | 20 | 扩展 DES |
des |
8 | 否 | 12 | 13 | 原始 UNIX crypt |
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()
的新的随机 salt 字符串。salt 字符串还告诉 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 crypt 旨在在当时硬件上的速度为每秒 4 次哈希。低于每秒 4 次哈希可能会降低可用性。高于每秒 100 次哈希可能太快了。
表 F.19 概述了不同哈希算法的相对缓慢程度。该表显示了尝试 8 个字符密码中所有字符组合所需的时间,假设密码仅包含小写字母,或包含大写和小写字母以及数字。在 crypt-bf
条目中,斜杠后的数字是 gen_salt
的 iter_count
参数。
表 F.19. 哈希算法速度
算法 | 哈希/秒 | 对于 [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 |
注释
使用的机器是英特尔酷睿 i3 移动版。
crypt-des
和 crypt-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 个字符的非单词类密码可能会逃脱破解。或者不会。
此处的函数实现了 OpenPGP (RFC 4880) 标准的加密部分。支持对称密钥和公钥加密。
PGP 加密消息由 2 部分或 数据包 组成
包含会话密钥的数据包——使用对称密钥或公钥加密。
包含使用会话密钥加密的数据的数据包。
当使用对称密钥(即密码)加密时
给定的密码使用 String2Key (S2K) 算法进行哈希处理。这与 crypt()
算法非常相似——故意缓慢且具有随机 salt——但它会生成完整长度的二进制密钥。
如果请求单独的会话密钥,则会生成一个新的随机密钥。否则,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 装甲格式中,该格式基本上是带 CRC 和附加格式的 Base64。
如果指定了 keys
和 values
数组,则会为每个键/值对向装甲格式添加一个 装甲头。这两个数组必须是一维的,并且必须具有相同的长度。键和值不能包含任何非 ASCII 字符。
pgp_armor_headers
#pgp_armor_headers(data text, key out text, value out text) returns setof record
pgp_armor_headers()
从 data
中提取装甲头。返回值是一组包含两列的行,键和值。如果键或值包含任何非 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 装甲格式导出公钥
gpg -a --export KEYID > public.key
要以 ASCII 装甲格式导出密钥
gpg -a --export-secret-keys KEYID > secret.key
在将这些密钥提供给 PGP 函数之前,您需要在这些密钥上使用 dearmor()
。或者,如果您能够处理二进制数据,则可以从命令中删除 -a
。
有关更多详细信息,请参阅 man gpg
、GNU Privacy 手册 和 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 <[email protected]>
pgcrypto
使用以下来源的代码
算法 | 作者 | 源代码来源 |
---|---|---|
DES 加密 | David Burren 及其他人员 | FreeBSD libcrypt |
MD5 加密 | Poul-Henning Kamp | FreeBSD libcrypt |
Blowfish 加密 | Solar Designer | www.openwall.com |
如果您在文档中发现任何不正确的内容,与您对特定功能的体验不符,或者需要进一步澄清,请使用此表单 报告文档问题。