ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
# 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建立,那么更多算法可用,在 [Table F-18](#calibre_link-2295)中详细说明。 如果你希望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. 它们中的一些是自适应的,这意味着当计算机更快速时,你可以将算法调整的慢一些, 而不会引入与现有口令的不相容。 [Table F-15](#calibre_link-2296)列出了`crypt()` 函数支持的算法。 **Table F-15\. `crypt()`支持的算法** | 算法 | 最大口令长度 | 自适应? | Salt位 | 描述 | | --- | --- | --- | --- | --- | | `bf` | 72 | yes | 128 | 基于Blowfish,2a的变体 | | `md5` | unlimited | no | 48 | 基于MD5加密 | | `xdes` | 8 | yes | 24 | 扩展的DES | | `des` | 8 | no | 12 | 原始的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`的允许值取决于算法,在[Table F-16](#calibre_link-2297)中显示。 **Table F-16\. `crypt()`的重复计数** | 算法 | 缺省 | 最小 | 最大 | | --- | --- | --- | --- | | `xdes` | 725 | 1 | 16777215 | | `bf` | 6 | 4 | 31 | 对于`xdes`,这里有一个附加的限制,那就是重复计数必须是奇数。 要选择一个合适的重复计数,考虑原始的DES加密设计是要在那个时间的硬件上每秒有4个散列的速度。 比4个散列每秒慢的可能会降低可用性。高于100散列每秒的可能太快了。 [Table F-17](#calibre_link-2298)给出了不同散列算法的相对缓慢的概述。 该表显示了在8字符口令里尝试所有字符的组合将会花费多长时间,假设口令只包含小写字母, 或者包含大小写字母和数字。在`crypt-bf`记录中, 斜线后的数字是`gen_salt`的`iter_count`参数。 **Table F-17\. 散列算法速度** | 算法 | 散列/sec | 对于 `[a-z]` | 对于 `[A-Za-z0-9]` | | --- | --- | --- | --- | | `crypt-bf/8` | 28 | 246 年 | 251322 年 | | `crypt-bf/7` | 57 | 121 年 | 123457 年 | | `crypt-bf/6` | 112 | 62 年 | 62831 年 | | `crypt-bf/5` | 211 | 33 年 | 33351 年 | | `crypt-md5` | 2681 | 2.6 年 | 2625 年 | | `crypt-des` | 362837 | 7 天 | 19 年 | | `sha1` | 590223 | 4 天 | 12 年 | | `md5` | 2345086 | 1 天 | 3 年 | 注意: * 使用的这个机器是1.5GHz Pentium 4。 * `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`的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\. 启用会话秘钥 使用单独的会话秘钥。公共秘钥加密总是使用一个单独的会话秘钥;这是为了对称秘钥加密, 这在默认情况下是直接使用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/gph/en/manual.html)和其他[http://www.gnupg.org](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个字节。 这是为了避免排干随机发生器池。 ## F.25.6\. 注意 ### F.25.6.1\. 配置 `pgcrypto`根据主PostgreSQL `configure`脚本的调查结果配置它本身。 影响它的选项是`--with-zlib`和`--with-openssl`。 当用zlib编译时,PGP加密函数可以在加密之前压缩数据。 当用OpenSSL编译时,有更多算法可用。公共秘钥加密函数也会更快, 因为OpenSSL有更多优化了的BIGNUM函数。 **Table 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。 ### F.25.6.4\. 有用的阅读 * [http://www.gnupg.org/gph/en/manual.html](http://www.gnupg.org/gph/en/manual.html) GNU 隐私手册。 * [http://www.openwall.com/crypt/](http://www.openwall.com/crypt/) crypt-blowfish算法描述。 * [http://www.stack.nl/~galactus/remailers/passphrase-faq.html](http://www.stack.nl/~galactus/remailers/passphrase-faq.html) 如何选择一个好的密码。 * [http://world.std.com/~reinhold/diceware.html](http://world.std.com/~reinhold/diceware.html) 选择密码的有趣想法。 * [http://www.interhack.net/people/cmcurtin/snake-oil-faq.html](http://www.interhack.net/people/cmcurtin/snake-oil-faq.html) 描述密码学的优劣。 ### F.25.6.5\. 技术参考文献 * [http://www.ietf.org/rfc/rfc4880.txt](http://www.ietf.org/rfc/rfc4880.txt) OpenPGP 消息格式。 * [http://www.ietf.org/rfc/rfc1321.txt](http://www.ietf.org/rfc/rfc1321.txt) MD5 消息摘要算法。 * [http://www.ietf.org/rfc/rfc2104.txt](http://www.ietf.org/rfc/rfc2104.txt) HMAC:散列的消息认证。 * [http://www.usenix.org/events/usenix99/provos.html](http://www.usenix.org/events/usenix99/provos.html) crypt-des、crypt-md5和bcrypt算法的比较。 * [http://csrc.nist.gov/cryptval/des.htm](http://csrc.nist.gov/cryptval/des.htm) DES、3DES和AES标准。 * [http://en.wikipedia.org/wiki/Fortuna_(PRNG)](http://en.wikipedia.org/wiki/Fortuna_(PRNG)) Fortuna CSPRNG的描述。 * [http://jlcooke.ca/random/](http://jlcooke.ca/random/) 基于Jean-Luc Cooke Fortuna的Linux `/dev/random`驱动器。 * [http://research.cyber.ee/~lipmaa/crypto/](http://research.cyber.ee/~lipmaa/crypto/) 密码学指针集合。 ## F.25.7\. 作者 Marko Kreen `<[markokr@gmail.com](mailto: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 |