天天看點

SQLite3 使用總結前序一、版本二、基本編譯三、SQLITE操作入門四、 給資料庫加密

前序

Sqlite3 的确很好用。小巧、速度快。但是因為非微軟的産品,幫助文檔總覺得不夠。這些天再次研究它,又有一些收獲,這裡把我對 sqlite3 的研究列出來,以備忘記。

這裡要注明,我是一個跨平台專注者,并不喜歡隻用 windows 平台。我以前的工作就是為 unix 平台寫代碼。下面我所寫的東西,雖然沒有驗證,但是我已盡量不使用任何windows 的東西,隻使用标準 C 或标準C++。但是,我沒有嘗試過在别的系統、别的編譯器下編譯,是以下面的叙述如果不正确,則留待以後修改。

下面我的代碼仍然用 VC 編寫,因為我覺得VC是一個很不錯的IDE,可以加快代碼編寫速度(例如配合 Vassist )。下面我所說的編譯環境,是VC2003。如果讀者覺得自己習慣于 unix 下用 vi 編寫代碼速度較快,可以不用管我的說明,隻需要符合自己習慣即可,因為我用的是标準 C 或 C++ 。不會給任何人帶來不便。

一、版本

從 www.sqlite.org<http://www.sqlite.org/> 網站可下載下傳到最新的 sqlite 代碼和編譯版本。我寫此文章時,最新代碼是 3.3.17 版本。

很久沒有去下載下傳 sqlite 新代碼,是以也不知道 sqlite 變化這麼大。以前很多檔案,現在全部合并成一個 sqlite3.c 檔案。如果單獨用此檔案,是挺好的,省去拷貝一堆檔案還擔心有沒有遺漏。但是也帶來一個問題:此檔案太大,快接近7萬行代碼,VC開它整個機器都慢下來了。如果不需要改它代碼,也就不需要打開sqlite3.c檔案,機器不會慢。但是,下面我要寫通過修改 sqlite 代碼完成加密功能,那時候就比較痛苦了。如果個人水準較高,建議用些簡單的編輯器來編輯,例如 UltraEdit 或 Notepad 。速度會快很多。(源碼網整理,www.codepub.com)

二、基本編譯

這個不想多說了,在 VC 裡建立 dos 控制台空白工程,把 sqlite3.c 和sqlite3.h 添加到工程,再建立一個 main.cpp 檔案。在裡面寫:

[cpp] view plain copy

  1. extern "C"  
  2. {  
  3. #include"./sqlite3.h"  
  4. };  
  5. int main( int , char** )  
  6. {  
  7. return 0;  
  8. }  

為什麼要 extern “C”?如果問這個問題,我不想說太多,這是C++的基礎。要在C++ 裡使用一段 C 的代碼,必須要用 extern “C”括起來。C++跟 C雖然文法上有重疊,但是它們是兩個不同的東西,記憶體裡的布局是完全不同的,在C++編譯器裡不用extern “C”括起C代碼,會導緻編譯器不知道該如何為 C 代碼描述記憶體布局。

可能在 sqlite3.c 裡人家已經把整段代碼都 extern “C”括起來了,但是你遇到一個 .c 檔案就自覺的再括一次,也沒什麼不好。

基本工程就這樣建立起來了。編譯,可以通過。但是有一堆的 warning。可以不管它。

三、SQLITE操作入門

sqlite提供的是一些C函數接口,你可以用這些函數操作資料庫。通過使用這些接口,傳遞一些标準sql 語句(以 char * 類型)給 sqlite 函數,sqlite 就會為你操作資料庫。

sqlite 跟MS的access一樣是檔案型資料庫,就是說,一個資料庫就是一個檔案,此資料庫裡可以建立很多的表,可以建立索引、觸發器等等,但是,它實際上得到的就是一個檔案。備份這個檔案就備份了整個資料庫。

sqlite 不需要任何資料庫引擎,這意味着如果你需要 sqlite 來儲存一些使用者資料,甚至都不需要安裝資料庫

(如果你做個小軟體還要求人家必須裝了sqlserver才能運作,那也太黑心了)。

下面開始介紹資料庫基本操作。

1、 基本流程

(1) 關鍵資料結構

sqlite 裡最常用到的是 sqlite3   * 類型。從資料庫打開開始,sqlite就要為這個類型準備好記憶體,直到資料庫關閉,整個過程都需要用到這個類型。當資料庫打開時開始,這個類型的變量就代表了你要操作的資料庫。下面再詳細介紹。

(2) 打開資料庫

[cpp] view plain copy

  1. int sqlite3_open( 檔案名, sqlite3  ** );  

用這個函數開始資料庫操作。

需要傳入兩個參數,一是資料庫檔案名,比如:c:\\DongChunGuang_Database.db。檔案名不需要一定存在,如果此檔案不存在,sqlite 會自動建立它。如果它存在,就嘗試把它當資料庫檔案來打開。sqlite3  ** 參數即前面提到的關鍵資料結構。這個結構底層細節如何,你不要關它。函數傳回值表示操作是否正确,如果是 SQLITE_OK 則表示操作正常。相關的傳回值sqlite定義了一些宏。具體這些宏的含義可以參考 sqlite3.h 檔案。裡面有詳細定義(順便說一下,sqlite3 的代碼注釋率自稱是非常高的,實際上也的确很高。隻要你會看英文,sqlite 可以讓你學到不少東西)。

下面介紹關閉資料庫後,再給一段參考代碼。

(3) 關閉資料庫

[cpp] view plain copy

  1. int sqlite3_close(sqlite3  *);  

前面如果用 sqlite3_open 開啟了一個資料庫,結尾時不要忘了用這個函數關閉資料庫。

下面給段簡單的代碼:

[cpp] view plain copy

  1. extern "C"  
  2. {  
  3. #include"./sqlite3.h"  
  4. };  
  5. int main( int , char** )  
  6. {  
  7.    sqlite3 * db = NULL; //聲明sqlite關鍵結構指針  
  8.    int result  
  9. //打開資料庫  
  10. //需要傳入 db 這個指針的指針,因為 sqlite3_open 函數要為這個指針配置設定記憶體,還要讓db指針指向這個記憶體區  
  11.    result = sqlite3_open( “c:\\Dcg_database.db”, &db );  
  12.    if( result != SQLITE_OK )  
  13.     {  
  14.       //資料庫打開失敗  
  15. return -1;  
  16. }  
  17. //資料庫操作代碼  
  18. //…  
  19. //資料庫打開成功  
  20. //關閉資料庫  
  21. sqlite3_close( db );  
  22. return 0;  
  23. }  

這就是一次資料庫操作過程。

2、 SQL語句操作

本節介紹如何用sqlite 執行标準 sql 文法。

(1) 執行sql語句

[cpp] view plain copy

  1. int sqlite3_exec(sqlite3*, const char *sql, sqlite3_callback, void *,  char **errmsg );  

這就是執行一條 sql 語句的函數。

第1個參數不再說了,是前面open函數得到的指針。說了是關鍵資料結構。

第2個參數const char*sql 是一條 sql 語句,以\0結尾。

第3個參數sqlite3_callback是回調,當這條語句執行之後,sqlite3會去調用你提供的這個函數。(什麼是回調函數,自己找别的資料學習)

第4個參數void* 是你所提供的指針,你可以傳遞任何一個指針參數到這裡,這個參數最終會傳到回調函數裡面,如果不需要傳遞指針給回調函數,可以填NULL。等下我們再看回調函數的寫法,以及這個參數的使用。

第5個參數char **errmsg 是錯誤資訊。注意是指針的指針。sqlite3裡面有很多固定的錯誤資訊。執行 sqlite3_exec 之後,執行失敗時可以查閱這個指針(直接 printf(“%s\3n”,errmsg))得到一串字元串資訊,這串資訊告訴你錯在什麼地方。sqlite3_exec函數通過修改你傳入的指針的指針,把你提供的指針指向錯誤提示資訊,這樣sqlite3_exec函數外面就可以通過這個 char*得到具體錯誤提示。

說明:通常,sqlite3_callback 和它後面的 void * 這兩個位置都可以填 NULL。填NULL表示你不需要回調。比如你做 insert 操作,做 delete 操作,就沒有必要使用回調。而當你做select 時,就要使用回調,因為 sqlite3 把資料查出來,得通過回調告訴你查出了什麼資料。

(2) exec 的回調

[cpp] view plain copy

  1. typedef int(*sqlite3_callback)(void*,int,char**, char**);  

你的回調函數必須定義成上面這個函數的類型。下面給個簡單的例子:

[cpp] view plain copy

  1. //sqlite3的回調函數         
  2. // sqlite 每查到一條記錄,就調用一次這個回調  
  3. int LoadMyInfo( void * para,  intn_column,  char ** column_value,  char ** column_name )  
  4. {  
  5. //para是你在 sqlite3_exec 裡傳入的void * 參數  
  6. //通過para參數,你可以傳入一些特殊的指針(比如類指針、結構指針),然後在這裡面強制轉換成對應的類型(這裡面是void*類型,必須強制轉換成你的類型才可用)。然後操作這些資料  
  7. //n_column是這一條記錄有多少個字段 (即這條記錄有多少列)  
  8. // char ** column_value 是個關鍵值,查出來的資料都儲存在這裡,它實際上是個1維數組(不要以為是2維數組),每一個元素都是一個 char * 值,是一個字段内容(用字元串來表示,以\0結尾)  
  9. //char ** column_name 跟column_value是對應的,表示這個字段的字段名稱  
  10. //這裡,我不使用 para 參數。忽略它的存在.  
  11. int i;  
  12. printf( “記錄包含 %d 個字段\n”, n_column );  
  13. for( i = 0 ; i < n_column; i ++ )  
  14. {  
  15.     printf( “字段名:%s  ?> 字段值:%s\n”,  column_name[i], column_value[i] );  
  16. }  
  17. printf( “------------------\n“ );         
  18. return 0;  
  19. }  
  20. int main( int , char ** )  
  21. {  
  22.           sqlite3 * db;  
  23.           int result;  
  24.           char * errmsg =NULL;  
  25.           result =sqlite3_open( “c:\\Dcg_database.db”, &db );  
  26.                 if( result !=SQLITE_OK )  
  27.                 {  
  28.                 //資料庫打開失敗  
  29. return -1;  
  30. }  
  31. //資料庫操作代碼  
  32. //建立一個測試表,表名叫MyTable_1,有2個字段: ID 和 name。其中ID是一個自動增加的類型,以後insert時可以不去指定這個字段,它會自己從0開始增加  
  33. result = sqlite3_exec( db, “create table MyTable_1( ID integerprimary key autoincrement, name nvarchar(32) )”, NULL, NULL, errmsg );  
  34. if(result != SQLITE_OK )  
  35. {  
  36.      printf( “建立表失敗,錯誤碼:%d,錯誤原因:%s\n”, result, errmsg );  
  37. }  
  38. //插入一些記錄  
  39. result = sqlite3_exec( db, “insert into MyTable_1( name ) values ( ‘走路’ )”, 0, 0, errmsg );  
  40. if(result != SQLITE_OK )  
  41. {  
  42.      printf( “插入記錄失敗,錯誤碼:%d,錯誤原因:%s\n”, result, errmsg );  
  43. }  
  44. result = sqlite3_exec( db, “insert into MyTable_1( name ) values ( ‘騎單車’ )”, 0, 0, errmsg );  
  45. if(result != SQLITE_OK )  
  46. {  
  47.      printf( “插入記錄失敗,錯誤碼:%d,錯誤原因:%s\n”, result, errmsg );  
  48. }  
  49. result = sqlite3_exec( db, “insert into MyTable_1( name ) values ( ‘坐汽車’ )”, 0, 0, errmsg );  
  50. if(result != SQLITE_OK )  
  51. {  
  52.      printf( “插入記錄失敗,錯誤碼:%d,錯誤原因:%s\n”, result, errmsg );  
  53. }  
  54. //開始查詢資料庫  
  55. result = sqlite3_exec( db, “select * from MyTable_1”, LoadMyInfo, NULL, errmsg );  
  56. //關閉資料庫  
  57. sqlite3_close( db );  
  58. return 0;  
  59. }  

通過上面的例子,應該可以知道如何打開一個資料庫,如何做資料庫基本操作。

有這些知識,基本上可以應付很多資料庫操作了。

(3) 不使用回調查詢資料庫

上面介紹的 sqlite3_exec 是使用回調來執行 select 操作。還有一個方法可以直接查詢而不需要回調。但是,我個人感覺還是回調好,因為代碼可以更加整齊,隻不過用回調很麻煩,你得聲明一個函數,如果這個函數是類成員函數,你還不得不把它聲明成 static 的(要問為什麼?這又是C++基礎了。C++成員函數實際上隐藏了一個參數:this,C++調用類的成員函數的時候,隐含把類指針當成函數的第一個參數傳遞進去。結果,這造成跟前面說的 sqlite 回調函數的參數不相符。隻有當把成員函數聲明成 static 時,它才沒有多餘的隐含的this參數)。

雖然回調顯得代碼整齊,但有時候你還是想要非回調的 select 查詢。這可以通過 sqlite3_get_table 函數做到。

[cpp] view plain copy

  1. int sqlite3_get_table(sqlite3*, const char *sql, char ***resultp,int *nrow, int *ncolumn, char **errmsg );  

第1個參數不再多說,看前面的例子。

第2個參數是 sql 語句,跟sqlite3_exec 裡的 sql 是一樣的。是一個很普通的以\0結尾的char *字元串。

第3個參數是查詢結果,它依然一維數組(不要以為是二維數組,更不要以為是三維數組)。它記憶體布局是:第一行是字段名稱,後面是緊接着是每個字段的值。下面用例子來說事。

第4個參數是查詢出多少條記錄(即查出多少行)。

第5個參數是多少個字段(多少列)。

第6個參數是錯誤資訊,跟前面一樣,這裡不多說了。

下面給個簡單例子:

[cpp] view plain copy

  1. int main( int , char ** )  
  2. {  
  3.        sqlite3 * db;  
  4.        int result;  
  5.        char * errmsg = NULL;  
  6. char **dbResult; //是 char ** 類型,兩個*号  
  7.        int nRow, nColumn;  
  8.        int i , j;  
  9. int index;  
  10. result = sqlite3_open( “c:\\Dcg_database.db”, &db );          
  11.            if( result != SQLITE_OK )  
  12.              { //資料庫打開失敗  
  13. return -1;  
  14. }  
  15. //資料庫操作代碼  
  16. //假設前面已經建立了MyTable_1 表  
  17. //開始查詢,傳入的dbResult 已經是 char **,這裡又加了一個 & 取位址符,傳遞進去的就成了 char ***  
  18. result = sqlite3_get_table( db, “select *from MyTable_1”,&dbResult, &nRow, &nColumn, &errmsg );  
  19. if( SQLITE_OK == result )  
  20. { //查詢成功  
  21.    index = nColumn; //前面說過 dbResult 前面第一行資料是字段名稱,從 nColumn 索引開始才是真正的資料  
  22.     printf( “查到%d條記錄\n”, nRow );  
  23.     for(  i = 0; i < nRow ; i++ )  
  24.     {  
  25.         printf( “第 %d 條記錄\n”, i+1 );  
  26.         for( j = 0 ; j < nColumn; j++ )  
  27.         {  
  28.               printf( “字段名:%s  ?> 字段值:%s\n”,  dbResult[j], dbResult [index] );  
  29.               ++index; // dbResult 的字段值是連續的,從第0索引到第 nColumn - 1索引都是字段名稱,從第 nColumn 索引開始,後面都是字段值,它把一個二維的表(傳統的行清單示法)用一個扁平的形式來表示  
  30.         }  
  31.         printf( “-------\n” );  
  32.     }  
  33. }  
  34. //到這裡,不論資料庫查詢是否成功,都釋放 char** 查詢結果,使用 sqlite 提供的功能來釋放  
  35. sqlite3_free_table( dbResult );  
  36. //關閉資料庫  
  37. sqlite3_close( db );  
  38. return 0;  
  39. }  

到這個例子為止,sqlite3 的常用用法都介紹完了。

用以上的方法,再配上 sql 語句,完全可以應付絕大多數資料庫需求。

但有一種情況,用上面方法是無法實作的:需要insert、select 二進制。當需要處理二進制資料時,上面的方法就沒辦法做到。下面這一節說明如何插入二進制資料

3、 操作二進制

sqlite 操作二進制資料需要用一個輔助的資料類型:sqlite3_stmt * 。

這個資料類型記錄了一個“sql語句”。為什麼我把 “sql語句”用雙引号引起來?因為你可以把 sqlite3_stmt * 所表示的内容看成是 sql語句,但是實際上它不是我們所熟知的sql語句。它是一個已經把sql語句解析了的、用sqlite自己标記記錄的内部資料結構。

正因為這個結構已經被解析了,是以你可以往這個語句裡插入二進制資料。當然,把二進制資料插到 sqlite3_stmt 結構裡可不能直接 memcpy ,也不能像 std::string 那樣用 + 号。必須用 sqlite 提供的函數來插入。

(1) 寫入二進制

下面說寫二進制的步驟。

要插入二進制,前提是這個表的字段的類型是 blob 類型。我假設有這麼一張表:

create table Tbl_2( IDinteger, file_content  blob )

首先聲明

[cpp] view plain copy

  1. sqlite3_stmt * stat;  

然後,把一個 sql 語句解析到 stat 結構裡去:

// sqlite3_prepare 接口把一條SQL語句編譯成位元組碼留給後面的執行函數. 使用該接口通路資料庫是目前比較好的的一種方法.

[cpp] view plain copy

  1. sqlite3_prepare( db,“insert into Tbl_2( ID, file_content) values( 10, ? )”, -1, &stat, 0 );  

上面的函數完成 sql 語句的解析。第一個參數跟前面一樣,是個 sqlite3 * 類型變量,第二個參數是一個 sql 語句。

這個 sql 語句特别之處在于 values 裡面有個? 号。在sqlite3_prepare函數裡,?号表示一個未定的值,它的值等下才插入。第三個參數我寫的是-1,這個參數含義是前面 sql 語句的長度。如果小于0,sqlite會自動計算它的長度(把sql語句當成以\0結尾的字元串)。第四個參數是 sqlite3_stmt 的指針的指針。解析以後的sql語句就放在這個結構裡。

第五個參數我也不知道是幹什麼的。為0就可以了。

如果這個函數執行成功(傳回值是 SQLITE_OK 且 stat 不為NULL ),那麼下面就可以開始插入二進制資料。

[cpp] view plain copy

  1. sqlite3_bind_blob(stat, 1, pdata, (int)(length_of_data_in_bytes), NULL ); // pdata為資料緩沖區,length_of_data_in_bytes為資料大小,以位元組為機關  

這個函數一共有5個參數。

第1個參數:是前面prepare得到的 sqlite3_stmt * 類型變量。

第2個參數:?号的索引。前面prepare的sql語句裡有一個?号,假如有多個?号怎麼插入?方法就是改變 bind_blob 函數第2個參數。這個參數我寫1,表示這裡插入的值要替換stat 的第一個?号(這裡的索引從1開始計數,而非從0開始)。如果你有多個?号,就寫多個 bind_blob 語句,并改變它們的第2個參數就替換到不同的?号。如果有?号沒有替換,sqlite為它取值null。

第3個參數:二進制資料起始指針。

第4個參數:二進制資料的長度,以位元組為機關。

第5個參數:是個析夠回調函數,告訴sqlite當把資料處理完後調用此函數來析夠你的資料。這個參數我還沒有使用過,是以了解也不深刻。但是一般都填NULL,需要釋放的記憶體自己用代碼來釋放。

bind完了之後,二進制資料就進入了你的“sql語句”裡了。你現在可以把它儲存到資料庫裡:

虛拟機執行位元組碼,執行過程是一個步進(stepwise)的過程,每一步(step)由sqlite3_step()啟動,并由VDBE(sqlite虛拟機)執行一段位元組 碼。由sqlite3_prepare編譯位元組代碼,并由sqlite3_step()啟動虛拟機執行。在周遊結果集的過程中,它傳回SQLITE_ROW,當到達結果末尾時,傳回SQLITE_DONE

[cpp] view plain copy

  1. int result =sqlite3_step( stat );  

通過這個語句,stat 表示的sql語句就被寫到了資料庫裡。

最後,要把 sqlite3_stmt結構給釋放:sqlite3_finalize( stat ); //把剛才配置設定的内容析構掉

(2) 讀出二進制

下面說讀二進制的步驟。

跟前面一樣,

先聲明 sqlite3_stmt *類型變量:

[cpp] view plain copy

  1. sqlite3_stmt * stat;  

然後,把一個 sql 語句解析到 stat 結構裡去:

[cpp] view plain copy

  1. sqlite3_prepare( db,“select * from Tbl_2”, -1,&stat, 0 );  

當 prepare 成功之後(傳回值是 SQLITE_OK ),開始查詢資料。

[cpp] view plain copy

  1. int result =sqlite3_step( stat );  

這一句的傳回值是SQLITE_ROW 時表示成功(不是 SQLITE_OK )。

你可以循環執行 sqlite3_step 函數,一次 step 查詢出一條記錄。直到傳回值不為 SQLITE_ROW 時表示查詢結束。

然後開始擷取第一個字段:ID 的值。ID是個整數,用下面這個語句擷取它的值:

int id =sqlite3_column_int( stat, 0 ); //第2個參數表示擷取第幾個字段内容,從0開始計算,因為我的表的ID字段是第一個字段,是以這裡我填0

下面開始擷取 file_content 的值,因為 file_content 是二進制,是以我需要得到它的指針,還有它的長度:

[cpp] view plain copy

  1. const void * pFileContent =sqlite3_column_blob( stat, 1 );  
  2. int len = sqlite3_column_bytes( stat, 1 );  

這樣就得到了二進制的值。

把 pFileContent 的内容儲存出來之後,

不要忘了釋放sqlite3_stmt 結構:

[cpp] view plain copy

  1. sqlite3_finalize( stat ); //把剛才配置設定的内容析構掉  

(3) 重複使用 sqlite3_stmt 結構

如果你需要重複使用sqlite3_prepare 解析好的 sqlite3_stmt 結構,需要用函數:sqlite3_reset。

[cpp] view plain copy

  1. result = sqlite3_reset(stat);  

這樣, stat 結構又成為sqlite3_prepare 完成時的狀态,你可以重新為它bind 内容。

(4) 事務處理

sqlite 是支援事務處理的。如果你知道你要同步删除很多資料,不仿把它們做成一個統一的事務。通常一次 sqlite3_exec 就是一次事務,如果你要删除1萬條資料,sqlite就做了1萬次:開始新事務->删除一條資料->送出事務->開始新事務->… 的過程。這個操作是很慢的。因為時間都花在了開始事務、送出事務上。你可以把這些同類操作做成一個事務,這樣如果操作錯誤,還能夠復原事務。

事務的操作沒有特别的接口函數,它就是一個普通的 sql 語句而已:

分别如下:

[cpp] view plain copy

  1. int result;  
  2. result =sqlite3_exec( db, "begin transaction", 0, 0, &zErrorMsg ); //開始一個事務  
  3. result =sqlite3_exec( db, "commit transaction", 0, 0, &zErrorMsg ); //送出事務  
  4. result = sqlite3_exec(db, "rollback transaction", 0, 0, &zErrorMsg ); //復原事務  

(3) 補充

  基本上,使用sqlite3_open, sqlite3_close, sqlite3_exec這三個函數,可以完成大大部分的工作。但還不完善。上面的例子中,都是直接以sql語句的形式來操作資料庫,這樣很容易被注入。是以有必要使用sql參數。

sqlite3_prepare

sqlite3_bind_*

sqlite3_step

sqlite3_column_*

struct sqlite3_stmt

sqlite3_finalize

sqlite3_prepare用來編譯sql語句。sql語句被執行之前,必須先編譯成位元組碼。S

qlite3_stmt是一個結構體,表示sql語句編譯後的位元組碼。

sqlite3_step用來執行編譯後的sql語句。

sqlite3_bind_*用于将sql參數綁定到sql語句。

sqlite3_column_*用于從查詢的結果中擷取資料。

sqlite3_finalize用來釋放sqlite3_stmt對象。

代碼最能說明函數的功能,

下面就用一個例子來示範吧~~

[cpp] view plain copy

  1. //----------------------------------------------  
  2. //sqlite3_prepare, sqlite3_bind_*,  
  3. //sqlite3_step, sqlite3_column_*,  
  4. //sqlite3_column_type  
  5. //sqlite3_stmt, sqlite3_finalize,sqlite3_reset  
  6. //查詢  
  7. //----------------------------------------------  
  8. sqlite3 *conn = NULL;  
  9. sqlite3_stmt *stmt = NULL;  
  10. const char *err_msg = NULL;  
  11. // 列資料類型  
  12. char col_types[][10] = { "","Integer", "Float", "Text", "Blob", "NULL"};  
  13. sqlite3_open("test.db",&conn);  
  14. sqlite3_prepare(conn, "SELECT * FROM[test_for_cpp] WHERE [id]>?", -1, &stmt, &err_msg);  
  15. sqlite3_bind_int(stmt, 1, 5);  
  16. while (SQLITE_ROW == sqlite3_step(stmt))  
  17. {                                                                     
  18.    int col_count = sqlite3_column_count(stmt); // 結果集中列的數量  
  19.    const char *col_0_name = sqlite3_column_name(stmt, 0); // 擷取列名  
  20.    int id = sqlite3_column_int(stmt, 0);  
  21.    int id_type = sqlite3_column_type(stmt, 0); // 擷取列資料類型  
  22.    const char *col_2_name = sqlite3_column_name(stmt, 2);  
  23.    int age = sqlite3_column_int(stmt, 2);  
  24.    int age_type = sqlite3_column_type(stmt, 2);  
  25.    const char *col_1_name = sqlite3_column_name(stmt, 1);  
  26.    char name[80];  
  27.    strncpy(name, (const char *)sqlite3_column_text(stmt, 1), 80);  
  28.    int name_type = sqlite3_column_type(stmt, 1);  
  29.    // 列印結果  
  30.    printf("col_count: %d, %s = %d(%s), %s = %s(%s), %s = %d(%s)\n",  
  31.        col_count, col_0_name, id, col_types[id_type], col_2_name, name,  
  32.        col_types[name_type], col_1_name, age, col_types[age_type]);  
  33. }  
  34. sqlite3_finalize(stmt); // 釋放sqlite3_stmt  
  35. sqlite3_close(conn);  

這段代碼查詢id号大于5的所有記錄,并顯示到控制台,最後效果為

Sqlite c/c++ api學習 -stanfordxu - stanfordxu的部落格其他函數

在上面的例子中,還使用了其他的一些函數,如:

sqlite3_column_count用于擷取結果集中列的數量;

sqlite3_column_name用于擷取列的名稱;

sqlite3_column_type用于擷取列的資料類型;

sqlite3_errcode用于擷取最近一次操作出錯的錯誤代碼;

sqlite3_errmsg用于擷取最近一次操作出錯的錯誤說明。 sqlite的api中還有很多的函數,有了上面的基礎,相信你通過查詢官方的文檔,能迅速掌握本文未介紹的api。

字元串編碼

在官網上檢視Sqlite的api的時候,發現有很同函數的名稱都非常相似,隻是最後添加了”_16”,如:sqlite3_open和 sqlite3_open16,  sqlite3_errmsg和sqlite3_errmsg16,等等。其實添加了”16”字尾的函數,主要用于支援utf-16編碼的字元串。如 sqlite3_open16可以接收utf-16編碼的資料庫路徑。

在sourceforge上,有一個開源的項目sqlitex,它封裝了這些api,使對sqlite資料庫的操作更加友善。sqlitex的源代碼非常的簡單,感興趣的同學可以下載下傳下來自己研究。

[cpp] view plain copy

  1. ///   另外一個代碼  ///  
  2. #include <stdio.h>  
  3. #include <stdlib.h>  
  4. #include "sqlite3.h"  
  5. #include <string.h>  
  6. int main(int argc, char **argv)  
  7. {  
  8.    int rc, i, ncols;  
  9.    sqlite3 *db;  
  10.    sqlite3_stmt *stmt;  
  11.    char *sql;  
  12.    const char *tail;  
  13.    //打開資料  
  14.    rc = sqlite3_open("foods.db", &db);  
  15.    if(rc) {  
  16.        fprintf(stderr, "Can't open database: %s\n",  
  17. sqlite3_errmsg(db));  
  18.        sqlite3_close(db);  
  19.        exit(1);  
  20.     }  
  21.    sql = "select * from episodes";  
  22.    //預處理  
  23.    rc = sqlite3_prepare(db, sql, (int)strlen(sql), &stmt, &tail);  
  24.    if(rc != SQLITE_OK) {  
  25.        fprintf(stderr, "SQL error: %s\n", sqlite3_errmsg(db));  
  26.     }  
  27.    rc = sqlite3_step(stmt);  
  28.    ncols = sqlite3_column_count(stmt);  
  29.    while(rc == SQLITE_ROW) {  
  30.        for(i=0; i < ncols; i++) {  
  31.            fprintf(stderr, "'%s' ", sqlite3_column_text(stmt, i));  
  32.        }  
  33.        fprintf(stderr, "\n");  
  34.        rc = sqlite3_step(stmt);  
  35.     }  
  36.    //釋放statement  
  37.    sqlite3_finalize(stmt);  
  38.    //關閉資料庫  
  39.    sqlite3_close(db);  
  40.    return 0;     
  41. //=====================================================================  

四、 給資料庫加密

前面所說的内容網上已經有很多資料,雖然比較零散,但是花點時間也還是可以找到的。現在要說的這個——資料庫加密,資料就很難找。也可能是我操作水準不夠,找不到對應資料。但不管這樣,我還是通過網上能找到的很有限的資料,探索出了給sqlite資料庫加密的完整步驟。

這裡要提一下,雖然 sqlite 很好用,速度快、體積小巧。但是它儲存的檔案卻是明文的。若不信可以用NotePad 打開資料庫檔案瞧瞧,裡面insert 的内容幾乎一覽無餘。這樣赤裸裸的展現自己,可不是我們的初衷。當然,如果你在嵌入式系統、智能手機上使用 sqlite,最好是不加密,因為這些系統運算能力有限,你做為一個新功能提供者,不能把使用者有限的運算能力全部花掉。

Sqlite為了速度而誕生。是以Sqlite本身不對資料庫加密,要知道,如果你選擇标準AES算法加密,那麼一定有接近50%的時間消耗在加解密算法上,甚至更多(性能主要取決于你算法編寫水準以及你是否能使用cpu提供的底層運算能力,比如MMX或sse系列指令可以大幅度提升運算速度)。

Sqlite免費版本是不提供加密功能的,當然你也可以選擇他們的收費版本,那你得支付2000塊錢,而且是USD。我這裡也不是說支付錢不好,如果隻為了資料庫加密就去支付2000塊,我覺得劃不來。因為下面我将要告訴你如何為免費的Sqlite擴充出加密子產品——自己動手擴充,這是Sqlite允許,也是它提倡的。

那麼,就讓我們一起開始為 sqlite3.c 檔案擴充出加密子產品。

1、 必要的宏

通過閱讀 Sqlite 代碼(當然沒有全部閱讀完,6萬多行代碼,沒有一行是我習慣的風格,我可沒那麼多眼神去看),我搞清楚了兩件事:

Sqlite是支援加密擴充的;

需要 #define 一個宏才能使用加密擴充。

這個宏就是  SQLITE_HAS_CODEC。

你在代碼最前面(也可以在 sqlite3.h 檔案第一行)定義:

[cpp] view plain copy

  1. #ifndef SQLITE_HAS_CODEC  
  2. #define SQLITE_HAS_CODEC  
  3. #endif  

如果你在代碼裡定義了此宏,但是還能夠正常編譯,那麼應該是操作沒有成功。因為你應該會被編譯器提示有一些函數無法連結才對。如果你用的是 VC 2003,你可以在“解決方案”裡右鍵點選你的工程,然後選“屬性”,找到“C/C++”,再找到“指令行”,在裡面手工添加“/D "SQLITE_HAS_CODEC"”。

定義了這個宏,一些被 Sqlite 故意屏蔽掉的代碼就被使用了。這些代碼就是加解密的接口。

嘗試編譯,vc會提示你有一些函數無法連結,因為找不到他們的實作。

如果你也用的是VC2003,那麼會得到下面的提示:

error LNK2019: 無法解析的外部符号 _sqlite3CodecGetKey ,該符号在函數 _attachFunc 中被引用

error LNK2019: 無法解析的外部符号 _sqlite3CodecAttach ,該符号在函數 _attachFunc 中被引用

error LNK2019: 無法解析的外部符号 _sqlite3_activate_see ,該符号在函數 _sqlite3Pragma 中被引用

error LNK2019: 無法解析的外部符号 _sqlite3_key ,該符号在函數 _sqlite3Pragma 中被引用

fatal error LNK1120: 4 個無法解析的外部指令

這是正常的,因為Sqlite隻留了接口而已,并沒有給出實作。

下面就讓我來實作這些接口。

2、 自己實作加解密接口函數

如果真要我從一份 www.sqlite.org 網上down下來的 sqlite3.c 檔案,直接摸索出這些接口的實作,我認為我還沒有這個能力。

好在網上還有一些代碼已經實作了這個功能。通過參照他們的代碼以及不斷編譯中vc給出的錯誤提示,最終我把整個接口整理出來。

實作這些預留接口不是那麼容易,要重頭說一次怎麼回事很困難。我把代碼都寫好了,直接把他們按我下面的說明拷貝到 sqlite3.c 檔案對應地方即可。我在下面也提供了sqlite3.c 檔案,可以直接參考或取下來使用。

這裡要說一點的是,我另外建立了兩個檔案:crypt.c和crypt.h。

其中crypt.h如此定義:

[cpp] view plain copy

  1. <pre name="code" class="cpp">#ifndef DCG_SQLITE_CRYPT_FUNC_  
  2. #define DCG_SQLITE_CRYPT_FUNC_  
  3. int My_Encrypt_Func( unsigned char * pData,unsigned int data_len, const char * key, unsigned int len_of_key );  
  4. int My_DeEncrypt_Func( unsigned char * pData,unsigned int data_len, const char * key, unsigned int len_of_key );  
  5. #endif  
  6. 其中的 crypt.c 如此定義:  
  7. #include "./crypt.h"  
  8. #include "memory.h"  
  9. int My_Encrypt_Func( unsigned char * pData,unsigned int data_len, const char * key, unsigned int len_of_key )  
  10. {  
  11. return 0;  
  12. }  
  13. int My_DeEncrypt_Func( unsigned char *pData, unsigned int data_len, const char * key, unsigned int len_of_key )  
  14. {  
  15. return 0;  
  16. }</pre>  
  17. <pre></pre>  
  18. <p></p>  
  19. <pre></pre>  
  20. <p></p>  
  21. <p>這個檔案很容易看,就兩函數,一個加密一個解密。傳進來的參數分别是待處理的資料、資料長度、密鑰、密鑰長度。</p>  
  22. <p>處理時直接把結果作用于 pData 指針指向的内容。</p>  
  23. <p>你需要定義自己的加解密過程,就改動這兩個函數,其它部分不用動。擴充起來很簡單。</p>  
  24. <p>這裡有個特點,data_len 一般總是 1024 位元組。正因為如此,你可以在你的算法裡使用一些特定長度的加密算法,比如AES要求被加密資料一定是128位(16位元組)長。這個1024不是碰巧,而是 Sqlite的頁定義是1024位元組,在sqlite3.c檔案裡有定義:</p>  
  25. <p></p>  
  26. <pre name="code" class="cpp"># define SQLITE_DEFAULT_PAGE_SIZE 1024</pre>  
  27. <p></p>  
  28. <p>你可以改動這個值,不過還是建議沒有必要不要去改它。</p>  
  29. <p>上面寫了兩個擴充函數,如何把擴充函數跟 Sqlite 挂接起來,這個過程說起來比較麻煩。我直接貼代碼。</p>  
  30. <p>分3個步驟。</p>  
  31. <p>首先,在 sqlite3.c 檔案頂部,添加下面内容:</p>  
  32. <p></p>  
  33. <pre name="code" class="cpp">#ifdef SQLITE_HAS_CODEC  
  34. #include "./crypt.h"  
  35. void sqlite3pager_free_codecarg(void*pArg);  
  36. #endif</pre>  
  37. <p></p>  
  38. <p>這個函數之是以要在 sqlite3.c 開頭聲明,是因為下面在 sqlite3.c 裡面某些函數裡要插入這個函數調用。是以要提前聲明。</p>  
  39. <p>其次,在sqlite3.c檔案裡搜尋“sqlite3PagerClose”函數,要找到它的實作代碼(而不是聲明代碼)。</p>  
  40. <p>實作代碼裡一開始是:</p>  
  41. <p></p>  
  42. <pre name="code" class="cpp">#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT  
  43.  ThreadData *pTsd = sqlite3ThreadData();  
  44.  assert( pPager );  
  45.  assert( pTsd && pTsd->nAlloc );  
  46. #endif</pre>  
  47. <p></p>  
  48. <p>需要在這部分後面緊接着插入:</p>  
  49. <p></p>  
  50. <pre name="code" class="cpp">#ifdef SQLITE_HAS_CODEC  
  51.  sqlite3pager_free_codecarg(pPager->pCodecArg);  
  52. #endif</pre>  
  53. <p></p>  
  54. <p>這裡要注意,sqlite3PagerClose 函數大概也是 3.3.17版本左右才改名的,以前版本裡是叫“sqlite3pager_close”。是以你在老版本sqlite代碼裡搜尋“sqlite3PagerClose”是搜不到的。</p>  
  55. <p>類似的還有“sqlite3pager_get”、“sqlite3pager_unref”、“sqlite3pager_write”、“sqlite3pager_pagecount”等都是老版本函數,它們在 pager.h 檔案裡定義。新版本對應函數是在 sqlite3.h 裡定義(因為都合并到 sqlite3.c和sqlite3.h兩檔案了)。是以,如果你在使用老版本的sqlite,先看看 pager.h 檔案,這些函數不是消失了,也不是新蹦出來的,而是老版本函數改名得到的。</p>  
  56. <p>最後,往sqlite3.c 檔案下找。找到最後一行:</p>  
  57. <p></p>  
  58. <p>在這一行後面,接上本文最下面的代碼段。</p>  
  59. <p>這些代碼很長,我不再解釋,直接接上去就得了。</p>  
  60. <p>唯一要提的是 DeriveKey 函數。這個函數是對密鑰的擴充。比如,你要求密鑰是128位,即是16位元組,但是如果使用者隻輸入 1個位元組呢?2個位元組呢?或輸入50個位元組呢?你得對密鑰進行擴充,使之符合16位元組的要求。</p>  
  61. <p>DeriveKey 函數就是做這個擴充的。有人把接收到的密鑰求md5,這也是一個辦法,因為md5運算結果固定16位元組,不論你有多少字元,最後就是16位元組。這是md5算法的特點。但是我不想用md5,因為還得為它添加包含一些 md5 的.c或.cpp檔案。我不想這麼做。我自己寫了一個算法來擴充密鑰,很簡單的算法。當然,你也可以使用你的擴充方法,也而可以使用 md5 算法。隻要修改DeriveKey 函數就可以了。</p>  
  62. <p>在 DeriveKey 函數裡,隻管申請空間構造所需要的密鑰,不需要釋放,因為在另一個函數裡有釋放過程,而那個函數會在資料庫關閉時被調用。參考我的 DeriveKey 函數來申請記憶體。</p>  
  63. <p>這裡我給出我已經修改好的 sqlite3.c 和 sqlite3.h 檔案。</p>  
  64. <p>如果太懶,就直接使用這兩個檔案,編譯肯定能通過,運作也正常。當然,你必須按我前面提的,建立 crypt.h 和 crypt.c 檔案,而且函數要按我前面定義的要求來做。</p>  
  65. <h2><a name="t20"></a>3、 加密使用方法:</h2>  
  66. <p>現在,你代碼已經有了加密功能。</p>  
  67. <p>你要把加密功能給用上,除了改 sqlite3.c 檔案、給你工程添加 SQLITE_HAS_CODEC 宏,還得修改你的資料庫調用函數。</p>  
  68. <p>前面提到過,要開始一個資料庫操作,必須先 sqlite3_open 。</p>  
  69. <p>加解密過程就在 sqlite3_open 後面操作。</p>  
  70. <p>假設你已經 sqlite3_open 成功了,緊接着寫下面的代碼:</p>  
  71. <p></p>  
  72. <pre name="code" class="cpp">     int i;  
  73. //添加、使用密碼       
  74.      i =  sqlite3_key( db,"dcg", 3 );  
  75.      //修改密碼  
  76.      i =  sqlite3_rekey( db,"dcg", 0 );</pre>  
  77. <p></p>  
  78. <p>用 sqlite3_key 函數來送出密碼。</p>  
  79. <p>第1個參數是 sqlite3 *類型變量,代表着用sqlite3_open 打開的資料庫(或建立資料庫)。</p>  
  80. <p>第2個參數是密鑰。</p>  
  81. <p>第3個參數是密鑰長度。</p>  
  82. <p>用 sqlite3_rekey 來修改密碼。參數含義同 sqlite3_key。</p>  
  83. <p>實際上,你可以在sqlite3_open函數之後,到 sqlite3_close 函數之前任意位置調用 sqlite3_key 來設定密碼。</p>  
  84. <p>但是如果你沒有設定密碼,而資料庫之前是有密碼的,那麼你做任何操作都會得到一個傳回值:SQLITE_NOTADB,并且得到錯誤提示:“file is encrypted or is not a database”。</p>  
  85. <p>隻有當你用 sqlite3_key 設定了正确的密碼,資料庫才會正常工作。</p>  
  86. <p>如果你要修改密碼,前提是你必須先 sqlite3_open 打開資料庫成功,然後 sqlite3_key 設定密鑰成功,之後才能用 sqlite3_rekey 來修改密碼。(源碼網整理:<a href="http://www.codepub.com/">www.codepub.com</a>)</p>  
  87. <p>如果資料庫有密碼,但你沒有用 sqlite3_key 設定密碼,那麼當你嘗試用 sqlite3_rekey 來修改密碼時會得到 SQLITE_NOTADB 傳回值。</p>  
  88. <p>如果你需要清空密碼,可以使用:</p>  
  89. <p></p>  
  90. <pre name="code" class="cpp">//修改密碼  
  91.      i =  sqlite3_rekey( db, NULL, 0 );</pre>  
  92. <p></p>  
  93. <p>來完成密碼清空功能。</p>  
  94. <h2><a name="t21"></a>4、 sqlite3.c 最後添加代碼段</h2>  
  95. <p></p>  
  96. <pre name="code" class="cpp">  
  97. #ifdef SQLITE_HAS_CODEC  
  98. #define CRYPT_OFFSET 8  
  99. typedef struct _CryptBlock  
  100. {  
  101. BYTE*    ReadKey;     // 讀資料庫和寫入事務的密鑰  
  102. BYTE*    WriteKey;    // 寫入資料庫的密鑰  
  103. int      PageSize;    // 頁的大小  
  104. BYTE*    Data;  
  105. } CryptBlock, *LPCryptBlock;  
  106. #ifndef DB_KEY_LENGTH_BYTE           
  107. #define DB_KEY_LENGTH_BYTE   16     
  108. #endif  
  109. #ifndef DB_KEY_PADDING               
  110. #define DB_KEY_PADDING       0x33    
  111. #endif  
  112. void sqlite3CodecGetKey(sqlite3* db, intnDB, void** Key, int* nKey)  
  113. {  
  114. return ;  
  115. }  
  116. int sqlite3CodecAttach(sqlite3 *db, intnDb, const void *pKey, int nKeyLen);  
  117. void sqlite3_activate_see(const char* right)  
  118. {    
  119. return;  
  120. }  
  121. int sqlite3_key(sqlite3 *db, const void*pKey, int nKey);  
  122. int sqlite3_rekey(sqlite3 *db, const void*pKey, int nKey);  
  123. // 從使用者提供的緩沖區中得到一個加密密鑰  
  124. // 使用者提供的密鑰可能位數上滿足不了要求,使用這個函數來完成密鑰擴充  
  125. static unsigned char * DeriveKey(const void*pKey, int nKeyLen);  
  126. //建立或更新一個頁的加密算法索引.此函數會申請緩沖區.  
  127. static LPCryptBlockCreateCryptBlock(unsigned char* hKey, Pager *pager, LPCryptBlock pExisting);  
  128. //加密/解密函數, 被pager調用  
  129. void * sqlite3Codec(void *pArg, unsignedchar *data, Pgno nPageNum, int nMode);  
  130. //設定密碼函數  
  131. int __stdcall sqlite3_key_interop(sqlite3*db, const void *pKey, int nKeySize);  
  132. // 修改密碼函數  
  133. int __stdcall sqlite3_rekey_interop(sqlite3*db, const void *pKey, int nKeySize);  
  134. //銷毀一個加密塊及相關的緩沖區,密鑰.  
  135. static void DestroyCryptBlock(LPCryptBlockpBlock);  
  136. static void * sqlite3pager_get_codecarg(Pager*pPager);  
  137. void sqlite3pager_set_codec(Pager*pPager,void *(*xCodec)(void*,void*,Pgno,int),void *pCodecArg    );  
  138. //加密/解密函數, 被pager調用  
  139. void * sqlite3Codec(void *pArg, unsignedchar *data, Pgno nPageNum, int nMode)  
  140. {  
  141. LPCryptBlock pBlock = (LPCryptBlock)pArg;  
  142. unsigned int dwPageSize = 0;  
  143. if (!pBlock) return data;  
  144. // 確定pager的頁長度和加密塊的頁長度相等.如果改變,就需要調整.  
  145. if (nMode != 2)  
  146. {  
  147.      PgHdr *pageHeader;  
  148.      pageHeader =DATA_TO_PGHDR(data);  
  149.      if(pageHeader->pPager->pageSize != pBlock->PageSize)  
  150.      {  
  151.           CreateCryptBlock(0,pageHeader->pPager, pBlock);  
  152.      }  
  153. }  
  154. switch(nMode)  
  155. {  
  156. case 0: // Undo a "case 7" journal file encryption  
  157. case 2: //重載一個頁  
  158. case 3: //載入一個頁  
  159.      if (!pBlock->ReadKey)break;  
  160.      dwPageSize =pBlock->PageSize;  
  161.      My_DeEncrypt_Func(data,dwPageSize, pBlock->ReadKey, DB_KEY_LENGTH_BYTE );   
  162.      break;  
  163. case 6: //加密一個主資料庫檔案的頁  
  164.      if (!pBlock->WriteKey)break;  
  165.      memcpy(pBlock->Data +CRYPT_OFFSET, data, pBlock->PageSize);  
  166.      data = pBlock->Data +CRYPT_OFFSET;  
  167.      dwPageSize =pBlock->PageSize;  
  168.      My_Encrypt_Func(data ,dwPageSize, pBlock->WriteKey, DB_KEY_LENGTH_BYTE );   
  169.      break;  
  170. case 7: //加密事務檔案的頁  
  171.      if (!pBlock->ReadKey)break;  
  172.      memcpy(pBlock->Data +CRYPT_OFFSET, data, pBlock->PageSize);  
  173.      data = pBlock->Data +CRYPT_OFFSET;  
  174.      dwPageSize =pBlock->PageSize;  
  175.      My_Encrypt_Func( data,dwPageSize, pBlock->ReadKey, DB_KEY_LENGTH_BYTE );   
  176.      break;  
  177. }  
  178. return data;  
  179. }  
  180. //銷毀一個加密塊及相關的緩沖區,密鑰.  
  181. static void DestroyCryptBlock(LPCryptBlock pBlock)  
  182. {  
  183. //銷毀讀密鑰.  
  184. if (pBlock->ReadKey){  
  185.     sqliteFree(pBlock->ReadKey);  
  186. }  
  187. //如果寫密鑰存在并且不等于讀密鑰,也銷毀.  
  188. if (pBlock->WriteKey && pBlock->WriteKey !=pBlock->ReadKey){  
  189.     sqliteFree(pBlock->WriteKey);  
  190. }  
  191. if(pBlock->Data){  
  192.     sqliteFree(pBlock->Data);  
  193. }  
  194. //釋放加密塊.  
  195. sqliteFree(pBlock);  
  196.           }   
  197. static void *sqlite3pager_get_codecarg(Pager *pPager)  
  198. {  
  199. return(pPager->xCodec) ? pPager->pCodecArg: NULL;  
  200. }  
  201. // 從使用者提供的緩沖區中得到一個加密密鑰  
  202. static unsigned char * DeriveKey(const void*pKey, int nKeyLen)  
  203. {  
  204. unsigned char *  hKey = NULL;  
  205. int j;  
  206. if( pKey == NULL || nKeyLen == 0 )  
  207. {  
  208.     return NULL;  
  209. }  
  210. hKey = sqliteMalloc( DB_KEY_LENGTH_BYTE + 1);  
  211. if( hKey == NULL )  
  212. {  
  213.      return NULL;  
  214. }  
  215. hKey[ DB_KEY_LENGTH_BYTE ] = 0;  
  216. if( nKeyLen < DB_KEY_LENGTH_BYTE )  
  217. {  
  218.     memcpy( hKey, pKey, nKeyLen ); //先拷貝得到密鑰前面的部分  
  219.     j = DB_KEY_LENGTH_BYTE - nKeyLen;  
  220.     //補充密鑰後面的部分  
  221.     memset(  hKey + nKeyLen,  DB_KEY_PADDING, j  );  
  222. }  
  223. else  
  224. { //密鑰位數已經足夠,直接把密鑰取過來  
  225.     memcpy(  hKey, pKey,DB_KEY_LENGTH_BYTE );  
  226. }  
  227. return hKey;  
  228. }  
  229. //建立或更新一個頁的加密算法索引.此函數會申請緩沖區.  
  230. static LPCryptBlockCreateCryptBlock(unsigned char* hKey, Pager *pager, LPCryptBlock pExisting)  
  231. {  
  232. LPCryptBlock pBlock;  
  233. if (!pExisting) //建立新加密塊  
  234. {  
  235.     pBlock = sqliteMalloc(sizeof(CryptBlock));  
  236.     memset(pBlock, 0, sizeof(CryptBlock));  
  237.     pBlock->ReadKey = hKey;  
  238.     pBlock->WriteKey = hKey;  
  239.     pBlock->PageSize = pager->pageSize;  
  240.     pBlock->Data = (unsigned char*)sqliteMalloc(pBlock->PageSize +CRYPT_OFFSET);  
  241. }  
  242. else //更新存在的加密塊  
  243. {  
  244.     pBlock = pExisting;  
  245.     if ( pBlock->PageSize != pager->pageSize &&!pBlock->Data){  
  246.          sqliteFree(pBlock->Data);  
  247.          pBlock->PageSize = pager->pageSize;  
  248.          pBlock->Data = (unsigned char*)sqliteMalloc(pBlock->PageSize +CRYPT_OFFSET);  
  249.     }  
  250. }  
  251. memset(pBlock->Data, 0,pBlock->PageSize + CRYPT_OFFSET);  
  252. return pBlock;  
  253. }  
  254. void sqlite3pager_set_codec(Pager *pPager,void *(*xCodec)(void*,void*,Pgno,int), void *pCodecArg )  
  255. {  
  256. pPager->xCodec = xCodec;  
  257. pPager->pCodecArg = pCodecArg;  
  258. }  
  259. int sqlite3_key(sqlite3 *db, const void*pKey, int nKey)  
  260. {  
  261. returnsqlite3_key_interop(db, pKey, nKey);  
  262. }  
  263. int sqlite3_rekey(sqlite3 *db, const void*pKey, int nKey)  
  264. {  
  265. returnsqlite3_rekey_interop(db, pKey, nKey);  
  266. }  
  267. int sqlite3CodecAttach(sqlite3 *db, intnDb, const void *pKey, int nKeyLen)  
  268. {  
  269.    int rc = SQLITE_ERROR;  
  270.    unsigned char* hKey = 0;  
  271.    //如果沒有指定密匙,可能辨別用了主資料庫的加密或沒加密.  
  272.    if (!pKey || !nKeyLen)  
  273.     {  
  274.        if (!nDb)  
  275.        {  
  276.            return SQLITE_OK; //主資料庫, 沒有指定密鑰是以沒有加密.  
  277.        }  
  278.        else //附加資料庫,使用主資料庫的密鑰.  
  279.        {   //擷取主資料庫的加密塊并複制密鑰給附加資料庫使用  
  280.            LPCryptBlock pBlock = (LPCryptBlock)sqlite3pager_get_codecarg(sqlite3BtreePager(db->aDb[0].pBt));  
  281.            if (!pBlock) return SQLITE_OK; //主資料庫沒有加密  
  282.            if (!pBlock->ReadKey) return SQLITE_OK; //沒有加密  
  283.            memcpy(pBlock->ReadKey, &hKey, 16);  
  284.        }  
  285.     }  
  286.    else //使用者提供了密碼,從中建立密鑰.  
  287.     {  
  288.        hKey = DeriveKey(pKey, nKeyLen);  
  289.     }  
  290.    //建立一個新的加密塊,并将解碼器指向新的附加資料庫.  
  291.    if (hKey)  
  292.     {  
  293.        LPCryptBlock pBlock = CreateCryptBlock(hKey,sqlite3BtreePager(db->aDb[nDb].pBt), NULL);  
  294.        sqlite3pager_set_codec(sqlite3BtreePager(db->aDb[nDb].pBt), sqlite3Codec,pBlock);  
  295.        rc = SQLITE_OK;  
  296.     }  
  297.    return rc;  
  298. }  
  299. // Changes the encryption key for anexisting database.  
  300. int __stdcall sqlite3_rekey_interop(sqlite3*db, const void *pKey, int nKeySize)  
  301. {  
  302. Btree *pbt = db->aDb[0].pBt;  
  303. Pager *p = sqlite3BtreePager(pbt);  
  304. LPCryptBlock pBlock =(LPCryptBlock)sqlite3pager_get_codecarg(p);  
  305. unsigned char * hKey = DeriveKey(pKey,nKeySize);  
  306. int rc = SQLITE_ERROR;  
  307. if (!pBlock && !hKey) returnSQLITE_OK;  
  308. //重新加密一個資料庫,改變pager的寫密鑰, 讀密鑰依舊保留.  
  309. if (!pBlock) //加密一個未加密的資料庫  
  310. {  
  311.     pBlock = CreateCryptBlock(hKey, p, NULL);  
  312.     pBlock->ReadKey = 0; // 原始資料庫未加密  
  313.     sqlite3pager_set_codec(sqlite3BtreePager(pbt), sqlite3Codec, pBlock);  
  314. }  
  315. else // 改變已加密資料庫的寫密鑰  
  316. {  
  317.     pBlock->WriteKey = hKey;  
  318. }  
  319. // 開始一個事務  
  320. rc = sqlite3BtreeBeginTrans(pbt, 1);  
  321. if (!rc)  
  322. {   // 用新密鑰重寫所有的頁到資料庫。  
  323.     Pgno nPage = sqlite3PagerPagecount(p);  
  324.     Pgno nSkip = PAGER_MJ_PGNO(p);  
  325.     void *pPage;  
  326.     Pgno n;  
  327.     for(n = 1; rc == SQLITE_OK && n <= nPage; n ++)  
  328.     {  
  329.          if (n == nSkip) continue;  
  330.           rc = sqlite3PagerGet(p, n, &pPage);  
  331.          if(!rc)  
  332.          {  
  333.                rc = sqlite3PagerWrite(pPage);  
  334.                sqlite3PagerUnref(pPage);  
  335.          }  
  336.     }  
  337. }  
  338. // 如果成功,送出事務。  
  339. if (!rc)  
  340. {  
  341.     rc = sqlite3BtreeCommit(pbt);  
  342. }  
  343. // 如果失敗,復原。  
  344. if (rc)  
  345. {  
  346.     sqlite3BtreeRollback(pbt);  
  347. }  
  348. // 如果成功,銷毀先前的讀密鑰。并使讀密鑰等于目前的寫密鑰。  
  349. if (!rc)  
  350. {  
  351.     if (pBlock->ReadKey)  
  352.     {  
  353.          sqliteFree(pBlock->ReadKey);  
  354.     }  
  355.     pBlock->ReadKey = pBlock->WriteKey;  
  356. }  
  357. else// 如果失敗,銷毀目前的寫密鑰,并恢複為目前的讀密鑰。  
  358. {  
  359.     if (pBlock->WriteKey)  
  360.     {  
  361.          sqliteFree(pBlock->WriteKey);  
  362.     }  
  363.     pBlock->WriteKey = pBlock->ReadKey;  
  364. }  
  365. // 如果讀密鑰和寫密鑰皆為空,就不需要再對頁進行編解碼。  
  366. // 銷毀加密塊并移除頁的編解碼器  
  367. if (!pBlock->ReadKey &&!pBlock->WriteKey)  
  368. {  
  369.     sqlite3pager_set_codec(p, NULL, NULL);  
  370.     DestroyCryptBlock(pBlock);  
  371. }  
  372. return rc;  
  373. }  
  374. int __stdcall sqlite3_key_interop(sqlite3*db, const void *pKey, int nKeySize)  
  375. {  
  376.  return sqlite3CodecAttach(db, 0, pKey, nKeySize);  
  377. }  
  378. // 釋放與一個頁相關的加密塊  
  379. void sqlite3pager_free_codecarg(void *pArg)  
  380. {  
  381. if (pArg)  
  382.     DestroyCryptBlock((LPCryptBlock)pArg);  
  383. }  
  384. #endif //#ifdef SQLITE_HAS_CODEC</pre>  
  385. <p></p>  
  386. <pre></pre> 

繼續閱讀