简洁的想法

 找回密码
 注册
搜索
查看: 2105|回复: 2

SQLite 数据库加密的一种解决方案

[复制链接]
发表于 2009-12-22 16:23:05 | 显示全部楼层 |阅读模式
SQLite是一个非常小巧的跨平台嵌入式数据库,它的数据库以文件的形式存放在本地磁盘上,但是在其开源的免费版中它却缺少了一个数据库中几乎是必备的功能,那就是对于数据库的加密。SQLite的数据库文件可以被任何的文本编辑工具打开,从而获取到其中的数据,这一点令很多开发者感到不安。


但是其实SQLite是支持数据库加密的,前些天看到了网友arris的帖子,具体如下:

sqlite的源代码中原本就考虑了加密的实现,并且保留了接口sqlite3_key和sqlite3_rekey,只是这两个函数在free版本中没有实现,但幸运的是,sqlite的源代码的代码是开放并允许修改,我们可以很方便的增加加密的实现。在http://www.sqlite.com.cn/POParticle/3/216.Html链接的的代码包中就包含有可加密sqlite的源代码的实现,我根据这个包编译了一个可加密的sqlite。这个包加密实现调用了windows API 的加密函数,所以只能在windows中使用。


这个可加密的版本是在一个ADO.NET 2.0 SQLite Data Provider的基础上改过来的(http://www.sqlite.com.cn/POParticle/3/216.Html),据原作者声称效率损失在千分之一以下。原始工程是基于VS2005的,但是考虑到其普及性还不是很广,所以重新建立了一个居于VC2003的工程。


其实SQLite的两个加密函数使用起来非常的简单,下面分情况说明:

①     给一个未加密的数据库添加密码:如果想要添加密码,则可以在打开数据库文件之后,关闭数据库文件之前的任何时刻调用sqlite3_key函数即可,该函数有三个参数,其中第一个参数为数据库对象,第二个参数是要设定的密码,第三个是密码的长度。例如:sqlite3_key(db,"1q2w3e4r",8);        //给数据库设定密码1q2w3e4r

②     读取一个加密数据库中的数据:完成这个任务依然十分简单,你只需要在打开数据库之后,再次调用一下sqlite3_key函数即可,例如,但数据库密码是123456时,你只需要在代码中加入sqlite3_key(db,"123456",6);

①     更改数据库密码:首先你需要使用当前的密码正确的打开数据库,之后你可以调用sqlite3_rekey(db,"112233",6) 来更改数据库密码。

②     删除密码:也就是把数据库恢复到明文状态。这时你仍然只需要调用sqlite3_rekey函数,并且把该函数的第二个参数置为NULL或者"",或者把第三个参数设为0。


加密后数据库文件显示为乱码:


为此我建立了一个简单的示例:
  1. sqlite3 *db;
  2. sqlite3_stmt *stat;
  3. char *zErrMsg = 0;
  4. char temp[256], FileRoot[256];
  5. char buffer2[1024]="0";

  6. sprintf(temp, _T("%s"), _T("utf.db"));
  7. CCodingConv::GB2312_2_UTF8(FileRoot, 256, temp, 0);
  8. sqlite3_open(FileRoot, &db);

  9. if(db == NULL)
  10. {
  11.         return -1;
  12. }

  13. sqlite3_key(db,"1q2w3e4r",8);

  14. sqlite3_exec(db, "CREATE TABLE list (fliename varchar(128) UNIQUE, fzip text);", 0, 0, &zErrMsg);
  15. sqlite3_prepare(db, "insert into list values ('中文GB2312编码',?);", -1, &stat, 0);

  16. strcpy(temp, "测试数据UTF-8的支持情况");
  17. int len = (int)strlen(temp);

  18. sqlite3_bind_text(stat, 1, temp, len, NULL);
  19. sqlite3_step(stat);

  20. sqlite3_prepare(db, "select * from list;", -1, &stat, 0);
  21. sqlite3_step(stat);

  22. const unsigned char * test = sqlite3_column_text(stat, 1);
  23. int size = sqlite3_column_bytes(stat, 1);

  24. printf("%s", test);

  25. sqlite3_finalize(stat);
  26. //sqlite3_rekey(db,"",0);
  27. sqlite3_close(db);
复制代码
具体的源代码如下:
SQLite3.3.7 加密版源代码(VC2003)
SQLite3.3.7 加密版源代码(VC2005)
例子1
例子2

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
 楼主| 发表于 2009-12-22 16:33:33 | 显示全部楼层
sqlite是一个非常小巧的跨平台嵌入式数据库,它本身不提供加密功能,不过设计者明显也考虑了加密的方案,我们在源码中可以找到两个预留的加密接口:sqlite3_key和sqlite3_rekey,可以通过实现这两个接口来达到加密的目的。

        如何加密,已经有很多文章描述,可以参考:《SQLite 数据库加密的一种解决方案》

        我这里要说的是这种方法的缺陷和改进的方法(测试sqlite版本v3.5.6);

加密函数的简要说明:

        sqlite3_key的输入参数有三个:一个是sqlite3的指针,二、三分别是密码的指针和数据长度;

        从第一个参数,我们可以看出,必须先用sqlite3_open获取一个sqlite3指针,才能调sqlite3_key设置密钥;

问题描述:

       sqlite3_open的目的是打开database,openDatabase的过程中会去读取和解析db文件的头信息;

       (我们用二进制查看工具(比如UltraEdit)打开数据库文件时,会在最前面发现“SQLite format 3...”一些可读的信息,前面这100个字节就是sqlite数据文件头信息。)

      openDatase的一个主要功能就读取这些头信息,其中有比较重要的database的page size, 这个参数用第16、17字节表示;在未加密的情况下,我们观察这个值一般是0400,表示数据库的page size是1024;

      问题出现了:我们已经知道,加密设置是在openDatase之后,如果数据已经加密,opendatase必然拿不到正确的page size等信息,那么为什么加密的方法使用时没有出错呢?

      因为此时数据库第16、17字节一般会是乱码,当pagesize读出不是512的整数倍,或者大于某个值时,opendatase会默认把page size当1024处理,而这种做法一般也都没有问题(前提是很多数据库的pagesize确实是1024);但这种做法并不安全,存在两个风险:

             风险一:如果数据库的pagesize不是1024,而是2048,加密后数据库还能打开吗?

             风险二:如果数据库加密后的16、17字节正好是512的整数倍,那就会被误认为是pagesize,导致数据库无法打开(这种数据已经试验出,并证明一定会出错);

解决方案:

       这种问题,实际上是因为sqlite代码中没有充分考虑这种pagesize在加密后读取的问题,解决方法有两个:

             方案一:(彻底解决方案)修改sqlite源码,使opendatase读取的pagesize无效,在设置好数据库密钥以后,第一次读取数据时重新计算pagesize;

             方案二:(针对加密的修补方案)修改sqlite3_key的加密实现,在设置密钥时,解密读取数据库的头信息,读取解密后的pagesize,再把这个正确的pagesize设置回去;

       经过比较分析,我选用了第二种方案,sqlite源码如果修改,就面临sqlite升级的维护,而只修改加密实现部分也能解决问题,这也不影响原来sqlite的实现,不过这个问题得向sqlite的开发小组提一下;



其它问题:

为什么pagesize读取不对,就会导致数据库打开失败?

       pagesize决定数据会从文件中读取多少字节进行page解析,第一个page尤其重要,它存放了数据库里面的各种table的信息,如果 pagesize不对,sqlite将无法解析到底有那些表,直接导致数据打开失败;即使侥幸打开成功,以后取数据也会出问题;

最新的sqlite版本有没有解决这个问题?

       目前sqlite更新频率很高,最新的是3.6.11,还没验证是否已经解决,不过发现lockBtree的代码中发现新增了一些重新设置pagesize的语句,但重新设置后没有重新读取page,所以还不明确这些语句的作用。
 楼主| 发表于 2009-12-22 16:36:46 | 显示全部楼层
更多请搜索:http://www.sqlite.com.cn
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|小黑屋|Archiver|简洁的想法

GMT+1, 2022-1-28 03:04

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表