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中详细说明。
如果你希望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)有以下方面的不同:
他们的速度很慢。因为数据很少,所以这是唯一的让蛮力破解口令困难些的方法。
它们使用随机值,称为salt,所以有相同口令的用户将会有不同加密了的口令。 也是也对反向算法的附加防御。
它们在结果中包括算法类型,所以不同算法的口令散列可以共存。
它们中的一些是自适应的,这意味着当计算机更快速时,你可以将算法调整的慢一些, 而不会引入与现有口令的不相容。
Table F-15列出了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中显示。
Table F-16. crypt()
的重复计数
算法 | 缺省 | 最小 | 最大 |
---|---|---|---|
xdes |
725 | 1 | 16777215 |
bf |
6 | 4 | 31 |
对于xdes
,这里有一个附加的限制,那就是重复计数必须是奇数。
要选择一个合适的重复计数,考虑原始的DES加密设计是要在那个时间的硬件上每秒有4个散列的速度。 比4个散列每秒慢的可能会降低可用性。高于100散列每秒的可能太快了。
Table F-17给出了不同散列算法的相对缓慢的概述。 该表显示了在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个部分,或数据包:
数据包包含一个会话秘钥—加密了的对称秘钥或者是公共秘钥。
数据包包含带有会话秘钥的加密数据。
当带有对称秘钥(如一个口令)加密时:
给定的口令使用String2Key (S2K)算法散列。这和
crypt()
算法很相似— 自觉地变慢并且带有随机salt—但是它产生一个全长的二进制秘钥。如果需要一个单独的会话秘钥,将会产生一个新的随机秘钥。否则将直接使用S2K秘钥作为会话秘钥。
如果直接使用S2K秘钥,那么只有S2K设置将被放入到会话秘钥包。 否则会话秘钥将用S2K秘钥加密然后放入会话秘钥包。
当使用公共秘钥加密时:
将会产生一个新的随机会话秘钥。
它使用公共密钥加密并放入会话秘钥包中。
两种情况下数据被加密的处理如下:
可选的数据操作:压缩,转换成UTF-8,和/或行尾的转换。
数据带有一块随机字节的前缀。这相当于使用一个随机的IV。
附加上一个随机前缀和数据的SHA1散列。
所有这些都带有会话秘钥加密,并放入数据包中。
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上的文档。
F.25.3.9. PGP 代码的限制
不支持签名。这也意味着不检查加密子秘钥是否属于主秘钥。
不支持加密秘钥作为主秘钥。因为通常不建议这样的做法,这应该不是一个问题。
不支持几个子秘钥。这可能看起来像是一个问题,因为这是习惯的做法。另一方面, 不应该使用带有
pgcrypto
的定期GPG/PGP秘钥,而是创建一个新的秘钥, 因为使用场景相当不同。
F.25.4. 行加密功能
这些功能在数据上只运行一个密码;它们没有任何比PGP加密更先进的特性。 因此它们有一些主要的问题:
它们使用用户秘钥直接作为加密秘钥。
它们不提供任何完整性检查,来看看加密的数据是否被修改了。
它们希望用户自己管理所有加密参数,即使是IV。
它们不处理文本。
所以,随着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
— Blowfishaes
— 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 |
注意:
SHA2算法在版本 0.9.8 的时候添加到了OpenSSL。对于更老的版本,
pgcrypto
使用内建的代码。任何OpenSSL支持的摘要算法是自动获得的。这对于密码来说是不可能的,密码需要明确的支持。
AES自版本 0.9.7 以来包含在OpenSSL中了。对于更老的版本,
pgcrypto
使用内建的代码。
F.25.6.2. NULL 处理
就像SQL中的标准,如果任一参数是NULL,那么所有函数都返回NULL。 这在粗心的使用中可能会造成安全风险。
F.25.6.3. 安全限制
所有pgcrypto
函数在数据库服务器内部运行。这意味着pgcrypto
和客户端应用之间的所有数据和口令移动都是以明文的形式。因此必须:
本地连接或使用SSL连接。
同时信任系统和数据库管理员。
如果你做不到,那么最好在客户端应用内部做crypto。
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://csrc.nist.gov/cryptval/des.htm
DES、3DES和AES标准。
http://en.wikipedia.org/wiki/Fortuna_(PRNG))
Fortuna CSPRNG的描述。
-
基于Jean-Luc Cooke Fortuna的Linux
/dev/random
驱动器。 http://research.cyber.ee/~lipmaa/crypto/
密码学指针集合。
F.25.7. 作者
Marko Kreen <[[email protected]](mailto:[email protected])>
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 |