这篇文章专门介绍如何扩展当前正越来越流行SQLite的数据库的加密部分, SQLite是"一个无须任何配置部署的嵌入式SQL数据库." 下面是本篇文章的目录:
· 背景
· 本扩展模块的技术说明书
· 安装和使用
· 下载
· 警告和版权限制
· 鸣谢
· 相关法律说明
背景
我为什么要写这个扩展? 为了满足我自己的一些疯狂的想法. :) 好了,不开玩笑了. 不久前我想写一个程序用来存储一些私人信息 (我自己的一个项目). 我不想用那些很大的开源数据库,例如MySQL,因为它们确实是太大了,需要占用很大的空间,而且要单独的安装部署. 后来我发现了 SQLite, 它非常的小巧而且运行起来很快,并且它的API函数十分的简单,在我的C++程序当中可以很方便的使用. 就是有一个问题,大概是由于它要保持简单所以它不支持任何的验证和加密. 这令我一点安全感都没有.
因此我开始寻找SQLite的加密解决方案. 我找到了两个,但是它们都是商业软件. SQLite的作者, D. Richard Hipp先生提供了一个可以对数据库文件进行完全加密SQLite的加强版本. 也就是那个叫做SQLcrypt(tm)的商业软件, 它实现了数据存储层的透明加密. 不幸的是它们对于我们这些普通人来说都太贵了, 尤其是对于我这种非商业目的只是想用SQLite开发一些小应用自己玩的人来说更是如此. 然而我却是非常的需要对数据库进行加密- 在数据库层对整个数据库文件进行透明的加密. 所有的开发者和用户只需要在打开数据库的时候提供密码就可以了. 然后接下来的事情就全都交给数据库去做了. 这种方式将比那种在数据和字段上的加密要容易的多,也方便的多, 不然需要加密的字段就都要设计成BLOB或者string类型了.
在我搜索了SQLite的邮件列表和在Google上搜索免费的SQLite插件或扩展之后,我发现没有能够满足我的需求的, 所以我决定自己写一个. 我从SQLite的作者预留的一些用来支持数据库加密解密的API接口获得了灵感,并且我发现实际上也有人写了一个基于SQLite的加密库(SQLcrypt). 我花费了几天的时间来研究一些加密的算法,我要选出一种使用(The AES (Rijndael) block cipher) ,另外还有就是如何生成密码, 当然最重要的就是我怎样才能把我用来完成加密解密数据库的代码嵌入SQLite的核心当中执行.
扩展模块的技术说明书
这个扩展模块的结构相当的简单. 大体上来说我编写了SQLite代码中已经提供了原型的四个函数: sqlite3_key(), sqlite3_rekey(), sqlite3CodecGetKey(), 和 sqlite3CodecAttach(). 前两个函数是在sqlite3.h头文件中定义的公共API. 另外两个定义在 attach.c 的sqlite3Attach()函数中. 我阅读了它的源代码中提供的函数原形(主要是 pager.c 和 btree.c两个文件),看一下它们是如何工作的. 我发现实现他们并不困难,因为对数据库加密解密的机制已经都实现了(感谢 Richard!).
这个扩展的其他部分就是写一个用来加密和解密的程序. 为此我使用了AES加密算法,其中代码的关键部分取自Brian Gladman (his site) 还有David Ireland的高精度加密算法库BigDigits. 因为我不想在一开始的时候就跟SQLite的数据库格式过多的纠缠, 所以我用了计数器模式(CTR)把AES加密块转换成256字节的加密流. 这样加密之后的密文就可以和原来的明文有相同的长度,加密和没加密的数据库文件尺寸相同 (也就是说不需要保存而外的信息). 我还使用了Brian Gladman网站上提供的 PKCS#5签名的SHA256算法从用户提供的密码中来生成AES 算法的密钥. 如果是这样的话, 我就要写很多的代码来处理salt value 才能避免往数据库中存入额外的信息.
Pager结构体用来存放指向编码函数的指针, 用来从数据库中加密或解密数据. 我写了这些代码用来加密或解密数据库中的记录. 因为我使用了计数器(CTR)模式,加密和解密用的是相同的算法,所以简单了许多. 但是我还是需要初始化一个进程的计数器. 我把数据库切分成了一个一个的块,每个都作为一个单独的AES加密块 (例如默认的情况下是16字节). 每个块都从0开始编号. 依据传入编码函数的page size和page number参数, 我算出计数块内的计数值 (等于块的编号) 和偏移量 (具体实现请看代码),然后用它来初始化加密流.
安装和使用
警告: 目前的代码还在试验当中. 因此我很希望大家来帮我测试这些代码 (因为我没有那么多的时间和数据去进行测试), 使用的风险需要你自己去承担.
如果要使用这个扩展模块你需要下载修改过的SQLite和BigDigits库,然后自己编译它们. 我只是在Win32系统下用MinGW compiler编译并测试过. 但是我认为它在*nix系统下也能正常工作. 如果你要使用MinGW, 你需要下载最新版MinGW的和M-sys . 我是用gcc 3.4.2编译的.
BigDigits高精度算法库可以从这里下载, 我自己写了一个Makefile 文件用MinGW把它编译成了一个静态的库文件. 下载包里有编译好的BigDigits库文件. (bigdigits.h 和libbdmpa.a) 如果你想要自己编译,请遵循以下步骤:
1. 从上面的地址下载源代码.
2. 解压到一个目录里.
3. 下载并保存'Makelib.mak'文件到刚才的目录.
4. 修改头文件中最开始的一些 #define's和typedef's使之适合你的操作系统.
5. 在命令行中输入'make -f Makelib.mak'编译源代码.
6. 把编译出来的libbdmpa.a文件和bigdigits.h头文件拷贝到SQLite的顶层目录下.
对于Linux/Unix和MinGW的用户, 你只需要按照通常的步骤去做就可以
./configure
make
make install
把SQLiteSecure编译了(包括配置和安装). 编译出来的库文件和命令行可执行文件和原始的SQLite基本相同,除了前面加上了'sec'前缀,从而避免和你之前使用的SQLite命名冲突.
注意: **请** 不要问我如何用VC++或者其他编译其编译源代码. 以为我不用,所以不知道.
你可以用的命令行sqlite3sec工具来先体验一下SQLiteSecure. 打开一个普通的数据库文件, 输入
$ sqlite3sec a.db
打开一个加密的数据库文件, 输入
$ sqlite3sec -key "your passphrase" b.db
在sqlite3sec中你可以使用下列三种方法来添加一个加密的数据库:
sqlite> ATTACH 'b.db' AS b;
sqlite> ATTACH 'b.db' AS b KEY 'your passphrase';
sqlite> ATTACH 'b.db' AS b KEY blob;
第一种方法使用和主数据库相同的密码(或者是没有密码) , 第二种方法用你输入的短语来做密码. 第三种方法假设你用BLOB的十六进制值作为密码(例如 f03d69ac3981...). 不过我还没有充分的测试这个使用BLOB作为密码的版本. 请注意:如果你的主数据库是加密的,然后你想添加一个不加密的数据库,这种情况下你需要用第二种方法然后传递一个空字符串('')作为密码.
在sqlite3sec中你还可以用.rekey命令来改变数据库的密码,但是这个我目前还没有写完,如果调用的话会返回一个错误. ;)
API方法, 如果你需要加密一个数据库,你仅仅需要从用户或者其他的地方得到设置的密码, 然后在sqlite3_open() 或者 sqlite3_open16()函数之后,在调用其他的sqlite3函数之前调用sqlite3_key()函数。
函数举例:
sqlite3 *db;
...
sqlite3_open(DbFilename, &db);
sqlite3_key(db, zKey, strlen(zKey));
...
之前提到过re-key函数目前还不能工作,所以如果你调用了sqlite3_rekey()将会直接返回一个错误.
下载
下载包中包含了SQLiteSecure的源代码和编译好的BigDigits库文件. 最省事的方法就是不改任何东西,直接解压缩以后先运行./configure, 然后运行make. 如果Makefile文件有问题请告诉我一下. 但是除了GNU编译器(MinGW 或者 Linux)之外我无法提供其他系统的技术支持.
SQLiteSecure source in .tar.gz format
SQLiteSecure source in ZIP format
警告和限制
· 我没有用tcl测试过这个库.
· 我没有在实际的数据或者程序中测试这个库,也没有用一些特殊用例来测试.
· 因为这个版本还没有实现更改密码. 所以如果你想要更改密码,你需要导出加密的数据库到一个新的数据库当中(没有密码或者设置新的密码):
$ sqlite3sec -key "cur pwd" a.db .dump | sqlite3sec b.db
或者
$ sqlite3sec -key "cur pwd" a.db .dump | sqlite3sec -key "new pwd" b.db
· 我要声明我并不是一个加密学的专家, 所以在设计和实现加密层的时候难免会存在安全漏洞. 因此大家一定要明白我设计的这个扩展模块只是打算用在握自己的个人工程当中,对于安全性的要求远低于一般的商业产品的标准.
· 最后你一定要记住它还不是一个完整的版本,它还在测试当中。
具体的实现请阅读src/sec_ext目录下的代码. 你可以用我名字的大写缩写(LWL)来搜索我对于原始代码的改动. 如果你对代码的实现有任何的意见或建议欢迎你反馈给我. 当然它确实还有很大的提升空间.
鸣谢
向以下人致谢:
· D Richard Hipp for his SQLite library
· Brian Gladman for his AES and key derivation code
· David Ireland for his BigDigits multiple-precision arithmetic library
· Everyone who helped test and gave feedback, comments, and suggestions
法律声明
为了避免其他和我有同样需求的人重新发明轮子,我最终把代码开源了. 但是我也说过了,这个扩展模块目前还是试验阶段,你可以使用或者修改代码来适合你的程序, 但是请在源代码中保留我的名字,另外在你的程序或者文档中也请注明我的付出.
但是如果你非不遵守这些约定,我也不会以法律的形式去起诉你,不会做任何对你本人,你的硬件产品,客户端程序,公司,个人名誉或者其他任何和使用这个软件相关的东西做有害的事情. 你也不是非要使用它不可, 你用了这个试验性的扩展模块就要考虑到它可能有bug,我对于这些bug造成的损失不负任何的责任. 源代码已经提供给你浏览和审查了,我只能是担保我没有故意写任何破坏你的系统的代码. 所以如果你编译运行这个代码,一切风险自负.
我认为我的代码没有任何地方违反了别人的版权或协议. 如果你发现有请通知我,我将去掉那些违反的地方.
联系信息
To contact me (Low Weng Liong), send email to lwl12 DOT gm AT_SIGN gmail DOT com.