前言
本文主要是介紹Sqlite 3.7.17版本加密功能添加。本文的代碼是基于網上流傳的一份樣本,并結合國外的一份加密開源代碼做了完善修改。
發表此文的原因
本人需要對sqlite的資料庫進行加密,使用了網絡流傳的一份代碼,發現有BUG。研究了一下,發現因為加密的時候把sqlite的資料庫檔案完全都加密了,連一些重要的版本等等資訊都給加密,導緻資料庫檔案讀取的時候pagesize這種重要的參數出現随機數,sqlite引擎在處理這個随機數的時候,大部分是能相容的,小部分機率會導緻資料庫檔案打不開。而在實際使用中,這種小機率引起的問題是非常嚴重的。并且網上流傳的代碼有明顯的缺陷,有一處拷貝記憶體的地方,目标和源都搞反了。
原理介紹
網上有介紹兩種方法:
方案一:(徹底解決方案)修改sqlite源碼,使opendatase讀取的pagesize無效,在設定好資料庫密鑰以後,第一次讀取資料時重新計算pagesize;
方案二:(針對加密的修補方案)修改sqlite3_key的加密實作,在設定密鑰時,解密讀取資料庫的頭資訊,讀取解密後的pagesize,再把這個正确的pagesize設定回去;
本人提出新的一種方法:
保留檔案頭法:檔案頭128位元組不加密,這樣讀取的pagesize就應該不會有錯!
提出這個方法的原因:sqlite的源碼版本總會前進改變,pagesize的位置今天是16,沒準下個版本會換到什麼位置,是以網上介紹的“方案二”不是長久辦法;而修改sqlite的源碼,這種實在是太暴力了,以後更新sqlite代碼時候,肯定不可取。
這個方法使用128位元組,是因為這樣能覆寫大部分檔案頭重要資訊了,而又沒洩漏太多的資料庫重要資料。實用又簡單。
這個方法經過測試驗證,暫時沒有發現什麼問題。如果誰發現有漏洞,歡迎探讨。
下面貼出關鍵的算法:
void* sqlite3Codec(void *pCodec, void *data, Pgno nPageNum, int nMode)
{
void *codecptr = data;
LPCryptBlock pBlock = (LPCryptBlock) pCodec;
int len = 0;
if (pCodec == NULL)
return data;
switch(nMode)
{
case 0:
case 2:
case 3:
if (!pBlock->ReadKey)
break;
len = 0 - (pBlock->PageSize/4);
if(nPageNum == 1)
{
len += no_codec_header_size/4;
(BYTE *)codecptr += no_codec_header_size;
}
call_Decrypt((int *)codecptr, len, (int *)pBlock->ReadKey);
break;
case 6:
if (!pBlock->WriteKey)
break;
memcpy(pBlock->Data + CRYPT_OFFSET, data, pBlock->PageSize);
data = pBlock->Data + CRYPT_OFFSET;
len = pBlock->PageSize/4;
codecptr = data;
if(nPageNum == 1)
{
len -= no_codec_header_size/4;
(BYTE *)codecptr += no_codec_header_size;
}
call_Encrypt((int *)codecptr , len, (int *)pBlock->WriteKey);
break;
case 7:
if (!pBlock->ReadKey)
break;
memcpy(pBlock->Data + CRYPT_OFFSET, data, pBlock->PageSize);
data = pBlock->Data + CRYPT_OFFSET;
len = pBlock->PageSize/4;
codecptr = data;
if(nPageNum == 1)
{
len -= no_codec_header_size/4;
(BYTE *)codecptr += no_codec_header_size;
}
call_Encrypt((int *)codecptr, len, (int *)pBlock->ReadKey);
break;
}
return data;
}