SQLITE3 加密总结.docx
《SQLITE3 加密总结.docx》由会员分享,可在线阅读,更多相关《SQLITE3 加密总结.docx(25页珍藏版)》请在冰豆网上搜索。
SQLITE3加密总结
SQLITE3加密总结(sqlite3.6.12版本)
董淳光42530(老工号)
***************
2009年4月9日星期四
I.序
时隔上次写《Sqlite3使用总结》已过去2年。
这2年时间我做了好些对自己人生影响很大的事情。
不扯太远了。
2年来一直想把sqlite3的加密搞清楚一些,但一直没时间去做。
这两天终于有空坐下来研究sqlite3的加密方法。
有点收获。
记录下来免得忘记。
我写本文章时,sqlite3最新版本是3.6.12。
我就以这个版本的源代码为例进行分析。
并且,我喜欢它那整合代码,整合成一个.c和一个.h文件。
虽然在vc2003里编辑慢如蜗牛,但是一旦编辑好,以后使用起来不至于每个工程都拖上一堆文件。
工程简洁方便。
所以下面的叙述全部都是以sqlite3v3.6.12整合的源代码为基础展开。
我认为未来sqlite3v3.XX的整合版本大体上都可以用下面介绍的代码进行加解密。
Sqlite3在版本变化中,有一些宏、函数被改名,读者很容易查出来并自己修正。
也有一些函数会被丢弃。
读者应该也可以自行分析出来。
我下面的代码尽可能保持与sqlite版本的兼容性。
不使用那些容易被丢弃的结构或函数。
这样以后就不会常常有人发邮件咨询我能否制作一个最新版本sqlite加密了。
II.问题初分析
首先是要理清sqlite3加解密思路。
这点在2年前就没有做到位。
我本人愚笨,不擅长凭空分析太抽象的事情。
对于软件问题最希望的就是边看现象边理解。
分析sqlite3加密思路也采用具体现象具体分析的方法。
还是vc2003编译器(使用其它编译器的读者可以自行对应着设置,我下面的叙述并没有使用过多的vc2003编译技巧,对应到vc2005、2008,甚至unix下的cc等等都是通用的)。
首先在工程属性里的“C/C++”选项里找到“命令行”,如图:
自己手工敲上“/D"SQLITE_HAS_CODEC"”。
这个意思是在整个工程里预先define“SQLITE_HAS_CODEC”这个宏。
这个宏是sqlite3作者留下的加密接口。
缺省没有这个宏,sqlite3就是不加密的标准版。
定义有这个宏,那么就是要开启加密功能。
设定好宏后,开始编译。
结果,问题现象就出现了:
testSqliteEncerrorLNK2019:
无法解析的外部符号_sqlite3_activate_see,该符号在函数_sqlite3Pragma中被引用
testSqliteEncerrorLNK2019:
无法解析的外部符号_sqlite3_key,该符号在函数_sqlite3Pragma中被引用
testSqliteEncerrorLNK2019:
无法解析的外部符号_sqlite3_rekey,该符号在函数_sqlite3Pragma中被引用
testSqliteEncerrorLNK2019:
无法解析的外部符号_sqlite3CodecAttach,该符号在函数_attachFunc中被引用
testSqliteEncerrorLNK2019:
无法解析的外部符号_sqlite3CodecGetKey,该符号在函数_attachFunc中被引用
意思很明显,有这些函数的定义,但作者没有实现这些函数。
需要我们自己实现。
反之,大概实现了这些函数后就能进行加密了。
III.研究结果
参阅了网上某文章,照着他的做法的确可以做出加密效果。
在这里十分感谢那文章的作者。
但我并不满足于此。
我无法确认人家的代码有没有问题。
而且看过他代码,觉得封装程度也并不好。
于是希望通过一些研究判断人家代码可靠性。
同时更进一步进行封装。
研究过程不再赘述,无非就是简单编写先前编译缺少的那些函数体,再对照网上代码编写那些函数内容,然后反复参考、单步调试sqlite3源代码。
这里直接给出最终结果。
下面章节再简述关键点。
我本想做到另外建立配套程序文件来实现,力求跟原始sqlite.c文件全兼容,但可惜的是加解密需要操作sqlite内部数据结构以及内部函数,这些函数和结构没有在sqlite3.h头文件里定义。
所以我不得不把代码写到了sqlite3.c文件里。
最终结果虽然是要修改sqlite.c文件,但我已经让修改地方尽量少。
这样改起来不容易出错。
修改地方总共加有3处,这3处都是添加代码,不需要修改,也没有删除。
需要添加的代码下面全部都有。
Sqlite3.c里需要添加代码的位置大概如下图所示:
下面列出源码。
打开sqlite3.c文件。
在最顶端添加:
#ifdefSQLITE_HAS_CODEC
voidDestroyKeyInBtree(void*lpTree);
#defineMY_DESTROY_KEY(BTREE)DestroyKeyInBtree(BTREE)
#else
#defineMY_DESTROY_KEY(BTREE)
#endif
这段代码用于释放我们自己分配的密钥数据块。
密钥数据块都是我们申请的内存,当数据库关闭时,我希望能释放密钥块内存,否则内存泄露。
但是,sqlite3没有提供数据库关闭进行释放密钥块的接口,得自己添加。
根据网上代码分析,我们释放密钥块时需要获得指定的数据库BTree结构体。
因为每个密钥块是关联到特定的数据库BTree结构体上的。
通过分析代码,不难找到sqlite3_close会调用下面这个函数
SQLITE_PRIVATEintsqlite3BtreeClose(Btree*p)
这是sqlite3的函数。
我确认销毁密钥块的时机就是在这个函数里。
搜索到这代码:
sqlite3BtreeRollback(p);
sqlite3BtreeLeave(p);
就在Leave后面添加我们销毁功能,如下:
/*Rollbackanyactivetransactionandfreethehandlestructure.
Thecalltosqlite3BtreeRollback()dropsanytable-locksheldby
thishandle.
*/
sqlite3BtreeRollback(p);
sqlite3BtreeLeave(p);
MY_DESTROY_KEY(p);//我们添加的删除密钥的功能
/*Iftherearestillotheroutstandingreferencestotheshared-btree
structure,returnnow.Theremainderofthisprocedurecleans
uptheshared-btree.
*/
assert(p->wantToLock==0&&p->locked==0);
这个销毁时机不能再往后放了,再往后BTree相关结构体都已被销毁,我们再从它里面获取我们密钥块指针就会崩溃。
因此在我上面代码那里销毁时机刚好。
接下来修改就简单了。
直接拖到最末尾,3.6.12版本的最后两行有效代码应该是:
#endif/*defined(SQLITE_ENABLE_ICU)*/
#endif/*!
defined(SQLITE_CORE)||defined(SQLITE_ENABLE_FTS3)*/
就在最后那个endif后面添加我下面列出来的所有代码。
//////////////////////////////////////////////////////////////////////////
////////////////////下面是我们自己添加的加密函数实现//////////////////////
//////////////////////我认为每个版本的整合sqlite//////////////////////////
////////////////////////只要添加这些函数就可以实现加密功能////////////////
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
#ifndefSQLITE_HAS_CODEC
#ifdefWIN32
#pragmamessage("Wedonotuseencryption")
#endif
#else
#include"./Encrypt.h"
#ifdefWIN32
#pragmamessage("&&&&&&&&Weuseencryption!
&&&&&&&&")
#endif
/*
下面结构体是我们封装的一个中间结构体
目的是为了给用户加解密环境提供更为独立的功能,不依赖一些sqlite莫名其妙设计带来的骚扰
结构体中的lpUserBlock才是用户自己DeriveKey返回的结构体
*/
struct_TmpUserKeyBlock
{
void*lpUserBlock_old;
void*lpUserBlock_new;
};
typedefstruct_TMP_ENCRYPT_BLOCK
{
struct_TmpUserKeyBlocklpUserKey;
intiPageSize;
intiEncryptingBufSize;
charpEncryptingBuf[1];
}TMP_ENCRYPT_BLOCK,*PTMP_ENCRYPT_BLOCK;
/*
下面是对用户DeriveKey、CopyKey、DestroyKey的封装。
封装目的是sqlite解密和加密操作的目标数据内存是不一样的。
如果不加以封装,后面编写加解密函数就
不得不考虑这些细节。
封装后用户函数就不需要再考虑这些部分了。
*/
PTMP_ENCRYPT_BLOCKDeriveKey_Tmp(constvoid*pKey,intnKeyLen,intiPageSize)
{
void*pUserKey;
PTMP_ENCRYPT_BLOCKpTmpBlock;
intiSize=iPageSize+128;
if(NULL==pKey||0==nKeyLen)
{
returnNULL;
}
pTmpBlock=(PTMP_ENCRYPT_BLOCK)malloc(sizeof(TMP_ENCRYPT_BLOCK)+iSize);
if(NULL==pTmpBlock)
{
returnNULL;
}
pTmpBlock->iEncryptingBufSize=iSize;//+128是为了多分配一些内存,免得跟sqlite打交道哪里出现溢出
pTmpBlock->iPageSize=iPageSize;
pUserKey=(void*)DeriveKey(pKey,nKeyLen,iPageSize);
if(NULL==pUserKey)
{
free(pTmpBlock);
returnNULL;
}
//刚开始时,新旧密钥应该都一致
pTmpBlock->lpUserKey.lpUserBlock_old=pUserKey;
pTmpBlock->lpUserKey.lpUserBlock_new=pUserKey;
returnpTmpBlock;
}
//复制一份数据库
PTMP_ENCRYPT_BLOCKCopyKey_Tmp(constPTMP_ENCRYPT_BLOCKlpKey)
{
PTMP_ENCRYPT_BLOCKpNew;
void*pUserKey;
intiSize;
if(NULL==lpKey)
returnNULL;
iSize=lpKey->iEncryptingBufSize;
pNew=(PTMP_ENCRYPT_BLOCK)malloc(iSize+sizeof(TMP_ENCRYPT_BLOCK));
if(NULL==pNew)
returnNULL;
pUserKey=CopyKey(lpKey->lpUserKey.lpUserBlock_new);
if(NULL==pUserKey)
{
free(pNew);
returnNULL;
}
pNew->lpUserKey.lpUserBlock_old=pUserKey;
pNew->lpUserKey.lpUserBlock_new=pUserKey;
pNew->iPageSize=lpKey->iPageSize;
pNew->iEncryptingBufSize=iSize;
returnpNew;
}
//释放密钥
voidDestroyKey_Tmp(PTMP_ENCRYPT_BLOCKlpKey)
{
if(NULL==lpKey)
return;
if(NULL!
=lpKey->lpUserKey.lpUserBlock_new)
DestroyKey(lpKey->lpUserKey.lpUserBlock_new);
free(lpKey);
return;
}
//文件加密解密的中间函数
//本函数做简单处理之后会调用My_sqlite3Codec函数
//nMode类型:
//case0:
//Undoa"case7"journalfileencryption
//case2:
//Reloadapage
//case3:
//Loadapage
//case6:
//Encryptapageforthemaindatabasefile
//case7:
//Encryptapageforthejournalfile
void*My_sqlite3Codec_tmp(void*pArg,void*data,unsignedintnPageNum,intnMode)
{
PTMP_ENCRYPT_BLOCKlpTmpArg;
void*lpRet;
void*lpUserKey;
if(!
pArg)
{
returndata;
}
lpTmpArg=(PTMP_ENCRYPT_BLOCK)pArg;
if(nMode<=5)
{
//解密
lpRet=data;
lpUserKey=lpTmpArg->lpUserKey.lpUserBlock_old;
}
else
{
//加密
lpRet=lpTmpArg->pEncryptingBuf;
memcpy(lpRet,data,lpTmpArg->iPageSize);
if(nMode==7)
{
//操作事务文件,需要用旧密钥
lpUserKey=lpTmpArg->lpUserKey.lpUserBlock_old;
}
else
{
//否则就是写入数据库页面,用新密钥
lpUserKey=lpTmpArg->lpUserKey.lpUserBlock_new;
}
}
if(NULL==lpUserKey)
returnlpRet;
My_sqlite3Codec(lpUserKey,lpRet,lpTmpArg->iPageSize,nPageNum,nMode);
returnlpRet;
}
//用于重设密钥
staticvoid*My_sqlite3pager_get_codecarg(Pager*pPager)
{
return(pPager->xCodec)?
pPager->pCodecArg:
NULL;
}
/*
设置数据库页面加解密参数
*/
voidMy_sqlite3pager_set_codec(Pager*pPager,void*(*xCodec)(void*,void*,Pgno,int),void*pCodecArg)
{
pPager->xCodec=xCodec;
pPager->pCodecArg=pCodecArg;
}
/*
**返回数据库磁盘文件总页面数
**IfthePENDING_BYTEliesonthepagedirectlyaftertheendofthe
**file,thenconsiderthispagepartofthefiletoo.Forexample,if
**PENDING_BYTEisbyte4096(thefirstbyteofpage5)andthesizeofthe
**fileis4096bytes,5isreturnedinsteadof4.
*/
intMy_sqlite3pager_pagecount(Pager*pPager)
{
sqlite_int64n;
assert(pPager);
if(pPager->dbSize>=0)
{
n=pPager->dbSize;
}
else
{
if(sqlite3OsFileSize(pPager->fd,&n)!
=SQLITE_OK)
{
pager_error(pPager,SQLITE_IOERR);
return0;
}
if(n>0&&npageSize)
{
n=1;
}
else
{
n/=pPager->pageSize;
}
if(pPager->state!
=PAGER_UNLOCK)
{
pPager->dbSize=(Pgno)n;
}
}
if(n==(PENDING_BYTE/pPager->pageSize))
{
n++;
}
return(int)n;
}
//设置密钥
voidMySetKey(sqlite3*db,constvoid*pKey,intnKey)
{
//设置密钥简单,直接调用密钥设置接口就行了
sqlite3CodecAttach(db,0,pKey,nKey);
}
//重新设置密钥
intMyResetKey(sqlite3*db,constvoid*pKey,intnKey)
{
Btree*pbt=db->aDb[0].pBt;
Pager*p=sqlite3BtreePager(pbt);
PTMP_ENCRYPT_BLOCKpNewKey=NULL;
PTMP_ENCRYPT_BLOCKpOldKey=(PTMP_ENCRYPT_BLOCK)My_sqlite3pager_get_codecarg(p);
void*lpUserOld=NULL;
intrc=SQLITE_ERROR;
//总之都是要新分配一个新密钥空间的
pNewKey=DeriveKey_Tmp(pKey,nKey,(int)(p->pageSize));
if(NULL==pOldKey&&NULL==pNewKey)
{
returnSQLITE_OK;
}
if(NULL==pOldKey)
{
pNewKey->lpUserKey.lpUserBlock_old=NULL;
My_sqlite3pager_set_codec(sqlite3BtreePager(pbt),My_sqlite3Codec_tmp,pNewKey);
}
else
{
lpUserOld=pOldKey->lpUserKey.lpUserBlock_new;
if(NULL!
=pNewKey)
pOldKey->lpUserKey.lpUserBlock_new=pNewKey->lpUserKey.lpUserBlock_new;
else
pOldKey->lpUserKey.lpUserBlock_new=NULL;
}
//开始事务,加密应该要加密的页面
rc=sqlite3BtreeBeginTrans(pbt,1);
if(!
rc)
{
//用新密钥重新加密并写入页面
PgnonPage=My_sqlite3pager_pagecount(p);
PgnonSkip=PAGER_MJ_PGNO(p);
void*pPage;
Pgnon;
for(n=1;rc==SQLITE_OK&&n<=nPage;n++)
{
if(n==nSkip)continue;
rc=sqlite3PagerGet(p,n,&pPage);
if(!
rc)
{
rc=sqlite3PagerWrite(pPage);
sqlite3PagerUnref(pPage);
}
}
}
//尝试提交事务
if(!
rc)
{
rc=sqlite3BtreeCommit(pbt);
}
//如果失败,就回滚事务
if(rc)
{
sqlite3BtreeRollback(pbt);
}
if(NULL!
=pOldKey)
{
if(NULL!
=lpUserOld)
{
pOldKey->lpUserKey.lpUserBlock_new=lpUserOld;
}
else
{
pO