PostgreSQL 9.4.4 中文手册
上一页 | 上一级 | 附录 F. 额外提供的模块 | 下一页

# F.25. pgcrypto

`pgcrypto`模块为PostgreSQL提供cryptographic函数。

## F.25.1. 一般散列函数

### F.25.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`。 如果`pgcrypto`带有OpenSSL建立，那么更多算法可用，在 表 F-18中详细说明。

如果你希望digest作为一个十六进制字符串，那么在结果上使用`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

为带有键`key`的`data`计算散列的MAC。`type` 和在`digest()`中相同。

类似于`digest()`但是散列只能在知道键的时候计算。 这样就阻止了某个人更改数据并改变匹配的散列的情况。

如果键比散列块大小要大，那么将首先把键散列然后散列的结果作为键使用。

## F.25.2. 口令散列函数

函数`crypt()`和`gen_salt()`是特别为散列口令设计的。 `crypt()`做散列法，`gen_salt()`为其准备算法参数。

`crypt()`中的算法与MD5或SHA1散列算法有以下方面的不同：

1. 他们的速度很慢。因为数据很少，所以这是唯一的让蛮力破解口令困难些的方法。

2. 它们使用随机值，称为salt，所以有相同口令的用户将会有不同加密了的口令。 也是也对反向算法的附加防御。

3. 它们在结果中包括算法类型，所以不同算法的口令散列可以共存。

4. 它们中的一些是自适应的，这意味着当计算机更快速时，你可以将算法调整的慢一些， 而不会引入与现有口令的不相容。

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

表 F-15. `crypt()`支持的算法

算法 | 最大口令长度 | 自适应? | Salt位 | 输出长度 | 描述
--- | --- | --- | --- | --- | ---
`bf` | 72 | yes | 128 | 60 | 基于Blowfish，2a的变体
`md5` | unlimited | no | 48 | 34 | 基于MD5加密
`xdes` | 8 | yes | 24 | 20 | 扩展的DES
`des` | 8 | no | 12 | 13 | 原始的UNIX加密

### 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'));

认证的示例：

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

如果输入的口令是正确的这个就返回`true`。

### F.25.2.2. `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-16中显示。

表 F-16. `crypt()`的重复计数

算法 | 缺省 | 最小 | 最大
--- | --- | --- | ---
`xdes` | 725 | 1 | 16777215
`bf` | 6 | 4 | 31

对于`xdes`，这里有一个附加的限制，那就是重复计数必须是奇数。

要选择一个合适的重复计数，考虑原始的DES加密设计是要在那个时间的硬件上每秒有4个散列的速度。 比4个散列每秒慢的可能会降低可用性。高于100散列每秒的可能太快了。

表 F-17给出了不同散列算法的相对缓慢的概述。 该表显示了在8字符口令里尝试所有字符的组合将会花费多长时间，假设口令只包含小写字母， 或者包含大小写字母和数字。在`crypt-bf`记录中， 斜线后的数字是`gen_salt`的`iter_count`参数。

表 F-17. 散列算法速度

算法 | 散列/sec | 对于 `[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

注意：

- 使用的这个机器是Intel Mobile Core 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`的213次循环/秒。（结果中非常小的不同与事实一致， `pgcrypto`中的`crypt-bf`实现和John the Ripper中使用的是同一个。）

请注意，"尝试所有组合"是不现实的。不寻常的密码破解在字典的帮助下完成， 包含普通的单词和它们的各种转变。所以，即使有点类似单词的密码可能比上述建议的数字破解的更快， 而一个6字符不像单词的密码可能避开破解。或者不能。

## F.25.3. PGP 加密功能

该功能实现了部分OpenPGP (RFC 4880)标准的加密。支持对称秘钥和公共秘钥的加密。

一条加密的PGP消息包含2个部分，或数据包：

- 数据包包含一个会话秘钥—加密了的对称秘钥或者是公共秘钥。

- 数据包包含带有会话秘钥的加密数据。

当带有对称秘钥（如一个口令）加密时：

1. 给定的口令使用String2Key (S2K)算法散列。这和`crypt()`算法很相似— 自觉地变慢并且带有随机salt—但是它产生一个全长的二进制秘钥。

2. 如果需要一个单独的会话秘钥，将会产生一个新的随机秘钥。否则将直接使用S2K秘钥作为会话秘钥。

3. 如果直接使用S2K秘钥，那么只有S2K设置将被放入到会话秘钥包。 否则会话秘钥将用S2K秘钥加密然后放入会话秘钥包。

当使用公共秘钥加密时：

1. 将会产生一个新的随机会话秘钥。

2. 它使用公共密钥加密并放入会话秘钥包中。

两种情况下数据被加密的处理如下：

1. 可选的数据操作：压缩，转换成UTF-8，和/或行尾的转换。

2. 数据带有一块随机字节的前缀。这相当于使用一个随机的IV。

3. 附加上一个随机前缀和数据的SHA1散列。

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`加密`data`。 `options`参数可以包含选项设置，就像下面描述的那样。

### 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消息。

用`pgp_sym_decrypt`解密`bytea`数据是不允许的。 这是为了避免输出不合法的字符数据。用`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`中给出密码。 如果没有密码，但是你希望指定选项，你需要给出一个空的密码。

用`pgp_pub_decrypt`解密`bytea`数据是不允许的。 这是为了避免输出不合法的字符数据。用`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. PGP功能的选项

选项的命名类似于GnuPG。选项的值应该在等号后面给出；选项之间用逗号隔开。例如：

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

除了`convert-crlf`之外的所有选项只应用到加密函数。 解密函数从PGP数据中获得参数。

最有趣的选项可能就是`compress-algo`和`unicode-mode`了。 其余的应该有合理的默认值。

#### F.25.3.7.1. cipher-algo

要使用的密码算法。

值: bf, aes128, aes192, aes256 (OpenSSL-only: `3des`, `cast5`)
缺省: aes128
适用于: pgp_sym_encrypt, pgp_pub_encrypt

#### F.25.3.7.2. compress-algo

要使用的压缩算法。只有PostgreSQL带有zlib建立时可以使用。

值:
0 - 没有压缩
1 - ZIP 压缩
2 - ZLIB 压缩 (= ZIP 加上元数据和块 CRCs)
缺省: 0
适用于: pgp_sym_encrypt, pgp_pub_encrypt

#### F.25.3.7.3. 压缩级别

压缩多少。较高层次压缩较小但是较慢。0表示禁用压缩。

值: 0, 1-9
缺省: 6
适用于: pgp_sym_encrypt, pgp_pub_encrypt

#### F.25.3.7.4. 转换 crlf

在加密时是否将`\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

#### F.25.3.7.5. 禁用 mdc

不要用SHA-1保护数据。唯一使用这个选项的理由是为了实现与古老的PGP产品的兼容， 该产品早于SHA-1受保护的包添加到RFC 4880。最近的gnupg.org和pgp.com软件也很好的支持它。

值: 0, 1
缺省: 0
适用于: pgp_sym_encrypt, pgp_pub_encrypt

#### F.25.3.7.6. sess秘钥

使用单独的会话秘钥。公共秘钥加密总是使用一个单独的会话秘钥；这个选项是为了对称秘钥加密， 这在默认情况下是直接使用S2K秘钥的。

值: 0, 1
缺省: 0
适用于: pgp_sym_encrypt

#### F.25.3.7.7. s2k 模式

使用S2K算法。

值:
0 - 没有salt。 危险的!
1 - 有salt但是带有固定的重复计数。
3 - 变量重复计数。
缺省: 3
适用于: pgp_sym_encrypt

#### F.25.3.7.8. s2k 摘要算法

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

值: md5, sha1
缺省: sha1
适用于: pgp_sym_encrypt

#### F.25.3.7.9. s2k 密码算法

加密单独的会话秘钥使用哪个密码。

值: bf, aes, aes128, aes192, aes256
缺省: use cipher-algo
适用于: pgp_sym_encrypt

#### F.25.3.7.10. unicode 模式

是否要转换文本数据从数据库内部编码到UTF-8及以前。如果你的数据库已经是UTF-8， 将不需要转换，但是消息将被标记为UTF-8。没有这个选项将不会这样。

值: 0, 1
缺省: 0
适用于: pgp_sym_encrypt, pgp_pub_encrypt

### F.25.3.8. 用 GnuPG 产生 PGP 秘钥

要生成一个新的秘钥：

    gpg --gen-key

首选的秘钥类型是"DSA and 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`， The GNU Privacy Handbook和其他http://www.gnupg.org上的文档。

### F.25.3.9. PGP 代码的限制

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

- 不支持加密秘钥作为主秘钥。因为通常不建议这样的做法，这应该不是一个问题。

- 不支持几个子秘钥。这可能看起来像是一个问题，因为这是习惯的做法。另一方面， 不应该使用带有`pgcrypto`的定期GPG/PGP秘钥，而是创建一个新的秘钥， 因为使用场景相当不同。

## F.25.4. 行加密功能

这些功能在数据上只运行一个密码；它们没有任何比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_iv`和`decrypt_iv`中，`iv` 参数是CBC模式的初始值；在ECB中忽略。如果不正好是块的大小则截断或用0补齐。 在没有这个参数的函数里缺省全部为0。

## F.25.5. 随机数据函数

    gen_random_bytes(count integer) returns bytea

密码强随机字节的返回`count`。一次最多可以提取1024个字节。 这是为了避免排干随机发生器池。

    gen_random_uuid() returns uuid

返回一个版本 4 (随机) UUID.

## F.25.6. 注意

### F.25.6.1. 配置

`pgcrypto`根据主PostgreSQL `configure`脚本的调查结果配置它本身。 影响它的选项是`--with-zlib`和`--with-openssl`。

当用zlib编译时，PGP加密函数可以在加密之前压缩数据。

当用OpenSSL编译时，有更多算法可用。公共秘钥加密函数也会更快， 因为OpenSSL有更多优化了的BIGNUM函数。

表 F-18. 带有和不带有 OpenSSL 的功能性总结

功能性 | 内建 | 带有 OpenSSL
--- | --- | ---
MD5 | yes | yes
SHA1 | yes | yes
SHA224/256/384/512 | yes | yes (注意 1)
其他摘要算法 | no | yes (注意 2)
Blowfish | yes | yes
AES | yes | yes (注意 3)
DES/3DES/CAST5 | no | yes
行加密 | yes | yes
PGP 对称加密 | yes | yes
PGP 公共秘钥加密 | yes | yes

注意：

1. SHA2算法在版本 0.9.8 的时候添加到了OpenSSL。对于更老的版本， `pgcrypto`使用内建的代码。

2. 任何OpenSSL支持的摘要算法是自动获得的。这对于密码来说是不可能的，密码需要明确的支持。

3. AES自版本 0.9.7 以来包含在OpenSSL中了。对于更老的版本， `pgcrypto`使用内建的代码。

### F.25.6.2. NULL 处理

就像SQL中的标准，如果任一参数是NULL，那么所有函数都返回NULL。 这在粗心的使用中可能会造成安全风险。

### F.25.6.3. 安全限制

所有`pgcrypto`函数在数据库服务器内部运行。这意味着`pgcrypto` 和客户端应用之间的所有数据和口令移动都是以明文的形式。因此必须：

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

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

如果你做不到，那么最好在客户端应用内部做crypto。

该实现并不抗拒side-channel attacks。例如，`pgcrypto`解密函数完成给定长度密文变化需要的时间。

### F.25.6.4. 有用的阅读

- http://www.gnupg.org/gph/en/manual.html

  GNU 隐私手册。

- http://www.openwall.com/crypt/

  crypt-blowfish算法描述。

- http://www.stack.nl/~galactus/remailers/passphrase-faq.html

  如何选择一个好的密码。

- http://world.std.com/~reinhold/diceware.html

  选择密码的有趣想法。

- http://www.interhack.net/people/cmcurtin/snake-oil-faq.html

  描述密码学的优劣。

### F.25.6.5. 技术参考文献

- http://www.ietf.org/rfc/rfc4880.txt

  OpenPGP 消息格式。

- http://www.ietf.org/rfc/rfc1321.txt

  MD5 消息摘要算法。

- http://www.ietf.org/rfc/rfc2104.txt

  HMAC:散列的消息认证。

- http://www.usenix.org/events/usenix99/provos.html

  crypt-des、crypt-md5和bcrypt算法的比较。

- http://en.wikipedia.org/wiki/Fortuna_(PRNG)

  Fortuna CSPRNG的描述。

- http://jlcooke.ca/random/

  基于Jean-Luc Cooke Fortuna的Linux `/dev/random`驱动器。

- http://research.cyber.ee/~lipmaa/crypto/

  密码学指针集合。

## F.25.7. 作者

Marko Kreen `<markokr@gmail.com>`

`pgcrypto`使用来自下列源码的代码：

算法 | 作者 | 起源
--- | --- | ---
DES 加密 | David Burren 和其他人 | FreeBSD libcrypt
MD5 加密 | Poul-Henning Kamp | FreeBSD libcrypt
Blowfish 加密 | Solar Designer | www.openwall.com
Blowfish 密码 | Simon Tatham | PuTTY
Rijndael 密码 | Brian Gladman | OpenBSD sys/crypto
MD5哈希和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

上一页 | 起始页 | 下一页
pg_buffercache | 上一级 | pg_freespacemap