ltcrypto
模块为 LightDB 提供了加密功能。
该模块被认为是“trusted”(可信任的),也就是说,它可以被非超级用户安装,只要他们在当前数据库上具有“CREATE”特权。
digest()
digest(data text, type text) returns bytea digest(data bytea, type text) returns bytea
计算给定的data
的二进制哈希值。
type
是要使用的算法。
标准算法有md5
、sha1
、sha224
、
sha256
、sha384
和sha512
。
如果使用 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;
函数crypt()
和gen_salt()
专门用于哈希密码。
crypt()
用于哈希,gen_salt()
用于准备算法参数。
crypt()
中的算法与通常的MD5或SHA1哈希算法有以下区别:
它们不够快。由于数据量很小,这是使暴力破解密码变得困难的唯一方法。
它们使用一个随机值,称为salt,以便具有相同密码的用户将具有不同的加密密码。 这也是对抗反向算法的额外防御措施。
它们在结果中包含算法类型,因此可以共存使用使用不同算法哈希的密码。
其中一些是适应性的,这意味着当计算机变得更快时,可以调整算法使其变慢,而不会与现有密码不兼容。
Table E.44列出了crypt()
函数支持的算法。
Table E.44.
crypt()
支持的算法
算法 | 最大密码长度 | 适应性? | 盐位数 | 输出长度 | 描述 |
---|---|---|---|---|---|
bf
| 72 | yes | 128 | 60 | 基于Blowfish的,2a变体 |
md5
| 无限制 | 否 | 48 | 34 | 基于MD5的crypt |
xdes
| 8 | yes | 24 | 20 | 扩展DES |
des
| 8 | 否 | 12 | 13 | 原始UNIX crypt |
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
。
gen_salt()
gen_salt(type text [, iter_count integer ]) 返回 text
为crypt()
生成一个新的随机盐字符串。
盐字符串还告诉crypt()
使用哪个算法。
type
参数指定哈希算法。
接受的类型包括:des
,xdes
,md5
和
bf
。
iter_count
参数允许用户指定迭代次数(对于具有迭代次数的算法)。
迭代次数越高,哈希密码所需的时间就越长,因此破解密码所需的时间也越长。
但是,如果迭代次数过高,计算哈希值所需的时间可能会长达数年,这在实际中是不可行的。
如果省略iter_count
参数,则使用默认的迭代次数。
iter_count
参数的允许值取决于算法,并在
Table E.45中显示。
Table E.45.
crypt()
的迭代次数
算法 | 默认值 | 最小值 | 最大值 |
---|---|---|---|
xdes
| 725 | 1 | 16777215 |
bf
| 6 | 4 | 31 |
对于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
| 1792 | 4年 | 3927年 | 100k |
crypt-bf/7
| 3648 | 2年 | 1929年 | 50k |
crypt-bf/6
| 7168 | 1年 | 982年 | 25k |
crypt-bf/5
| 13504 | 188 天 | 521 年 | 12.5k |
crypt-md5
| 171584 | 15 天 | 41 年 | 1k |
crypt-des
| 23221568 | 157.5 分钟 | 108 天 | 7 |
sha1
| 37774272 | 90 分钟 | 68 天 | 4 |
md5 (哈希)
| 150085504 | 22.5 分钟 | 17 天 | 1 |
注意事项:
使用的计算机是一台英特尔移动版Core i3。
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
的循环速度为每秒 13506
次。(结果的微小差异符合 ltcrypto
中的 crypt-bf
实现与 John the Ripper 中使用的实现相同的事实。)
请注意,“尝试所有组合”并不是一项现实的任务。通常密码破解是通过使用包含常规单词和各种变形的字典来完成的。因此,即使是类似单词的密码也可能比上述数字快得多被破解,而一个6个字符的不像单词的密码可能会逃脱破解。也可能不会。
这里的函数实现了OpenPGP(RFC 4880)标准的加密部分。支持对称密钥和公钥加密。
加密的PGP消息由两个部分或数据包(packets)组成:
包含会话密钥的数据包 — 可以是对称密钥或公钥加密。
包含使用会话密钥加密的数据的数据包。
使用对称密钥(即密码)进行加密时:
给定的密码使用String2Key (S2K)算法进行哈希。这与crypt()
算法非常相似,目的是缓慢处理并带有随机盐,但它会生成一个完整长度的二进制密钥。
如果请求一个单独的会话密钥,将生成一个新的随机密钥。否则,S2K密钥将直接用作会话密钥。
如果要直接使用S2K密钥,则会将仅S2K设置放入会话密钥数据包中。否则,会话密钥将使用S2K密钥进行加密,并放入会话密钥数据包中。
使用公钥加密时:
生成一个新的随机会话密钥。
使用公钥对其进行加密,并将其放入会话密钥数据包中。
在任何一种情况下,要加密的数据都按以下方式处理:
可选的数据处理:压缩、转换为UTF-8和/或换行符的转换。
数据前缀有一段随机字节块。这相当于使用随机IV。
附加随机前缀和数据的SHA1哈希值。
所有这些都使用会话密钥加密,并放入数据包中。
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
可以包含选项设置,如下所述。
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
参数可以包含以下描述的选项设置。
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
参数可以包含以下描述的选项设置。
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
参数可以包含以下描述的选项设置。
pgp_key_id()
pgp_key_id(bytea) 返回 text
pgp_key_id
提取 PGP 公钥或私钥的密钥 ID。
如果给定加密消息,则提供用于加密数据的密钥 ID。
它可以返回两个特殊的密钥 ID:
SYMKEY
该消息使用对称密钥加密。
ANYKEY
消息是使用公钥加密的,但密钥 ID 已被删除。
这意味着您需要尝试所有的私钥才能看到哪一个可以解密它。ltcrypto
本身不会产生这样的消息。
请注意,不同的密钥可能具有相同的 ID。这是罕见但正常的事件。客户端应用程序应尝试使用每个密钥进行解密,以查看哪个适合 — 就像处理 ANYKEY
一样。
armor()
, dearmor()
armor(data bytea [ , keys text[], values text[] ]) 返回 text dearmor(data text) 返回 bytea
这些函数将二进制数据封装/解封为 PGP ASCII-armor 格式,这基本上是带有 CRC 和附加格式的 Base64。
如果指定了 keys
和 values
数组,则为每个键/值对向装甲格式添加一个
armor header。
两个数组必须是单维数组,并且它们的长度必须相同。键和值不能包含任何非 ASCII 字符。
pgp_armor_headers
pgp_armor_headers(data text, key out text, value out text) 返回 setof record
pgp_armor_headers()
从 data
中提取装甲头。返回值是具有两个列(键和值)的行集。如果键或值包含任何非 ASCII 字符,则将其视为 UTF-8。
选项的命名方式类似于 GnuPG。选项的值应在等号后给出;用逗号将选项分开。例如:
pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
除了 convert-crlf
之外的所有选项都仅适用于加密函数。解密函数从 PGP 数据获取参数。
最有趣的选项可能是 compress-algo
和 unicode-mode
。
其余选项应具有合理的默认值。
使用哪种密码算法。
Values: bf, aes128, aes192, aes256 (仅 OpenSSL: 3des
,
cast5
)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
使用哪种压缩算法。仅在使用 zlib 构建 LightDB 时可用。
Values:
0 - 不压缩
1 - ZIP 压缩
2 - ZLIB 压缩(= ZIP 加上元数据和块 CRC)
Default: 0
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
压缩程度。较高的级别可以压缩更小的数据,但速度较慢。0 禁用压缩。
Values: 0, 1-9
Default: 6
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
在加密时是否将 \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
不使用 SHA-1 保护数据。使用此选项的唯一好处是实现与古老的 PGP 产品的兼容性,这些产品早于在 RFC 4880 中添加 SHA-1 保护数据包。最近的 gnupg.org 和 pgp.com 软件都可以很好地支持它。
Values: 0, 1
Default: 0
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
使用单独的会话密钥。公钥加密总是使用单独的会话密钥;此选项用于对称密钥加密,默认情况下直接使用 S2K 密钥。
Values: 0, 1
Default: 0
Applies to: pgp_sym_encrypt
使用哪种 S2K 算法。
Values:
0 - 不使用盐。危险!
1 - 使用盐但使用固定的迭代次数。
3 - 可变的迭代次数。
Default: 3
Applies to: pgp_sym_encrypt
使用 S2K 算法的迭代次数。它必须是介于 1024 和 65011712 之间的值,包括这两个值。
Default: 介于 65536 和 253952 之间的随机值
Applies to: 仅在 s2k-mode=3 时适用于 pgp_sym_encrypt
在 S2K 计算中使用哪种摘要算法。
Values: md5, sha1
Default: sha1
Applies to: pgp_sym_encrypt
用于加密单独会话密钥的密码算法。
Values: bf, aes, aes128, aes192, aes256
Default: 使用 cipher-algo
Applies to: pgp_sym_encrypt
是否将数据库内部编码的文本数据转换为 UTF-8 编码并转换回来。如果您的数据库已经是 UTF-8,则不会进行转换,但是消息将被标记为 UTF-8。如果没有此选项,则不会进行标记。
Values: 0, 1
Default: 0
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
生成新密钥:
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 gpg
,GNU 隐私手册
和其他文档 https://www.gnupg.org/。
没有签名支持。这也意味着不会检查加密子密钥是否属于主密钥。
不支持以加密密钥作为主密钥。由于通常不鼓励这种做法,因此这不应该是一个问题。
不支持多个子密钥。这可能看起来像是一个问题,因为这是常见的做法。另一方面,您不应该使用您的常规 GPG/PGP 密钥与
ltcrypto
,而是要创建新的密钥,因为使用场景是非常不同的。
这些函数仅在数据上运行密码算法;它们没有 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
— 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_iv
和 decrypt_iv
中,iv
参数是 CBC 模式的初始值;对于 ECB,它将被忽略。
如果不完全是块大小,则它将被剪切或填充为零。
在没有此参数的函数中,默认值为全零。
gen_random_bytes(count integer) 返回 bytea
返回 count
个具有密码学强度的随机字节。
一次最多可以提取 1024 个字节。 这是为了避免耗尽随机数生成器池。
gen_random_uuid() 返回 uuid
返回版本 4(随机)UUID。 (已过时,此函数现在也包含在核心 LightDB 中。)
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公钥加密 | 是 | 是 |
注意:
GmSSL支持的任何摘要算法都会自动被使用。 这对于密码算法来说并不适用,它需要显式地被支持。
正如在SQL中的标准操作,如果任何一个参数为NULL,所有函数都会返回NULL。 这可能会在不小心使用时造成安全风险。
所有 ltcrypto
函数在数据库服务器内运行。
这意味着所有数据和密码在 ltcrypto
和客户端应用程序之间以明文形式传输。
因此,您必须:
本地连接或使用SSL连接。
信任系统和数据库管理员。
如果您不能,那么最好在客户端应用程序内部进行加密。
该实现不抵御侧信道攻击。
例如,ltcrypto
解密函数完成所需的时间在给定大小的密文之间会变化。
http://gmssl.org/docs/docindex.html
GmSSL reference manual