Postgresql 中文操作指南

F.28. pgcrypto — cryptographic functions #

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

The pgcrypto module provides cryptographic functions for PostgreSQL.

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

This module is considered “trusted”, that is, it can be installed by non-superusers who have CREATE privilege on the current database.

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

pgcrypto requires OpenSSL and won’t be installed if OpenSSL support was not selected when PostgreSQL was built.

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 支持的任何摘要算法都会自动选择。

Computes a binary hash of the given data. type is the algorithm to use. Standard algorithms are md5, sha1, sha224, sha256, sha384 and sha512. Moreover, any digest algorithm OpenSSL supports is automatically picked up.

如果您希望摘要为十六进制字符串,请对结果使用 encode()。例如:

If you want the digest as a hexadecimal string, use encode() on the result. For example:

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() 中相同。

Calculates hashed MAC for data with key key. type is the same as in digest().

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

This is similar to digest() but the hash can only be recalculated knowing the key. This prevents the scenario of someone altering data and also changing the hash to match.

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

If the key is larger than the hash block size it will first be hashed and the result will be used as key.

F.28.2. Password Hashing Functions #

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

The functions crypt() and gen_salt() are specifically designed for hashing passwords. crypt() does the hashing and gen_salt() prepares algorithm parameters for it.

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

The algorithms in crypt() differ from the usual MD5 or SHA1 hashing algorithms in the following respects:

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

Table F.18 lists the algorithms supported by the crypt() function.

Table F.18. Supported Algorithms for 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,并测试结果是否与存储的值匹配。

Calculates a crypt(3)-style hash of password. When storing a new password, you need to use gen_salt() to generate a new salt value. To check a password, pass the stored hash value as salt, and test whether the result matches the stored value.

设置新密码的示例:

Example of setting a new password:

UPDATE ... SET pswhash = crypt('new password', gen_salt('md5'));

验证的示例:

Example of authentication:

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

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

This returns true if the entered password is correct.

F.28.2.2. gen_salt() #

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

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

Generates a new random salt string for use in crypt(). The salt string also tells crypt() which algorithm to use.

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

The type parameter specifies the hashing algorithm. The accepted types are: des, xdes, md5 and bf.

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

The iter_count parameter lets the user specify the iteration count, for algorithms that have one. The higher the count, the more time it takes to hash the password and therefore the more time to break it. Although with too high a count the time to calculate a hash may be several years — which is somewhat impractical. If the iter_count parameter is omitted, the default iteration count is used. Allowed values for iter_count depend on the algorithm and are shown in Table F.19.

Table F.19. Iteration Counts for crypt()

Table F.19. Iteration Counts for crypt()

Algorithm

Default

Min

Max

xdes

725

1

16777215

bf

6

4

31

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

For xdes there is an additional limitation that the iteration count must be an odd number.

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

To pick an appropriate iteration count, consider that the original DES crypt was designed to have the speed of 4 hashes per second on the hardware of that time. Slower than 4 hashes per second would probably dampen usability. Faster than 100 hashes per second is probably too fast.

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

Table F.20 gives an overview of the relative slowness of different hashing algorithms. The table shows how much time it would take to try all combinations of characters in an 8-character password, assuming that the password contains either only lower case letters, or upper- and lower-case letters and numbers. In the crypt-bf entries, the number after a slash is the iter_count parameter of gen_salt.

Table F.20. Hash Algorithm Speeds

Algorithm

Hashes/sec

For [a-z]

For [A-Za-z0-9]

Duration relative to 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

备注:

Notes:

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

Note that “try all combinations” is not a realistic exercise. Usually password cracking is done with the help of dictionaries, which contain both regular words and various mutations of them. So, even somewhat word-like passwords could be cracked much faster than the above numbers suggest, while a 6-character non-word-like password may escape cracking. Or not.

F.28.3. PGP Encryption Functions #

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

The functions here implement the encryption part of the OpenPGP (RFC 4880) standard. Supported are both symmetric-key and public-key encryption.

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

An encrypted PGP message consists of 2 parts, or packets:

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

When encrypting with a symmetric key (i.e., a password):

使用公钥加密时:

When encrypting with a public key:

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

In either case the data to be encrypted is processed as follows:

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 参数可以包含选项设置,如下所述。

Encrypt data with a symmetric PGP key psw. The options parameter can contain option settings, as described below.

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 消息进行解密。

Decrypt a symmetric-key-encrypted PGP message.

不允许使用 pgp_sym_decryptbytea 数据进行解密。这是为了避免输出无效字符数据。可以使用 pgp_sym_decrypt_bytea 对原始文本数据进行解密。

Decrypting bytea data with pgp_sym_decrypt is disallowed. This is to avoid outputting invalid character data. Decrypting originally textual data with pgp_sym_decrypt_bytea is fine.

options 参数可以包含选项设置,如下所述。

The options parameter can contain option settings, as described below.

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 进行加密。将密钥函数应用于私有密钥会导致错误。

Encrypt data with a public PGP key key. Giving this function a secret key will produce an error.

options 参数可以包含选项设置,如下所述。

The options parameter can contain option settings, as described below.

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 中提供密码。如果没有密码,但需要指定选项,则需要提供空密码。

Decrypt a public-key-encrypted message. key must be the secret key corresponding to the public key that was used to encrypt. If the secret key is password-protected, you must give the password in psw. If there is no password, but you want to specify options, you need to give an empty password.

不允许使用 pgp_pub_decrypt 解密 bytea 数据。这是为了避免输出无效的字符数据。使用 pgp_pub_decrypt_bytea 解密最初的文本数据是正常的。

Decrypting bytea data with pgp_pub_decrypt is disallowed. This is to avoid outputting invalid character data. Decrypting originally textual data with pgp_pub_decrypt_bytea is fine.

options 参数可以包含选项设置,如下所述。

The options parameter can contain option settings, as described below.

F.28.3.5. pgp_key_id() #

pgp_key_id(bytea) returns text

pgp_key_id 提取 PGP 公钥或私钥的密钥 ID。如果给定加密的消息,它还会给出自该加密消息的密钥 ID。

pgp_key_id extracts the key ID of a PGP public or secret key. Or it gives the key ID that was used for encrypting the data, if given an encrypted message.

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

It can return 2 special key IDs:

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

Note that different keys may have the same ID. This is rare but a normal event. The client application should then try to decrypt with each one, to see which fits — like handling 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。

These functions wrap/unwrap binary data into PGP ASCII-armor format, which is basically Base64 with CRC and additional formatting.

如果指定了 keysvalues 数组,则会将 armor header 添加到每个键/值对的装甲格式中。两个数组都必须是单维的,而且长度必须相同。键和值不能包含任何非 ASCII 字符。

If the keys and values arrays are specified, an armor header is added to the armored format for each key/value pair. Both arrays must be single-dimensional, and they must be of the same length. The keys and values cannot contain any non-ASCII characters.

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。

pgp_armor_headers() extracts the armor headers from data. The return value is a set of rows with two columns, key and value. If the keys or values contain any non-ASCII characters, they are treated as UTF-8.

F.28.3.8. Options for PGP Functions #

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

Options are named to be similar to GnuPG. An option’s value should be given after an equal sign; separate options from each other with commas. For example:

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

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

All of the options except convert-crlf apply only to encrypt functions. Decrypt functions get the parameters from the PGP data.

最有趣的选项可能是 compress-algounicode-mode。其余的选项应具有合理的默认值。

The most interesting options are probably compress-algo and unicode-mode. The rest should have reasonable defaults.

要使用的密码算法。

Which cipher algorithm to use.

值:bf、aes128、aes192、aes256、3des、cast5 默认值:aes128 适用于:pgp_sym_encrypt、pgp_pub_encrypt

Values: bf, aes128, aes192, aes256, 3des, cast5 Default: aes128 Applies to: pgp_sym_encrypt, pgp_pub_encrypt

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

Which compression algorithm to use. Only available if PostgreSQL was built with zlib.

值: 0 - 无压缩 1 - ZIP 压缩 2 - ZLIB 压缩(= ZIP 加上元数据和块 CRC) 默认值:0 适用于:pgp_sym_encrypt、pgp_pub_encrypt

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

要压缩的程度。压缩等级越高,压缩后体积越小,但速度也越慢。0 表示禁用压缩。

How much to compress. Higher levels compress smaller but are slower. 0 disables compression.

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

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

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

Whether to convert \n into \r\n when encrypting and \r\n to \n when decrypting. RFC 4880 specifies that text data should be stored using \r\n line-feeds. Use this to get fully RFC-compliant behavior.

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

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

不使用 SHA-1 来保护数据。使用此选项的唯一充分理由是与早期 PGP 产品兼容,那些产品在 RFC 4880 中添加受 SHA-1 保护的数据包之前就已上市。最近的 gnupg.org 和 pgp.com 软件完全支持此项。

Do not protect data with SHA-1. The only good reason to use this option is to achieve compatibility with ancient PGP products, predating the addition of SHA-1 protected packets to RFC 4880. Recent gnupg.org and pgp.com software supports it fine.

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

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

使用独立会话密钥。公钥加密始终使用独立会话密钥;此选项适用于对称密钥加密,默认情况下直接使用 S2K 密钥。

Use separate session key. Public-key encryption always uses a separate session key; this option is for symmetric-key encryption, which by default uses the S2K key directly.

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

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

要使用的 S2K 算法。

Which S2K algorithm to use.

值: 0 - 无盐。 危险! 1 - 有盐,但具有固定的迭代次数。 3 - 可变迭代次数。 默认值: 3 适用于: pgp_sym_encrypt

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

要使用的 S2K 算法的迭代次数。它必须是 1024 到 65011712(含)之间的值。

The number of iterations of the S2K algorithm to use. It must be a value between 1024 and 65011712, inclusive.

默认值: 65536 到 253952 之间的随机值 仅适用于: pgp_sym_encrypt,且 s2k-mode=3

Default: A random value between 65536 and 253952 Applies to: pgp_sym_encrypt, only with s2k-mode=3

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

Which digest algorithm to use in S2K calculation.

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

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

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

Which cipher to use for encrypting separate session key.

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

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

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

Whether to convert textual data from database internal encoding to UTF-8 and back. If your database already is UTF-8, no conversion will be done, but the message will be tagged as UTF-8. Without this option it will not be.

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

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

F.28.3.9. Generating PGP Keys with GnuPG #

要生成新密钥:

To generate a new key:

gpg --gen-key

首选的密钥类型是“DSA 和 Elgamal”。

The preferred key type is “DSA and Elgamal”.

对于 RSA 加密,你必须创建 DSA 或 RSA 单向签名密钥作为主密钥,然后使用 gpg --edit-key 添加 RSA 加密子密钥。

For RSA encryption you must create either DSA or RSA sign-only key as master and then add an RSA encryption subkey with gpg --edit-key.

要列出密钥:

To list keys:

gpg --list-secret-keys

要以 ASCII 盔甲格式导出公钥:

To export a public key in ASCII-armor format:

gpg -a --export KEYID > public.key

要以 ASCII 盔甲格式导出私钥:

To export a secret key in ASCII-armor format:

gpg -a --export-secret-keys KEYID > secret.key

在将这些密钥提供给 PGP 函数之前,你需要使用 dearmor() 对这些密钥进行处理。或者,如果你可以处理二进制数据,你可以从该命令中删除 -a

You need to use dearmor() on these keys before giving them to the PGP functions. Or if you can handle binary data, you can drop -a from the command.

更多详细信息,请参阅 man gpgThe GNU Privacy Handbookhttps://www.gnupg.org/ 中的其他文档。

For more details see man gpg, The GNU Privacy Handbook and other documentation on https://www.gnupg.org/.

F.28.3.10. Limitations of PGP Code #

F.28.4. Raw Encryption Functions #

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

These functions only run a cipher over data; they don’t have any advanced features of PGP encryption. Therefore they have some major problems:

因此,随着 PGP 加密的引入,不鼓励使用原始加密函数。

So, with the introduction of PGP encryption, usage of raw encryption functions is discouraged.

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 字符串的语法为:

Encrypt/decrypt data using the cipher method specified by type. The syntax of the type string is:

algorithm [ - mode ] [ /pad: padding ]

其中 algorithm 为以下之一:

where algorithm is one of:

mode 为以下之一:

and mode is one of:

padding 为以下之一:

and padding is one of:

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

So, for example, these are equivalent:

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

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

In encrypt_iv and decrypt_iv, the iv parameter is the initial value for the CBC mode; it is ignored for ECB. It is clipped or padded with zeroes if not exactly block size. It defaults to all zeroes in the functions without this parameter.

F.28.5. Random-Data Functions #

gen_random_bytes(count integer) returns bytea

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

Returns count cryptographically strong random bytes. At most 1024 bytes can be extracted at a time. This is to avoid draining the randomness generator pool.

gen_random_uuid() returns uuid

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

Returns a version 4 (random) UUID. (Obsolete, this function internally calls the core function of the same name.)

F.28.6. Notes #

F.28.6.1. Configuration #

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

pgcrypto configures itself according to the findings of the main PostgreSQL configure script. The options that affect it are —​with-zlib and —​with-ssl=openssl.

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

When compiled with zlib, PGP encryption functions are able to compress data before encrypting.

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

pgcrypto requires OpenSSL. Otherwise, it will not be built or installed.

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

When compiled against OpenSSL 3.0.0 and later versions, the legacy provider must be activated in the openssl.cnf configuration file in order to use older ciphers like DES or Blowfish.

F.28.6.2. NULL Handling #

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

As is standard in SQL, all functions return NULL, if any of the arguments are NULL. This may create security risks on careless usage.

F.28.6.3. Security Limitations #

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

All pgcrypto functions run inside the database server. That means that all the data and passwords move between pgcrypto and client applications in clear text. Thus you must:

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

If you cannot, then better do crypto inside client application.

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

The implementation does not resist side-channel attacks. For example, the time required for a pgcrypto decryption function to complete varies among ciphertexts of a given size.

F.28.7. Author #

pgcrypto 使用以下来源的代码:

pgcrypto uses code from the following sources: