Pgcrypto

From PostgreSQL 中文维基, PostgreSQL 中文站, PostgreSQL 中国社区, PostgreSQL Chinese community

Jump to: navigation, search

[编辑] pgcrypto

目录

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

[编辑] 通用的哈希函数

[编辑] digest()

   digest(data text, type text) returns bytea
   digest(data bytea, type text) returns bytea

为给出的数据计算一个二进制哈希。type 是使用的算法。标准算法有 md5,sha1,sha224,sha256,sha384 和 sha512。如果带着 OpenSSL 制作 pgcrypto,那么有更多可用的算法,如表 F-21 所示。

如果你希望以十六进制字串获取信息摘要,那么在结果上使用 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 text, type text) returns bytea

使用密钥 key 为数据计算哈希后的 MAC (信息认证码,Message Authencate Code,不是以太网的 MAC 地址)。类型和 digest() 里的一样。

这个类似于 digest(),但是哈希只有在知道密钥后才能重新计算。这样就避免了有人修改数据同时也修改了待匹配的哈希的情况。

如果密钥比哈希块大小更大,那么它首先会被哈希,然后再把结果用做密钥。

[编辑] 口令哈希函数

函数 crypt() 和 gen_salt() 是专门设计给哈希口令使用的。crypt() 进行哈希而 gen_salt() 为其准备算法参数。

crypt() 里的算法和类似 MD5 或者 SHA1 那样的常用哈希算法不同的地方有:

  1. 它们很慢。因为数据很少,所以这是唯一的让蛮力破解口令困难些的方法。
  2. 它们使用一个随机值,叫盐粒,这样口令相同的用户会有不同的加密口令。这也是防止逆向算法的额外的防护。
  3. 它们在结果里包含算法类型,因此用不同算法哈希的口令可以共存。
  4. 它们中有些是可适应的 -- 也就是说如果计算机变得更快了,你可以调节算法变得更慢,而不用引入和现有口令不兼容的东西。
表 F-18. crypt() 支持的算法
算法 最大密码长度 是否可适应? 盐粒位数 描述
bf 72 128 基于 Blowfish 的,变体 2a
md5 无限 48 基于 MD5 的加密
xdes 8 24 扩展的 DES
des 8 12 最初的 UNIX 加密

[编辑] crypt()

   crypt(password text, salt text) returns text

计算一个 crypt(3) 风格的密码哈希。在存储一个新的密码的时候,你需要使用 gen_salt() 来生成新的盐粒值。要检查一个密码,把已存储的哈希值当盐粒传入,然后检查结果是否和存储的值相同。

设置新密码的例子:

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

认证的例子:

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

如果输入的密码是正确的,这个语句返回真。

[编辑] gen_salt()

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

为在 crypt() 中使用生成一个新的随机盐粒。盐粒字串也告诉 crypt() 应该使用哪种算法。

参数 type 声明哈希算法。接受的类型是:des,xdes,md5 和 bf。

iter_count 参数让用户声明循环次数,用于那些有循环次数的算法。次数越多,计算密码的哈希的时间越长,因而破解越要花时间。当然,如果次数抬高,那么计算一个哈希的时间可能需要好几年 --- 这就不太实际了。如果省略 iter_count 参数,则使用缺省的次数。iter_count 的可用值取决于算法:

表 F-19. crypt() 的循环次数
算法 缺省 最小值 最大值
xdes 725 1 16777215
bf 6 4 31

对于 xdes,还有额外的限制是循环次数必须是奇数。

想选取一个合适的循环次数,就要知道最早的 DES 在设计的时候所拥有的硬件是每秒钟能做 4 个哈希。比每秒 4 哈希慢可能就会让使用者觉得不便。而比每秒 100 次哈希还快就可能是太快了。

下面是一个表,它给出了不同哈希算法的速度的一个概要。这个表显示了在一个 8 字符的口令里尝试所有组合所需要的时间,假设口令构成是:包含全部小写字母,或者大小写字母和数字。在 crypt-bf 项后面,些港后面的数字是 gen_salt 的 iter_count 参数。

表 F-20. 哈希算法的速度
算法 哈希/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.5 GHz 的 Pentium 4.
  • crypt-des 和 crypt-md5 算法数是从 John the Ripper v1.6.38 测试输出获取的。
  • 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 字符常的非单词样的口令则可能避免被破解,当然,也可能无法避免。

[编辑] PGP 加密函数

这里的函数实现了 OpenPGP (RFC 4880) 标准的加密部门。支持对称密钥和公开密钥加密。

一个加密后的 PGP 消息包含两个部分(或者说两个包):

  • 包含会话密钥的包 --- 此会话密钥要么是对称密钥加密要么是公开密钥加密的。
  • 包含用会话密钥加密后的包。

在使用对称密钥加密时(也就是用口令加密):

  1. 给出的密钥使用 String2Key(S2K)算法哈希过。这点和 crypt() 算法很像 --- 但它生成的是全长的二进制密钥。
  2. 如果要求一个独立的会话密钥,那么就会生成一个新的随机密钥。否则会直接拿 S2K 密钥做会话密钥。
  3. 如果直接使用 S2K 密钥,那么只有 S2K 设置会放进会话密钥包中。否则会话密钥会用 S2K 密钥加密然后放到会话密钥包中。

在使用公开密钥加密时:

  1. 生成一个新的随机会话密钥。
  2. 然后用公钥加密并放进会话密钥包中。

在两种情况下,要加密的数据都是按照下面过程处理的:

  1. 可选的数据操作:压缩,转换成 UTF-8,和/或转换行结束符。
  2. 用一块随机字节做数据的前缀。这么做等效于使用随机 IV。
  3. 附加一个随机前缀和数据的 SHA1 哈希。
  4. 所有这些都都是用会话密钥加密并放进数据包中。

[编辑] 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 加密数据。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 加密数据。给这个函数传递一个私钥会产生一个错误。

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。

它可以返回两种特定的密钥 ID:

  • SYMKEY
消息是用一个对称的密钥加密的。
  • ANYKEY
消息是公钥加密的,但是密钥 ID 已经删除了。这就意味着你需要尝试自己的所有私钥来看看是哪个加密的它。pgcrypto 自己并不生成这样的信息。

请注意不同的键可能有相同的 ID。这是一个罕见但又正常的情况。这时候客户端应用应该尝试用所有的密钥进行解密,看看哪个何时 --- 就像操作 ANYKEY 一样。

[编辑] armor(), dearmor()

   armor(data bytea) returns text
   dearmor(data text) returns bytea

这些函数把二进制数据打包/解包成 PGP Ascii 铠甲格式,基本上就是 Base64 加上 CRC 和一些额外的格式。

[编辑] PGP 函数的选项

选项的命名是类似 GnuPG的。一个选项的值应该在等号标志之后给出;每个选项之间用逗号分隔:

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

除了 convert-crlf 之外所有选项都只适用于加密函数。解密函数从 PGP 数据中获取参数。

最有趣的选项可能是 compress-algo 和 unicode-mode。剩下的都可以有合理的缺省。

[编辑] cipher-algo

使用哪个加密算法。

   数值:bf,aes128,aes192,aes256 (只有 OpenSSL:3des,cast5)
   缺省:aes128
   适用于:pgp_sym_encrypt, pgp_pub_encrypt
[编辑] compress-algo

要使用哪个压缩算法。只有在 PostgreSQL 带着 zlib 制作的时候才可用。

   值:
     0 - 无压缩
     1 - ZIP 压缩
     2 - ZLIB 压缩 (= ZIP 加上元数据和 block CRC)
   缺省:0
   适用于:pgp_sym_encrypt, pgp_pub_encrypt
[编辑] compress-level

压缩程度如何。高级别的压缩压缩得更小,但是更慢。0 禁止压缩。

   数值:0, 1-9
   缺省:6
   适用于:pgp_sym_encrypt, pgp_pub_encrypt
[编辑] convert-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
[编辑] disable-mdc

不要用 SHA-1 保护数据。使用这个选项的唯一的好理由是为了和古老的 PGP 产品 --- 在向 RFC 4880 中添加 SHA-1 保护包之前的产品兼容。最近的 gnupg.org 和 pgp.com 软件都支持得很好。

   数值:0, 1
   缺省:0
   适用于:pgp_sym_encrypt, pgp_pub_encrypt
[编辑] enable-session-key

使用独立的会话密钥。公钥加密总是使用独立的会话密钥;这个选项是给对称密钥加密用的,缺省的时候是直接用 S2K 密钥。

   数值:0, 1
   缺省:0
   适用于:pgp_sym_encrypt
[编辑] s2k-mode

使用哪个 S2K 算法。

   数值:
     0 - 无盐粒儿。危险!
     1 - 有盐粒儿,但是循环次数固定。
     3 - 变量循环次数。
   缺省:3
   适用于:pgp_sym_encrypt
[编辑] s2k-digest-algo

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

   数值:md5, sha1
   缺省:sha1
   适用于:pgp_sym_encrypt
[编辑] s2k-cipher-algo

加密独立的会话密钥时使用哪个加密算法。

   数值:bf, aes, aes128, aes192, aes256
   缺省:使用 cipher-algo
   适用于:pgp_sym_encrypt
[编辑] unicode-mode

是否把来自数据库内部编码的文本信息在内部编码和 UTF-8 之间互转。如果你的数据库已经是 UTF-8,就不必做转换,但消息会标记为 UTF-8。没有这个选项它就不是 UTF-8。

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

[编辑] 用 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。

更多细节请参考 gpg 的 man,GNU 隐私手册和其它在 http://www.gnupg.org 上的信息。

[编辑] PGP代码的限制

  • 不支持签名。同时也就意味着不检查加密的子密钥是否属于主密钥。
  • 不支持加密密钥做主密钥。因为通常并不鼓励这么做,所以这应该不是个问题。
  • 不支持多个子密钥。这一点很可能是一个问题,因为这是常见的做法。从另外一个角度来看,你不应该在 pgcrypto 里用自己常用的 GPG/PGP 密钥,而是应该创建一个新的,因为使用的场景是差别相当大的。

[编辑] 裸加密函数

这些函数只是在数据上执行加密,并不具有任何 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 字串的语法是:

  算法 [ - 模式 ] [ /pad: 填充物 ]

这里的算法是下列之一:

  • bf — Blowfish
  • aes — AES (Rijndael-128)

模式是下列之一:

  • cbc — 下一个块依赖于前一块(缺省)
  • ecb — 每个块都分别加密(只用于测试)

填充物是下列之一:

  • 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 字节。这样可以避免耗尽随机性发生器池中的数据。

[编辑] 注意

[编辑] 配置

pgcrypto 根据自己在 PostgreSQL 的主配置脚本中找到的东西配置自己。影响它的选项是 --with-zlib 和 --with-openssl。

在编译了 zlib 的时候,PGP 加密函数就可以在加密之前压缩数据。

在编译了 OpenSSL 的时候,将会有更多算法可用。而且公钥加密函数也会更快,因为 OpenSSL 的 BIGNUM 函数优化得更好。

表 F-21. 有无 OpenSSL 的功能对比概述
功能 内置 有 OpenSSL
MD5
SHA1
SHA224/256/384/512 是(注 1)
其它摘要算法 是(注 2)
Blowfish
AES 是(注 3)
DES/3DES/CAST5
裸加密
PGP 对称加密
PGP 公钥加密

注:

  1. SHA2 在 OpenSSL 0.9.8 版本中添加。对于更老的版本,pgcrypto 将使用内置的代码。
  2. 会自动选取任何 OpenSSL 支持的摘要算法。这点对加密来说是不可能的,因为加密需要明确地支持。
  3. AES 是在 OpenSSL 0.9.7 里考试支持的。对于更老的版本,pgcrypto 会使用内置的代码。

[编辑] 处理 NULL

和 SQL 标准一样,如果任意参数是 NULL,那么所有函数都返回 NULL。在粗心地使用中,这可能会导致安全风险。

[编辑] 安全限制

所有 pgcrypto 函数都运行在数据库服务器内部。那就意味着所有在 pgcrypto 和客户端应用之间跑的数据都是明文。因此你必须:

  1. 本地连接或者使用 SSL 连接。
  2. 信任两边的系统和数据库管理员。

如果不能,那最好是在客户端应用内部进行加密。

[编辑] 有用的读物

GNU 隐私手册。
描述 crypt-blowfish 算法。
如何选择一个好的口令。
选取密码的有趣观点。
描述好的和坏的加密方法。

[编辑] 技术引用

OpenPGP 信息格式。
MD5 信息摘要算法。
HMAC: 用于信息认证的密钥哈希(Keyed-Hashing for Message Authentication)。
crypt-des,crypt-md5和 bcrypt 算法的比较。
DES,3DES 和 AES 的标准。
Fortuna CSPRNG 的描述。
Linux 的以 Jean-Luc Cooke Fortuna 为基础的 /dev/random 驱动。
一堆加密相关的连接。

[编辑] 作者

Marko Kreen <markokr@gmail.com>

pgcrypto 使用了下列源代码中的代码:

表 F-22. 致谢
算法 作者 源初代码
DES crypt David Burren 及其他 FreeBSD libcrypt
MD5 crypt Poul-Henning Kamp FreeBSD libcrypt
Blowfish crypt Solar Designer www.openwall.com
Blowfish cipher Simon Tatham PuTTY
Rijndael cipher Brian Gladman OpenBSD sys/crypto
MD5 和 SHA1 WIDE 项目 KAME kame/sys/crypto
SHA256/384/512 Aaron D. Gifford OpenBSD sys/crypto
BIGNUM 数学 Michael J. Fromberger dartmouth.edu/~sting/sw/imath
Personal tools