E.28. ltcrypto

E.28.1. 通用哈希函数
E.28.2. 密码哈希函数
E.28.3. PGP Encryption Functions
E.28.4. 原始加密函数
E.28.5. 随机数据函数
E.28.6. 注释

ltcrypto 模块为 LightDB 提供了加密功能。

该模块被认为是“trusted”(可信任的),也就是说,它可以被非超级用户安装,只要他们在当前数据库上具有“CREATE”特权。

E.28.1. 通用哈希函数

E.28.1.1.  digest()

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

计算给定的data的二进制哈希值。 type是要使用的算法。 标准算法有md5sha1sha224 sha256sha384sha512。 如果使用 GmSSL 编译了 ltcrypto,则可以使用更多算法,详见 Table E.47

如果您想将哈希值作为十六进制字符串返回,请对结果使用encode()函数。例如:

CREATE OR REPLACE FUNCTION sha1(bytea) returns text AS $$
    SELECT encode(digest($1, 'sha1'), 'hex')
$$ LANGUAGE SQL STRICT IMMUTABLE;

E.28.1.2.  hmac()

                hmac(data text, key text, type text) returns bytea
                hmac(data bytea, key bytea, type text) returns bytea
            

计算带有密钥keydata的哈希MAC。 typedigest()中的一致。

这类似于digest(),但只有知道密钥才能重新计算哈希值。 这可以防止有人篡改数据并更改哈希值以匹配。

如果密钥大于哈希块大小,它将首先被哈希,然后使用哈希结果作为密钥。

E.28.2. 密码哈希函数

函数crypt()gen_salt()专门用于哈希密码。 crypt()用于哈希,gen_salt()用于准备算法参数。

crypt()中的算法与通常的MD5或SHA1哈希算法有以下区别:

  1. 它们不够快。由于数据量很小,这是使暴力破解密码变得困难的唯一方法。

  2. 它们使用一个随机值,称为salt,以便具有相同密码的用户将具有不同的加密密码。 这也是对抗反向算法的额外防御措施。

  3. 它们在结果中包含算法类型,因此可以共存使用使用不同算法哈希的密码。

  4. 其中一些是适应性的,这意味着当计算机变得更快时,可以调整算法使其变慢,而不会与现有密码不兼容。

Table E.44列出了crypt()函数支持的算法。

Table E.44.  crypt()支持的算法

算法最大密码长度适应性?盐位数输出长度描述
bf 72yes12860基于Blowfish的,2a变体
md5 无限制4834基于MD5的crypt
xdes 8yes2420扩展DES
des 81213原始UNIX crypt

E.28.2.1.  crypt()

                crypt(password text, salt text) 返回 text
            

计算password的crypt(3)格式哈希值。 当存储新密码时,需要使用gen_salt()生成新的salt值。 要检查密码,请将存储的哈希值作为salt传递,并测试结果是否与存储的值匹配。

设置新密码的示例:

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

验证的示例:

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

如果输入的密码正确,则返回true

E.28.2.2.  gen_salt()

                gen_salt(type text [, iter_count integer ]) 返回 text
            

crypt()生成一个新的随机盐字符串。 盐字符串还告诉crypt()使用哪个算法。

type参数指定哈希算法。 接受的类型包括:desxdesmd5 bf

iter_count参数允许用户指定迭代次数(对于具有迭代次数的算法)。 迭代次数越高,哈希密码所需的时间就越长,因此破解密码所需的时间也越长。 但是,如果迭代次数过高,计算哈希值所需的时间可能会长达数年,这在实际中是不可行的。 如果省略iter_count参数,则使用默认的迭代次数。 iter_count参数的允许值取决于算法,并在 Table E.45中显示。

Table E.45.  crypt()的迭代次数

算法默认值最小值最大值
xdes 725116777215
bf 6431

对于xdes,还有一个额外的限制,即迭代次数必须是奇数。

为了选择适当的迭代次数,请考虑原始的DES crypt是为每秒4个哈希值的硬件设计的。 每秒慢于4个哈希值可能会降低可用性。 每秒快于100个哈希值可能太快了。

Table E.46提供了不同哈希算法相对较慢的概述。 该表格显示了尝试在8个字符密码中所有字符组合所需的时间,假设密码仅包含小写字母,或大写和小写字母和数字。 在crypt-bf条目中,斜杠后面的数字是gen_salt iter_count参数。

Table E.46. 哈希算法速度

算法哈希值/秒对于[a-z] 对于[A-Za-z0-9] 相对于md5 hash的持续时间
crypt-bf/8 17924年3927年100k
crypt-bf/7 36482年1929年50k
crypt-bf/6 71681年982年25k
crypt-bf/5 13504188 天521 年12.5k
crypt-md5 17158415 天41 年1k
crypt-des 23221568157.5 分钟108 天7
sha1 3777427290 分钟68 天4
md5 (哈希) 15008550422.5 分钟17 天1

注意事项:

  • 使用的计算机是一台英特尔移动版Core i3。

  • crypt-descrypt-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 的循环速度为每秒 13506 次。(结果的微小差异符合 ltcrypto 中的 crypt-bf 实现与 John the Ripper 中使用的实现相同的事实。)

请注意,“尝试所有组合”并不是一项现实的任务。通常密码破解是通过使用包含常规单词和各种变形的字典来完成的。因此,即使是类似单词的密码也可能比上述数字快得多被破解,而一个6个字符的不像单词的密码可能会逃脱破解。也可能不会。

E.28.3. PGP Encryption Functions

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

加密的PGP消息由两个部分或数据包(packets)组成:

  • 包含会话密钥的数据包 — 可以是对称密钥或公钥加密。

  • 包含使用会话密钥加密的数据的数据包。

使用对称密钥(即密码)进行加密时:

  1. 给定的密码使用String2Key (S2K)算法进行哈希。这与crypt() 算法非常相似,目的是缓慢处理并带有随机盐,但它会生成一个完整长度的二进制密钥。

  2. 如果请求一个单独的会话密钥,将生成一个新的随机密钥。否则,S2K密钥将直接用作会话密钥。

  3. 如果要直接使用S2K密钥,则会将仅S2K设置放入会话密钥数据包中。否则,会话密钥将使用S2K密钥进行加密,并放入会话密钥数据包中。

使用公钥加密时:

  1. 生成一个新的随机会话密钥。

  2. 使用公钥对其进行加密,并将其放入会话密钥数据包中。

在任何一种情况下,要加密的数据都按以下方式处理:

  1. 可选的数据处理:压缩、转换为UTF-8和/或换行符的转换。

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

  3. 附加随机前缀和数据的SHA1哈希值。

  4. 所有这些都使用会话密钥加密,并放入数据包中。

E.28.3.1.  pgp_sym_encrypt()

                pgp_sym_encrypt(data text, psw text [, options text ]) 返回 bytea
                pgp_sym_encrypt_bytea(data bytea, psw text [, options text ]) 返回 bytea
            

使用对称PGP密钥psw加密data。参数options 可以包含选项设置,如下所述。

E.28.3.2.  pgp_sym_decrypt()

                pgp_sym_decrypt(msg bytea, psw text [, options text ]) 返回 text
                pgp_sym_decrypt_bytea(msg bytea, psw text [, options text ]) 返回 bytea
            

解密对称密钥加密的 PGP 消息。

使用 pgp_sym_decrypt 解密 bytea 数据是被禁止的。 这是为了避免输出无效的字符数据。使用 pgp_sym_decrypt_bytea 解密原始文本数据是可以的。

options 参数可以包含以下描述的选项设置。

E.28.3.3.  pgp_pub_encrypt()

                pgp_pub_encrypt(data text, key bytea [, options text ]) 返回 bytea
                pgp_pub_encrypt_bytea(data bytea, key bytea [, options text ]) 返回 bytea
            

使用公共 PGP 密钥 key 加密 data。 给予此函数一个私钥将会产生错误。

options 参数可以包含以下描述的选项设置。

E.28.3.4.  pgp_pub_decrypt()

                pgp_pub_decrypt(msg bytea, key bytea [, psw text [, options text ]]) 返回 text
                pgp_pub_decrypt_bytea(msg bytea, key bytea [, psw text [, options text ]]) 返回 bytea
            

解密一个公钥加密的消息。 key 必须是用于加密的公钥对应的私钥。 如果私钥受到密码保护,则必须在 psw 中提供密码。 如果没有密码,但您想指定选项,则需要提供空密码。

使用 pgp_pub_decrypt 解密 bytea 数据是被禁止的。 这是为了避免输出无效的字符数据。使用 pgp_pub_decrypt_bytea 解密原始文本数据是可以的。

options 参数可以包含以下描述的选项设置。

E.28.3.5.  pgp_key_id()

                pgp_key_id(bytea) 返回 text
            

pgp_key_id 提取 PGP 公钥或私钥的密钥 ID。 如果给定加密消息,则提供用于加密数据的密钥 ID。

它可以返回两个特殊的密钥 ID:

  • SYMKEY

    该消息使用对称密钥加密。

  • ANYKEY

    消息是使用公钥加密的,但密钥 ID 已被删除。 这意味着您需要尝试所有的私钥才能看到哪一个可以解密它。ltcrypto 本身不会产生这样的消息。

请注意,不同的密钥可能具有相同的 ID。这是罕见但正常的事件。客户端应用程序应尝试使用每个密钥进行解密,以查看哪个适合 — 就像处理 ANYKEY 一样。

E.28.3.6.  armor(), dearmor()

                armor(data bytea [ , keys text[], values text[] ]) 返回 text
                dearmor(data text) 返回 bytea
            

这些函数将二进制数据封装/解封为 PGP ASCII-armor 格式,这基本上是带有 CRC 和附加格式的 Base64。

如果指定了 keysvalues 数组,则为每个键/值对向装甲格式添加一个 armor header。 两个数组必须是单维数组,并且它们的长度必须相同。键和值不能包含任何非 ASCII 字符。

E.28.3.7.  pgp_armor_headers

                pgp_armor_headers(data text, key out text, value out text) 返回 setof record
            

pgp_armor_headers()data 中提取装甲头。返回值是具有两个列(键和值)的行集。如果键或值包含任何非 ASCII 字符,则将其视为 UTF-8。

E.28.3.8. PGP 函数的选项

选项的命名方式类似于 GnuPG。选项的值应在等号后给出;用逗号将选项分开。例如:

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

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

最有趣的选项可能是 compress-algounicode-mode。 其余选项应具有合理的默认值。

E.28.3.8.1. cipher-algo

使用哪种密码算法。


                    Values: bf, aes128, aes192, aes256 (仅 OpenSSL: 3des,
                    cast5)
                    Default: aes128
                    Applies to: pgp_sym_encrypt, pgp_pub_encrypt
                

E.28.3.8.2. compress-algo

使用哪种压缩算法。仅在使用 zlib 构建 LightDB 时可用。


                    Values:
                    0 - 不压缩
                    1 - ZIP 压缩
                    2 - ZLIB 压缩(= ZIP 加上元数据和块 CRC)
                    Default: 0
                    Applies to: pgp_sym_encrypt, pgp_pub_encrypt
                

E.28.3.8.3. compress-level

压缩程度。较高的级别可以压缩更小的数据,但速度较慢。0 禁用压缩。


                    Values: 0, 1-9
                    Default: 6
                    Applies to: pgp_sym_encrypt, pgp_pub_encrypt
                

E.28.3.8.4. convert-crlf

在加密时是否将 \n 转换为 \r\n,在解密时将 \r\n 转换为 \n。RFC 4880 指定文本数据应使用 \r\n 换行符进行存储。使用此选项可以获得完全符合 RFC 的行为。


                    Values: 0, 1
                    Default: 0
                    Applies to: pgp_sym_encrypt, pgp_pub_encrypt, pgp_sym_decrypt, pgp_pub_decrypt
                

E.28.3.8.5. disable-mdc

不使用 SHA-1 保护数据。使用此选项的唯一好处是实现与古老的 PGP 产品的兼容性,这些产品早于在 RFC 4880 中添加 SHA-1 保护数据包。最近的 gnupg.org 和 pgp.com 软件都可以很好地支持它。


                    Values: 0, 1
                    Default: 0
                    Applies to: pgp_sym_encrypt, pgp_pub_encrypt
                

E.28.3.8.6. sess-key

使用单独的会话密钥。公钥加密总是使用单独的会话密钥;此选项用于对称密钥加密,默认情况下直接使用 S2K 密钥。


                    Values: 0, 1
                    Default: 0
                    Applies to: pgp_sym_encrypt
                

E.28.3.8.7. s2k-mode

使用哪种 S2K 算法。


                    Values:
                    0 - 不使用盐。危险!
                    1 - 使用盐但使用固定的迭代次数。
                    3 - 可变的迭代次数。
                    Default: 3
                    Applies to: pgp_sym_encrypt
                

E.28.3.8.8. s2k-count

使用 S2K 算法的迭代次数。它必须是介于 1024 和 65011712 之间的值,包括这两个值。


                    Default: 介于 65536 和 253952 之间的随机值
                    Applies to: 仅在 s2k-mode=3 时适用于 pgp_sym_encrypt
                

E.28.3.8.9. s2k-digest-algo

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


                    Values: md5, sha1
                    Default: sha1
                    Applies to: pgp_sym_encrypt
                

E.28.3.8.10. s2k-cipher-algo

用于加密单独会话密钥的密码算法。


                    Values: bf, aes, aes128, aes192, aes256
                    Default: 使用 cipher-algo
                    Applies to: pgp_sym_encrypt
                

E.28.3.8.11. unicode-mode

是否将数据库内部编码的文本数据转换为 UTF-8 编码并转换回来。如果您的数据库已经是 UTF-8,则不会进行转换,但是消息将被标记为 UTF-8。如果没有此选项,则不会进行标记。


                    Values: 0, 1
                    Default: 0
                    Applies to: pgp_sym_encrypt, pgp_pub_encrypt
                

E.28.3.9. 使用 GnuPG 生成 PGP 密钥

生成新密钥:

gpg --gen-key

首选密钥类型为 DSA 和 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 gpgGNU 隐私手册 和其他文档 https://www.gnupg.org/

E.28.3.10. PGP 代码的限制

  • 没有签名支持。这也意味着不会检查加密子密钥是否属于主密钥。

  • 不支持以加密密钥作为主密钥。由于通常不鼓励这种做法,因此这不应该是一个问题。

  • 不支持多个子密钥。这可能看起来像是一个问题,因为这是常见的做法。另一方面,您不应该使用您的常规 GPG/PGP 密钥与 ltcrypto,而是要创建新的密钥,因为使用场景是非常不同的。

E.28.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、-192 或 -256)

mode 是以下之一:

  • cbc — 下一个块依赖于前一个块(默认)

  • ecb — 每个块都单独加密(仅用于测试)

padding 是以下之一:

  • pkcs — 数据可以是任意长度(默认)

  • none — 数据必须是密码块大小的倍数

因此,例如,这些是等价的:

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

encrypt_ivdecrypt_iv 中,iv 参数是 CBC 模式的初始值;对于 ECB,它将被忽略。 如果不完全是块大小,则它将被剪切或填充为零。 在没有此参数的函数中,默认值为全零。

E.28.5. 随机数据函数

            gen_random_bytes(count integer) 返回 bytea
        

返回 count 个具有密码学强度的随机字节。 一次最多可以提取 1024 个字节。 这是为了避免耗尽随机数生成器池。

            gen_random_uuid() 返回 uuid
        

返回版本 4(随机)UUID。 (已过时,此函数现在也包含在核心 LightDB 中。)

E.28.6. 注释

E.28.6.1. 配置

ltcrypto 根据主LightDB configure脚本的结果来进行配置。 影响它的选项有 --with-zlib--with-gmssl

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

当使用GmSSL编译时,将会有更多的算法可用。 同时,由于GmSSL拥有更优化的BIGNUM函数,公钥加密函数将会更快。

Table E.47. 使用和不使用GmSSL的功能概述

功能内置使用GmSSL
MD5
SHA1
SHA224/256/384/512
其他摘要算法是(注1)
Blowfish
AES
DES/3DES/CAST5
原始加密
PGP对称加密
PGP公钥加密

注意:

  1. GmSSL支持的任何摘要算法都会自动被使用。 这对于密码算法来说并不适用,它需要显式地被支持。

E.28.6.2. NULL值处理

正如在SQL中的标准操作,如果任何一个参数为NULL,所有函数都会返回NULL。 这可能会在不小心使用时造成安全风险。

E.28.6.3. 安全限制

所有 ltcrypto 函数在数据库服务器内运行。 这意味着所有数据和密码在 ltcrypto 和客户端应用程序之间以明文形式传输。 因此,您必须:

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

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

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

该实现不抵御侧信道攻击。 例如,ltcrypto 解密函数完成所需的时间在给定大小的密文之间会变化。

E.28.6.4. 有用的阅读材料