Postgresql 中文操作指南

F.28. pgcrypto — cryptographic functions #

pgcrypto 模块为 PostgreSQL 提供加密功能。

此模块被认为是“受信任的”,也就是说,它可以由在当前数据库上具有 CREATE 权限的非超级用户安装。

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

F.28.1. General Hashing Functions #

F.28.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.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。typedigest() 中相同。

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

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

F.28.2. Password Hashing Functions #

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

crypt() 中的算法与通常的 MD5 或 SHA1 哈希算法在以下方面有所不同:

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

Table F.18. Supported Algorithms for crypt()

Algorithm

Max Password Length

Adaptive?

Salt Bits

Output Length

Description

bf

72

yes

128

60

Blowfish-based, variant 2a

md5

unlimited

no

48

34

MD5-based crypt

xdes

8

yes

24

20

Extended DES

des

8

no

12

13

Original 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

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

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

对于拥有算法的算法,iter_count 参数允许用户指定迭代计数。计数越高,散列密码所需时间就越长,因此破解密码所需时间也越长。尽管如果计数太高,则计算哈希值所需时间可能长达数年——这有点不切实际。如果省略 iter_count 参数,则使用默认迭代计数。iter_count 的允许值取决于算法,并在 Table F.19 中显示。

Table F.19. Iteration Counts for crypt()

Algorithm

Default

Min

Max

xdes

725

1

16777215

bf

6

4

31

xdes 还有一个附加限制,即迭代次数必须为奇数。

要挑选合适的迭代次数,请考虑最初的 DES 加密设计是为了在当时的硬件上每秒哈希处理 4 次。每秒哈希处理速度低于 4 次可能会影响易用性。每秒哈希处理速度快于 100 次可能太快。

Table F.20 给出了不同哈希算法相对慢速的概览。该表格显示了尝试密码中所有字符组合所需的时间,假设密码仅包含小写字母,或大写和小写字母和数字。在 crypt-bf 条目中,斜杠后面的数字是 gen_salt 中的 iter_count 参数。

Table F.20. Hash Algorithm Speeds

Algorithm

Hashes/sec

For [a-z]

For [A-Za-z0-9]

相对于 md5 hash 的持续时间

crypt-bf/8

1792

4 years

3927 years

100k

crypt-bf/7

3648

2 years

1929 years

50k

crypt-bf/6

7168

1 year

982 years

25k

crypt-bf/5

13504

188 days

521 years

12.5k

crypt-md5

171584

15 days

41 years

1k

crypt-des

23221568

157.5 minutes

108 days

7

sha1

37774272

90 minutes

68 days

4

md5 (hash)

150085504

22.5 minutes

17 days

1

备注:

请注意,“尝试所有组合”并不是一项现实的操作。通常使用字典来破解密码,字典中包含常规词语以及各种变体。因此,甚至一些单词般的密码也可以比上述数字表明的更快地破解,而 6 个字符的非单词般的密码可能无法破解。有时可以破解。

F.28.3. PGP Encryption Functions #

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

加密的 PGP 消息包含 2 部分或 packets

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

使用公钥加密时:

在任何情况下,要加密的数据都将经过以下处理:

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

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_decryptbytea 数据进行解密。这是为了避免输出无效字符数据。可以使用 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 密钥 keydata 进行加密。将密钥函数应用于私有密钥会导致错误。

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:

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

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

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

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

如果指定了 keysvalues 数组,则会将 armor header 添加到每个键/值对的装甲格式中。两个数组都必须是单维的,而且长度必须相同。键和值不能包含任何非 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. Options for PGP Functions #

选项的命名与 GnuPG 类似。选项的值应在等号后面给出;用逗号将选项相互分开。例如:

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

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

最有趣的选项可能是 compress-algounicode-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。RFC 4880 规定应使用 \r\n 行尾来存储文本数据。使用此项以获得完全符合 RFC 的行为。

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

不使用 SHA-1 来保护数据。使用此选项的唯一充分理由是与早期 PGP 产品兼容,那些产品在 RFC 4880 中添加受 SHA-1 保护的数据包之前就已上市。最近的 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

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

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

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

值: bf、aes、aes128、aes192、aes256 默认值: 使用加密算法 适用于: pgp_sym_encrypt

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

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

F.28.3.9. Generating PGP Keys with GnuPG #

要生成新密钥:

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 gpgThe GNU Privacy Handbookhttps://www.gnupg.org/ 中的其他文档。

F.28.3.10. Limitations of PGP Code #

F.28.4. Raw Encryption Functions #

这些函数仅在数据上运行一次密码,它们没有任何高级 PGP 加密功能。因此,它们有一些主要问题:

因此,随着 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 为以下之一:

mode 为以下之一:

padding 为以下之一:

因此,例如,以下内容是等效的:

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

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

F.28.5. Random-Data Functions #

gen_random_bytes(count integer) returns bytea

返回 count 个密码学上强大的随机字节。一次最多可以提取 1024 个字节。这是为了避免耗尽随机数生成器池。

gen_random_uuid() returns uuid

返回一个 4 版本(随机)的 UUID。(已过时,此函数内部调用同名的 core function 。)

F.28.6. Notes #

F.28.6.1. Configuration #

pgcrypto 根据主 PostgreSQL configure 脚本的结果配置自身。影响它的选项是 —​with-zlib—​with-ssl=openssl

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

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

当针对 OpenSSL 3.0.0 和更高版本编译时,必须在 openssl.cnf 配置文件中激活旧版提供程序才能使用 DES 或 Blowfish 等较旧的密码。

F.28.6.2. NULL Handling #

在 SQL 中是标准的,如果任何参数为 NULL,则所有函数将返回 NULL。在使用不当时,这可能会产生安全风险。

F.28.6.3. Security Limitations #

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

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

该实现不抵抗 side-channel attacks. 例如, pgcrypto 解密函数完成所需时间随给定大小的密文而变化。

F.28.7. Author #

pgcrypto 使用以下来源的代码: