如何编译和使用 SEE
1.0 简介
本文件描述了 SQLite 的 SQLite 加密扩展 (SEE)。SEE 允许 SQLite 读取和写入加密的数据库文件。所有数据库内容,包括元数据,都经过加密,因此对于外部观察者来说,数据库看起来像是白噪声。
包含 SEE 的 SQLite 版本也能够读取和写入使用公共领域版本 SQLite 创建的普通数据库文件。但公共版本的 SQLite 将无法读取或写入加密的数据库文件。实际上,任何已知软件的任何版本都无法在不知道加密密钥的情况下访问加密的数据库文件。
SEE 实际上是一组使用各种加密算法的扩展。目前支持以下加密算法
- AES-256 处于 OFB 模式(推荐用于所有新的开发)
- AES-128 处于 OFB 模式
- AES-128 处于 CCM 模式
- RC4 具有安全增强功能(仅限旧版)
2.0 许可证
核心 SQLite 库属于公共领域。但是,读取和写入加密数据库文件所需的扩展是许可软件。只有在您拥有许可证的情况下,您才能看到此软件。
您的许可证是永久性的。您已支付一次性费用,允许您永远使用和修改该软件。您可以向您的客户发送任意数量的软件副本,只要您确保只发送编译后的二进制文件(您不能分发源代码)并且您的客户不能为其他目的额外复制该软件即可。
您可以创建多个使用此软件的产品,只要所有产品都由同一个团队开发和维护即可。在本段落中,“团队”是指一个工作单元,其中每个人都知道彼此的名字。如果您在一个大型公司中,此产品由多个团队使用,则每个团队都应该获得他们自己的单独许可证或企业许可证。
3.0 如何编译
您的应用程序将 SEE 看作一个大型的 C 代码文件,它是 SQLite 联合文件 的直接替换。SEE 源代码文件的工作方式和编译方式与公共领域的“sqlite3.c”联合文件完全相同。如果您已经使用公共领域的“sqlite3.c”文件构建您的应用程序,那么要使用 SEE 构建,您只需将公共领域的“sqlite3.c”替换为启用了 SEE 的“sqlite3.c”文件并重新编译即可。
有九个不同的启用了 SEE 的“sqlite3.c”文件可供选择
- sqlite3-see-aes256-openssl.c
- sqlite3-see-aes256-cryptoapi.c
- sqlite3-see-aes256-ofb.c
- sqlite3-see-cccrypt.c
- sqlite3-see-aes128-ofb.c
- sqlite3-see-aes128-ccm.c
- sqlite3-see.c
- sqlite3-rc4.c
- sqlite3-xor.c
将 SEE 添加到您的应用程序中的推荐方法是将这些文件之一复制到您的应用程序源代码树中,将其重命名为“sqlite3.c”并覆盖公共领域的“sqlite3.c”源文件,然后重新编译。重新编译后,您的应用程序应该继续像以前一样工作,读取和写入普通的未加密的 SQLite 数据库。重新编译并验证一切正常后,然后返回并添加一个 PRAGMA(如下所述)以激活加密到您的应用程序代码中,然后您就完成了。
3.1 SEE 发行版中的源代码文件
以下是用于实现 SQLite 加密扩展的源代码文件
- sqlite3-see-aes256-openssl.c
此文件是公共领域“sqlite3.c”文件的直接替换,通过链接外部 OpenSSL 库添加了对使用 AES-256 处于 OFB 模式的加密的支持。
- sqlite3-see-cryptoapi.c
此文件是公共领域“sqlite3.c”源文件的直接替换,使用 Windows 上的 CryptoAPI 本机接口添加了加密功能,使用 AES256 处于 OFB 模式。
- sqlite3-see-aes256-ofb.c
此文件是公共领域“sqlite3.c”文件的直接替换,添加了对使用内置的 Rijndaal 参考实现的 AES-256 处于 OFB 模式的加密的支持。
- sqlite3-see-cccrypt.c
此文件是公共领域“sqlite3.c”文件的直接替换,添加了对使用外部 CCCrypt 加密的 AES-128 和 AES-256 加密算法(处于 OFB 模式)的支持。CCCrypt 是 MacOS 和 iOS 上的默认加密库,因此此 SEE 实现推荐用于这些平台。
see-ccrypt.c 模块通常只执行 AES128 加密。但是,当 see-cccrypt 使用 -DCCCRYPT256 编译时,如果密钥恰好是 32 个字节长,它将使用 AES256。
- sqlite3-see-aes128-ofb.c
此文件是公共领域“sqlite3.c”文件的直接替换。此替换添加了对使用 Rijndaal 参考实现的 AES-128 加密算法(处于 OFB 模式)的支持。
- sqlite3-see-aes128-ccm.c
此文件是公共领域“sqlite3.c”文件的直接替换。此替换添加了对 AES-128 加密算法(处于 CCM 模式)的支持。CCM 模式包括消息认证码,除了机密性之外还提供认证。这使用 Rijndaal 参考实现进行 AES。
- sqlite3-see-rc4.c
此文件是公共领域“sqlite3.c”文件的直接替换,添加了对使用 RC4 算法加密的支持。RC4 不再被认为是安全的。您不应该使用此 SEE 实现。它仅出于历史兼容性而提供。
- sqlite3-see.c
此文件是公共领域“sqlite3.c”源文件的直接替换,添加了对使用任何 RC4、AES128-OFB 或 AES258-OFB 算法加密的支持。使用的算法基于加密密钥的前缀。如果密钥材料以“rc4:”开头,则使用 RC4 加密。如果密钥材料以“aes128:”开头,则使用 AES128-OFB。如果密钥材料以“aes256:”开头,则使用 AES256-OFB。如果这三个有效前缀都不出现在密钥上,则 AES128-OFB 是默认算法。在传递给加密算法之前,将从密钥中删除有效的前缀。
- sqlite3-see-xor.c
此文件是公共领域“sqlite3.c”源文件的直接替换,添加了伪加密,它除了将数据库与加密密钥的重复副本进行 XOR 运算之外什么也不做。此 SEE 变体不提供真正的加密。仅供演示使用,或用于在需要混淆数据库文件而不实际加密的情况下使用,例如由于法律限制。
- sqlite3.c
普通未加密的 SQLite 的副本,其中包含添加加密所需的额外挂钩。上面其他加密的 SQLite 模块都是此文件的副本,在其开头和结尾添加了额外的代码以执行加密工作。此文件仅供参考,可能对开发没有用。
- sqlite3.h
此文件包含 SQLite 的接口定义。其他与 SQLite 链接的程序将需要此文件,并且您需要此文件才能编译 CLI,但您不需要此文件来编译 SQLite 本身。
- shell.c
此文件包含“CLI”(命令行界面程序,名为“sqlite3.exe”,您可以使用它来访问和控制 SQLite 数据库文件)的源代码。此文件与公共领域版本 SQLite 附带的“shell.c”文件不同。此 shell.c 已得到增强,可以利用加密扩展。
3.2 构建和编译 SEE 代码
要将 SEE 编译成静态库,请选择一个合适的“sqlite3-see-*.c”源文件(包含您所需的算法和实现),然后像编译普通的公共领域“sqlite3.c”源文件一样编译该文件。在 unix 系统上,命令序列类似于以下内容
gcc -c sqlite3-see-aes256-ofb.c ar a sqlite3-see-aes256-ofb.a sqlite3-see-aes256-ofb.o
在 Windows 上,命令更像是这样
cl -c sqlite3-see-aes256-ofb.c lib /out:libsee.lib sqlite3-see-aes256-ofb.obj
3.3 构建共享库或 DLL
我们建议您将 SQLite 静态链接到您的应用程序。但是,如果您必须将 SQLite 作为单独的 DLL 或共享库使用,则可以在 Linux 上按如下方式编译
gcc -fPIC -shared -o libsee.so sqlite3-see-aes256-ofb.c
或在 Windows 上
cl -DSQLITE_API=__declspec(dllexport) sqlite3-see-aes256-ofb.c /link /dll /out:libsee.dll
3.4 构建命令行 Shell 程序
要编译 CLI,只需将 shell.c 源文件传递给您的 C 编译器,以及上面准备的静态库或原始源代码文件。Linux 上的典型命令是
gcc -o sqlite3 shell.c sqlite3-see-aes256-ofb.c -lpthread -ldl
在 Mac 上
gcc -o sqlite3 shell.c sqlite3-see-aes256-ofb.c -ldl
在使用 MSVC 的 Windows 上
cl /Fesqlite3.exe shell.c sqlite3-see-aes256-ofb.c
在构建 CLI 时,为了提高性能,请考虑添加 -DSQLITE_THREADSAFE=0 选项。CLI 是单线程的,如果 SQLite 不必使用其互斥锁,则运行速度更快。
SEE 也可以为 Windows Phone 8、UWP 10 和 Android 构建。
4.0 命令行用法
CLI 与 公共领域 SQLite 使用的 CLI 相同,但进行了增强以支持加密。有新的命令行选项(“-key”,“-hexkey”和“-textkey”)用于指定加密密钥。示例
sqlite3 -key secret database.db sqlite3 -hexkey 736563726574 database.db sqlite3 -textkey secret2 database.db
如果省略密钥或密钥为空字符串,则不执行加密。
有三种不同的密钥格式。第一种格式(-key)获取密钥字符串并重复它,直到它超过底层算法密钥中的字节数(AES128 为 16 个字节,AES256 为 32 个字节,或 RC4 为 256 个字节)。然后将其截断为算法密钥大小。这种方法限制了密钥空间,因为它不允许密钥中出现 0x00 字节。第二种格式(-hexkey)以十六进制接受密钥,因此可以表示任何密钥。如果提供的密钥太长,则将其截断。如果提供的密钥太短,则将其重复以填充到算法密钥长度。第三种格式(-textkey)对输入密钥材料计算强散列,并使用该散列作为算法密钥。对于新应用程序,建议使用 -textkey 格式。
4.1 更改加密密钥
启用了 SEE 的 CLI 还包括新的 点命令“.rekey”,“.hex-rekey”和“.text-rekey”用于更改加密密钥
.rekey OLD NEW NEW .hex-rekey OLD NEW NEW .text-rekey OLD NEW NEW
第一个参数始终是旧密码,其格式与命令行工具启动时提供给“-key”,“-hexkey”或“-textkey”选项的格式完全相同。如果数据库以前未加密,请使用空字符串“”作为密钥。第二个和第三个参数是新的加密密钥。您必须输入两次新密钥以检查是否有错别字 - 除非新密钥的两个实例都相同,否则不会执行重新加 密。要加密以前未加密的数据库,请执行以下操作
.rekey "" new-key new-key VACUUM
VACUUM 步骤不是启用加密所必需的,但强烈建议使用。VACUUM 命令确保数据库文件的每一页都有一个安全的 nonce。仅当首次加密现有的非空数据库文件时,才需要 VACUUM。
要解密数据库,请执行以下操作
.rekey old-key "" ""
.rekey 命令仅适用于文本密钥。要重新加 密包含二进制密钥的数据库,请改用“.hex-rekey”命令。.hex-rekey 命令的工作方式与 .rekey 相同,只是新密钥以十六进制而不是文本输入。“.text-rekey”命令计算 NEW 参数的散列,并将该散列用作加密密钥。
5.0 C 接口
如果您将 SQLite 加密扩展部署为 DLL 或共享库,则必须首先通过调用以下函数激活库:
sqlite3_activate_see("7bb07b8d471d642e");
参数是您的产品激活密钥。激活密钥以纯文本形式在源代码中提供,因此您可以清楚地看到它是什么。激活密钥的目的是防止您的某个客户提取 SQLite 库并将其与您的应用程序分开使用。在不知道激活密钥的情况下(只有您应该知道),您的用户将无法访问加密功能。
如果您无法调用 C 接口 sqlite3_activate_see()(可能是因为您通过包装层访问 SQLite),则还可以选择使用 PRAGMA 激活加密功能:
PRAGMA activate_extensions='see-7bb07b8d471d642e';
使用 sqlite3_open() API 打开加密数据库或任何您想要重新加密的数据库。打开后立即使用 sqlite3_key_v2() 指定密钥:
int sqlite3_key_v2( sqlite3 *db, /* The connection from sqlite3_open() */ const char *zDbName, /* Which ATTACHed database to key */ const void *pKey, /* The key */ int nKey /* Number of bytes in the key */ );
如果 pKey 参数为 NULL 或 nKey 为 0,则假定数据库未加密。nKey 参数可以任意大,但仅使用前 256 个字节(RC4)或 16 个字节(AES128)或 32 个字节(AES256)。在 SEE 3.15.0 及更高版本中,如果 nKey 为负数,则假定 pKey 是一个以零结尾的密码短语字符串。在这种情况下,密码短语会被哈希化,哈希值用作 AES 算法的密钥。密码短语本身用作 RC4 的密钥。
注意:在 3.15.0 版本中添加了在 nKey<0 时使用密码短语哈希的功能。如果您在 3.15.0 之前的任何 SEE 版本中使用 nKey<0,则加密将被静默禁用,就像您设置了 nKey=0 一样。
see-ccrypt.c 模块默认使用 AES128 加密。但是,如果 see-ccrypt.c 使用 -DCCCRYPT256 编译,并且如果 sqlite3_key_v2() 接口被调用且 nKey==32,则改为使用 AES256 加密。
如果您指定了错误的密钥,您不会立即收到错误消息。但是,当您第一次尝试访问数据库时,您将收到 SQLITE_NOTADB 错误,并显示消息“文件已加密或不是数据库”。
zDbName 参数指定哪个附加的数据库应该获得密钥。通常是“main”。您可以传入一个 NULL 指针作为“main”的别名。除非您有充分的理由这样做,否则最好为 zDbName 参数传入一个 NULL 指针。
您可以使用 sqlite3_rekey() 例程更改数据库的密钥:
int sqlite3_rekey_v2( sqlite *db, /* Database to be rekeyed */ const char *zDbName, /* Which ATTACHed database to rekey */ const void *pKey, int nKey /* The new key */ );
NULL 密钥将解密数据库。
重新加密需要读取数据库文件的每一页,解密,使用新密钥重新加密,然后再次写入。因此,在较大的数据库上重新加密可能需要很长时间。
大多数 SEE 变体允许您加密使用公共领域的 SQLite 版本创建的现有数据库。在使用 see-aes128-ccm.c 中的加密扩展的认证版本时,这是不可能的。如果您确实加密了使用公共领域的 SQLite 版本创建的数据库,则不会使用 nonce,并且文件容易受到选择明文攻击。如果您在第一次创建数据库时,在 sqlite3_open() 之后立即调用 sqlite3_key_v2(),则将在数据库中为 nonce 预留空间,并且加密将更加强大。如果您不想立即加密,请无论如何调用 sqlite3_key_v2(),使用 NULL 密钥,即使最初没有进行任何加密,也会在数据库中为 nonce 预留空间。
公共领域的 SQLite 库可以使用 NULL 密钥读取和写入加密数据库。只有在密钥非 NULL 时,才需要加密扩展。
6.0 使用“key”PRAGMA
作为调用 sqlite3_key_v2() 设置数据库解密密钥的替代方法,您可以调用一个 pragma:
PRAGMA key='your-secret-key';
您必须在尝试与数据库进行任何其他交互之前调用此 pragma。key pragma 仅适用于字符串密钥。如果您使用二进制密钥,请改用 hexkey pragma:
PRAGMA hexkey='796f75722d7365637265742d6b6579';
对于 --textkey 选项的等效项,其中文本密码短语被哈希以计算实际的加密密钥,请使用:
PRAGMA textkey='your-secret-key';
使用 rekey、hexrekey 或 textrekey pragma 更改密钥。例如,要将密钥更改为“demo2”,请使用以下其中一个:
PRAGMA rekey='demo2'; PRAGMA hexrekey='64656d6f32'; PRAGMA textrekey='long-passphrase';
通过使用这些 pragma,无需直接调用 sqlite3_key_v2() 或 sqlite3_rekey_v2() 接口。这意味着 SEE 可以与不知道这些接口的语言包装器一起使用。
“key”、“hexkey”和“textkey”PRAGMA 语句分别期望与命令行 shell 的“-key”、“-hexkey”和“-textkey”参数相同的密钥字符串。
如果 key PRAGMA 成功将加密密钥加载到 SEE 中,则将返回字符串“ok”。如果您在不支持加密的系统上调用其中一个 pragma,或者如果密钥加载操作因任何原因失败,则不会返回任何内容。请注意,当加载任何密钥时都会返回“ok”字符串,不一定是正确的密钥。确定密钥是否正确的唯一方法是尝试从数据库文件读取。错误的密钥将导致读取错误。
7.0 使用 ATTACH 命令
附加数据库的密钥是在 ATTACH 语句末尾使用 KEY 子句指定的。像这样:
ATTACH DATABASE 'file2.db' AS two KEY 'xyzzy';
如果省略了 KEY 子句,则使用主数据库当前正在使用的相同密钥。如果附加的数据库未加密,请指定空字符串作为密钥。KEY 关键字的参数可以是 BLOB 常量。例如:
ATTACH DATABASE 'file2.db' AS two KEY X'78797a7a79';
在 ATTACH 语句中使用文本作为 KEY 期望与命令行 shell 的“-key”选项提供的密钥相同。KEY 的 BLOB 值表示使用与命令行 shell 的“-hexkey”选项提供的密钥相同的密钥。没有机制可以在 ATTACH 语句中指定要哈希的密码短语。如果您使用的是哈希密钥,则必须自己计算哈希值并将其作为 BLOB 提供。
8.0 密钥材料
加密扩展实际使用的密钥材料数量取决于您使用的 SEE 变体。对于 see-rc4.c,使用密钥的前 256 个字节。对于 see-aes128-ofb 和 see-aes128-ccm 变体,使用密钥的前 16 个字节。对于 see-aes256-ofb,使用密钥的前 32 个字节。
如果您指定的密钥短于最大密钥长度,则密钥材料将重复多次,直到密钥完成。如果您指定的密钥大于最大密钥长度,则多余的密钥材料将被静默忽略。
对于“-textkey”选项,最多 256 个字节的密码短语将使用 RC4 进行哈希,哈希值成为加密密钥。请注意,在此上下文中,RC4 算法用作哈希函数,而不是加密函数,因此 RC4 是密码学上弱算法这一事实无关紧要。
8.1 使用密钥前缀选择加密算法
对于“sqlite3-see.c”SEE 变体,密钥可以以一个前缀开头,以指定要使用的算法。前缀必须正好是“rc4:”、“aes128:”或“aes256:”之一。前缀不用作发送到加密算法中的密钥的一部分。因此,真正的密钥应该从前缀后的第一个字节开始。请注意以下重要细节:
前缀区分大小写。“aes256:”是有效的前缀,但“AES256:”不是。
如果密钥前缀被省略或拼写错误,则加密算法默认为“aes128”,并且拼写错误的前缀成为密钥的一部分。
可以使用 sqlite3_rekey_v2() 接口或 .rekey 命令行更改加密算法。例如,要将旧的 RC4 加密数据库转换为使用 AES-256,请输入:
.rekey rc4:mykey aes256:mykey aes256:mykey
算法前缀字符串仅适用于 SEE 的“sqlite-see.c”变体。对于任何 SEE 实现,密钥上的任何前缀都被解释为密钥的一部分。
sqlite3_key() 和 sqlite3_key_v2() 上的 nKey 参数必须包含前缀的大小以及密钥的大小。
使用 PRAGMA hexkey 或 PRAGMA hexrekey 时,密钥前缀必须像密钥的其余部分一样进行十六进制编码。
PRAGMA hexkey='aes128:6d796b6579'; -- Wrong!! PRAGMA hexkey='6165733132383a6d796b6579'; -- correct
9.0 Nonce 的重要性
如果每个数据库页面上都有一个随机的 nonce 值,则加密会更加安全。如果没有 nonce,则可以使用选择明文攻击破解加密。纯粹主义者会(正确地)争论说,如果没有 nonce,加密是脆弱的。
每个数据库页面上的 nonce 字节数由数据库文件的第 20 个字节确定。在公共领域版本的 SQLite 创建的数据库中,此值默认为零。您可以通过使用启用了 SEE 的 SQLite 版本运行 VACUUM 命令将其字节更改为正值。
您可以通过在普通的 sqlite3.exe 命令行 shell 程序中使用“.dbinfo”命令来检查数据库的 nonce 大小。“.dbinfo”命令的输出将如下所示:
database page size: 4096 write format: 1 read format: 1 reserved bytes: 12 ← Nonce size file change counter: 3504448735 database page count: 14190 freelist page count: 0 schema cookie: 107 schema format: 4 default cache size: 0 autovacuum top root: 0 incremental vacuum: 0 text encoding: 1 (utf8) user version: 0 application id: 0 software version: 3008008 number of tables: 53 number of indexes: 53 number of triggers: 0 number of views: 0 schema size: 14257
数据库的第 16 到 23 个字节未加密。因此,即使在加密的数据库文件上,您也可以始终通过查看第 20 个字节来查看正在使用多少 nonce。建议任何使用加密的产品都检查此字节,以确保它被设置为 4 或 12 或 32,而不是 0。
可以使用以下方法之一增加 nonce 大小,但不能减小:
从命令行 shell:
.filectrl reserve_bytes 32 VACUUM;
使用 C API:
int nNonce = 32; sqlite3_file_control(db, "main", SQLITE_FCNTL_RESERVE_BYTES, &nNonce); sqlite3_exec(db, "VACUUM;", 0, 0, 0);
10.0 安全检查清单
在应用程序中使用 SEE 时,建议您至少通过执行以下测试来仔细检查所有内容是否已正确实现,以及您是否获得了强大的加密:
- 使用启用了 SEE 的 CLI 运行“sqlite3 $DATABASE .dbinfo”命令(添加适当的 -key、-hexkey 或 -textkey 参数),并验证您的加密数据库文件是否包含 nonce。nonce 应该至少为 12 个字节。
- 使用启用了 SEE 的 CLI 读取加密数据库,但将提供的密钥的最后一个字符更改为单个字符值。验证像这样对密钥末尾进行微小的更改是否会使数据库无法读取。错误消息应为“文件不是数据库”。使用密钥的多个变体重复此测试。确认只有在密钥完全正确的情况下才能访问数据库。
- 尝试压缩加密数据库文件,并验证文件是否无法压缩。换句话说,对加密数据库运行像“zip”或“gzip”这样的程序,并验证压缩是否不会使文件的大小减少超过几个字节。
限制
- TEMP 表未加密。
- 内存中(“:memory:”)数据库未加密。
- 数据库文件的第 16 到 23 个字节包含未加密的标题信息。
11.0 SEE 的工作原理
每个页面分别加密。加密密钥是页码、随机 nonce(如果有)和数据库密钥的组合。数据在主数据库和回滚日志或 WAL 文件中都被加密,但在内存中时未加密。这意味着,如果攻击者能够查看程序使用的内存,她将能够看到未加密的数据。
回滚会更改 nonce 值。
see-aes128-ccm.c 变体使用 AES 的 CCM 模式,每个页面使用一个 16 字节的随机选择的 nonce 和一个 16 字节的消息认证码 (MAC)。因此,使用 crypto3ccm.c 时,每个数据库页面的 32 字节会被加密和认证开销占用。结果,使用 crypto3ccm.c 创建的数据库文件可能会略大一些。此外,由于每次修改页面时都会计算 MAC,并在读取页面时进行验证,因此 crypto3ccm.c 通常会稍微慢一些。这就是认证的代价。