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, sha384sha512。 如果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

为带有键keydata计算散列的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位输出长度描述
bf72yes12860基于Blowfish,2a的变体
md5unlimitedno4834基于MD5加密
xdes8yes2420扩展的DES
des8no1213原始的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, md5bf

iter_count参数让用户指定重复计数,为这一个算法。计数值越高, 拿它去散列口令的次数越多,因此解开它的次数也越多。尽管太高的计数来计算一个散列可能会用几年的时间, 这有点不切实际。如果省略了iter_count参数,那么使用缺省的重复计数。 iter_count的允许值取决于算法,在表 F-16中显示。

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

算法缺省最小最大
xdes725116777215
bf6431

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

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

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

表 F-17. 散列算法速度

算法散列/sec对于 [a-z]对于 [A-Za-z0-9]相对于md5 hash的持续时间
crypt-bf/817924 年3927 年100k
crypt-bf/736482 年1929 年50k
crypt-bf/671681 年982 年25k
crypt-bf/513504188 天521 年12.5k
crypt-md517158415 天41 年1k
crypt-des23221568157.5 分钟108 天7
sha13777427290 分钟68 天4
md5 (hash)15008550422.5 分钟17 天1

注意:

  • 使用的这个机器是Intel Mobile Core i3。

  • crypt-descrypt-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加密dataoptions参数可以包含选项设置,就像下面描述的那样。

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-algounicode-mode了。 其余的应该有合理的默认值。

F.25.3.7.1. cipher-algo

要使用的密码算法。

值: bf, aes128, aes192, aes256 (OpenSSL-only: 3descast5)
缺省: 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 gpgThe 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是下列之一:

mode是下列之一:

padding是下列之一:

所以,例如,这些是相等的:

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

encrypt_ivdecrypt_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
MD5yesyes
SHA1yesyes
SHA224/256/384/512yesyes (注意 1)
其他摘要算法noyes (注意 2)
Blowfishyesyes
AESyesyes (注意 3)
DES/3DES/CAST5noyes
行加密yesyes
PGP 对称加密yesyes
PGP 公共秘钥加密yesyes

注意:

  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.5. 技术参考文献

F.25.7. 作者

Marko Kreen

pgcrypto使用来自下列源码的代码:

算法作者起源
DES 加密David Burren 和其他人FreeBSD libcrypt
MD5 加密Poul-Henning KampFreeBSD libcrypt
Blowfish 加密Solar Designerwww.openwall.com
Blowfish 密码Simon TathamPuTTY
Rijndael 密码Brian GladmanOpenBSD sys/crypto
MD5哈希和SHA1WIDE ProjectKAME kame/sys/crypto
SHA256/384/512 Aaron D. GiffordOpenBSD sys/crypto
BIGNUM mathMichael J. Frombergerdartmouth.edu/~sting/sw/imath