SQLite 加密擴充

說明文件
登入

如何編譯和使用 SEE

1.0 簡介

本文件描述 SQLite 的 SQLite 加密擴充 (SEE)。SEE 允許 SQLite 讀寫加密的資料庫檔案。所有資料庫內容,包括中繼資料,都會被加密,因此對於外部觀察者來說,資料庫看起來像是白噪音。

包含 SEE 的 SQLite 版本也能讀寫使用公開領域版本的 SQLite 建立的普通資料庫檔案。但公開版本的 SQLite 將無法讀寫加密的資料庫檔案。實際上,任何已知軟體的任何版本都無法在不知道加密金鑰的情況下存取加密的資料庫檔案。

SEE 實際上是一組採用各種加密演算法的擴充。目前支援以下加密演算法:

2.0 授權

核心 SQLite 函式庫屬於公共領域。但是,讀寫加密資料庫檔案所需的擴充是授權軟體。您只有在擁有授權的情況下才能看到此軟體。

您的授權是永久的。您已支付一次性費用,可讓您永久使用和修改軟體。您可以將任意數量的軟體副本發送給您的客戶,只要您確保只發送已編譯的二進位檔(您不能分發原始碼),並且您的客戶不能為了其他目的製作軟體的額外副本。

您可以建立多個使用此軟體的產品,只要所有產品都由同一個團隊開發和維護。就本段而言,「團隊」是指一個工作單位,其中每個人都知道彼此的姓名。如果您在一家大型公司中,此產品由多個團隊使用,則每個團隊都應取得各自的獨立授權或企業授權。

3.0 如何編譯

您的應用程式將 SEE 視為一個大型的 C 語言程式碼檔案,它是 SQLite 整合檔 的直接替代品。SEE 原始程式碼檔案的工作方式和編譯方式與公共領域的「sqlite3.c」整合檔相同。如果您已經使用公共領域的「sqlite3.c」檔案建置您的應用程式,那麼要使用 SEE 建置,您只需將公共領域的「sqlite3.c」替換為啟用 SEE 的「sqlite3.c」檔案,然後重新編譯即可。

有九個不同的啟用 SEE 的「sqlite3.c」檔案可供選擇:

  1. sqlite3-see-aes256-openssl.c
  2. sqlite3-see-aes256-cryptoapi.c
  3. sqlite3-see-aes256-ofb.c
  4. sqlite3-see-cccrypt.c
  5. sqlite3-see-aes128-ofb.c
  6. sqlite3-see-aes128-ccm.c
  7. sqlite3-see.c
  8. sqlite3-rc4.c
  9. sqlite3-xor.c

將 SEE 新增到應用程式中的建議程序是將其中一個檔案複製到您的應用程式原始碼樹狀目錄中,將其重新命名為「sqlite3.c」並覆寫公共領域的「sqlite3.c」原始碼檔案,然後重新編譯。重新編譯後,您的應用程式應該會繼續像以前一樣正常工作,讀寫普通的未加密 SQLite 資料庫。一旦您重新編譯並確認一切正常後,請返回並將啟用加密的 PRAGMA(如下所述)新增到您的應用程式碼中,這樣就完成了。

3.1 SEE 發行版本中的原始碼檔案

以下是實作 SQLite 加密擴充所使用的原始碼檔案:

sqlite3-see-aes256-openssl.c

此檔案可以直接取代公有領域的 "sqlite3.c" 檔案,透過連結外部 OpenSSL 函式庫,新增使用 AES-256 OFB 模式加密的功能。

sqlite3-see-cryptoapi.c

此檔案可以直接取代公有領域的 "sqlite3.c" 原始碼檔案,透過使用 Windows 上的 CryptoAPI 原生介面,新增使用 AES256 OFB 模式加密的功能。

sqlite3-see-aes256-ofb.c

此檔案可以直接取代公有領域的 "sqlite3.c" 檔案,透過使用內建的 Rijndaal 參考實作,新增使用 AES-256 OFB 模式加密的功能。

sqlite3-see-cccrypt.c

此檔案可以直接取代公有領域的 "sqlite3.c" 檔案,新增使用外部 CCCrypt 加密的 AES-128 和 AES-256 加密演算法(OFB 模式)的功能。CCCrypt 是 MacOS 和 iOS 上的預設加密函式庫,因此建議在這些平台上使用此 SEE 實作。

see-ccrypt.c 模組通常只執行 AES128 加密。然而,當使用 -DCCCRYPT256 編譯 see-cccrypt 時,若且唯若金鑰長度正好為 32 位元組,它才會使用 AES256。

sqlite3-see-aes128-ofb.c

此檔案可以直接取代公有領域的 "sqlite3.c" 檔案。此替代方案新增使用 Rijndaal 參考實作的 AES-128 加密演算法(OFB 模式)的功能。

sqlite3-see-aes128-ccm.c

此檔案可以直接取代公有領域的 "sqlite3.c" 檔案。此替代方案新增使用 AES-128 加密演算法(CCM 模式)的功能。CCM 模式包含訊息驗證碼,除了機密性之外,還提供身份驗證。此模式使用 Rijndaal 參考實作的 AES。

sqlite3-see-rc4.c

此檔案可以直接取代公有領域的 "sqlite3.c" 檔案,新增使用 RC4 演算法加密的功能。RC4 已不再被視為安全。您不應該使用此 SEE 實作。它僅提供用於歷史相容性。

sqlite3-see.c

此檔案可以直接取代公有領域的 "sqlite3.c" 原始碼檔案,新增使用 RC4、AES128-OFB 或 AES256-OFB 演算法進行加密的功能。使用的演算法基於加密金鑰的前綴。如果金鑰資料以 "rc4:" 開頭,則使用 RC4 加密。如果金鑰資料以 "aes128:" 開頭,則使用 AES128-OFB。如果金鑰資料以 "aes256:" 開頭,則使用 AES256-OFB。如果金鑰中沒有出現這三個有效的前綴,則預設演算法為 AES128-OFB。在將金鑰傳遞給加密演算法之前,會移除有效的字首。

sqlite3-see-xor.c

此檔案可以直接取代公有領域的 "sqlite3.c" 原始碼檔案,新增僅將資料庫與重複的加密金鑰副本進行 XOR 的虛擬加密功能。此 SEE 變體不提供真正的加密。它僅供示範使用,或用於希望在不實際加密資料庫檔案的情況下混淆它的情況,可能是由於法律限制。

sqlite3.c

包含新增加密所需之額外掛鉤的普通、未加密 SQLite 副本。上述其他加密 SQLite 模組都是此檔案的副本,並預先添加和附加了額外的程式碼來執行加密工作。此檔案僅供參考,可能對開發沒有用處。

sqlite3.h

此檔案包含 SQLite 的介面定義。其他連結 SQLite 的程式將需要此檔案,您也需要此檔案才能編譯 CLI,但您不需要此檔案來編譯 SQLite 本身。

shell.c

此檔案包含「CLI」(命令列介面程式,名稱為 "sqlite3.exe")的原始碼,您可以使用它來存取和控制 SQLite 資料庫檔案。此檔案與公有領域版 SQLite 附帶的 "shell.c" 檔案不同。此 shell.c 已增強以使用加密擴充功能。

3.2 建置與編譯 SEE 程式碼

要將 SEE 編譯成靜態函式庫,請選擇適當的 "sqlite3-see-*.c" 原始程式碼檔案(包含您所需的演算法和實作),然後像編譯一般的公用領域 "sqlite3.c" 原始程式碼檔案一樣編譯該檔案。在 Unix 系統上,指令序列大致如下:

    gcc -c sqlite3-see-aes256-ofb.c
    ar a sqlite3-see-aes256-ofb.a sqlite3-see-aes256-ofb.o

在 Windows 上,指令更像是這樣:

    cl -c sqlite3-see-aes256-ofb.c
    lib /out:libsee.lib sqlite3-see-aes256-ofb.obj

3.3 建置共用函式庫或 DLL

我們建議您將 SQLite 靜態連結到您的應用程式。但是,如果您必須將 SQLite 作為單獨的 DLL 或共用函式庫使用,則可以在 Linux 上編譯如下:

    gcc -fPIC -shared -o libsee.so sqlite3-see-aes256-ofb.c

或者在 Windows 上:

    cl -DSQLITE_API=__declspec(dllexport) sqlite3-see-aes256-ofb.c /link /dll /out:libsee.dll

3.4 建置命令列 shell 程式

要編譯 CLI,只需將 shell.c 原始程式碼檔案連同上面準備的靜態函式庫或原始程式碼檔案一起提交給您的 C 編譯器。在 Linux 上的典型指令是:

    gcc -o sqlite3 shell.c sqlite3-see-aes256-ofb.c -lpthread -ldl

在 Mac 上:

    gcc -o sqlite3 shell.c sqlite3-see-aes256-ofb.c -ldl

在使用 MSVC 的 Windows 上:

    cl /Fesqlite3.exe shell.c sqlite3-see-aes256-ofb.c

為了在建置 CLI 時提升效能,請考慮新增 -DSQLITE_THREADSAFE=0 選項。CLI 是單執行緒的,如果 SQLite 不必使用其互斥鎖,則執行速度會更快。

SEE 也可以針對 Windows Phone 8UWP 10Android 進行建置。

4.0 命令列用法

CLI 與公用領域 SQLite 使用的 CLI 相同,但增強了對加密的支援。有一些新的命令列選項(「-key」、「-hexkey」和「-textkey」)用於指定加密金鑰。範例:

    sqlite3 -key secret database.db
    sqlite3 -hexkey 736563726574 database.db
    sqlite3 -textkey secret2 database.db

如果省略金鑰或金鑰是空字串,則不執行加密。

有三種不同的金鑰格式。第一種格式 (-key) 採用金鑰字串並重複它,直到它超過底層演算法的金鑰位元組數(AES128 為 16 位元組,AES256 為 32 位元組,RC4 為 256 位元組)。然後將結果截斷為演算法金鑰大小。這種方法限制了金鑰空間,因為它不允許金鑰中出現 0x00 位元組。第二種格式 (-hexkey) 接受十六進位制金鑰,因此可以表示任何金鑰。如果提供的金鑰太長,則會被截斷。如果提供的金鑰太短,則會重複它以填滿到演算法金鑰長度。第三種格式 (-textkey) 會對輸入金鑰素材計算強雜湊,並使用該雜湊作為演算法的金鑰。建議新應用程式使用 -textkey 格式。

4.1 變更加密金鑰

啟用 SEE 的 CLI 還包含新的點命令「.rekey」、「.hex-rekey」和「.text-rekey」用於變更加密金鑰。

   .rekey OLD NEW NEW
   .hex-rekey OLD NEW NEW
   .text-rekey OLD NEW NEW

第一個參數始終是舊密碼,其格式與啟動命令列工具時提供給「-key」、「-hexkey」或「-textkey」選項的格式完全相同。如果資料庫先前未加密,請使用空字串 "" 作為金鑰。第二個和第三個參數是新的加密金鑰。您必須輸入兩次新金鑰以檢查是否有輸入錯誤 - 除非兩個新金鑰實例相同,否則不會執行重新設定金鑰。要加密先前未加密的資料庫,請執行以下操作:

   .rekey "" new-key new-key
   VACUUM

啟用加密並不需要 VACUUM 步驟,但強烈建議執行此步驟。VACUUM 命令可確保資料庫檔案的每一頁都具有安全的隨機數。僅當首次加密現有的非空資料庫檔案時才需要 VACUUM。

要解密資料庫,請執行以下操作:

   .rekey old-key "" ""

.rekey 命令僅適用於文字金鑰。要重新設定包含二進位制金鑰的資料庫的金鑰,請改用 .hex-rekey 命令。.hex-rekey 命令的工作方式與 .rekey 類似,只是新金鑰以十六進位制而不是文字輸入。.text-rekey 命令會計算 NEW 參數的雜湊,並使用該雜湊作為加密金鑰。

5.0 C 介面

如果您將 SQLite 加密擴充功能部署為 DLL 或共享函式庫,則必須先透過呼叫以下函式來啟用該函式庫:

   sqlite3_activate_see("7bb07b8d471d642e");

參數是您的產品啟用金鑰。啟用金鑰以純文字形式存在於原始程式碼中,因此您可以清楚地看到它的內容。啟用金鑰的目的是防止您的客戶提取 SQLite 函式庫並將其與您的應用程式分開使用。如果不知道只有您才知道的啟用金鑰,您的使用者將無法使用加密功能。

如果您無法呼叫 sqlite3_activate_see() 的 C 介面(可能是因為您透過包裝層存取 SQLite),則也可以使用 PRAGMA 來啟用加密功能:

  PRAGMA activate_extensions='see-7bb07b8d471d642e';

使用 sqlite3_open() API 開啟加密的資料庫或您想要重新設定金鑰的任何資料庫。開啟後,立即使用 sqlite3_key_v2() 指定金鑰:

   int sqlite3_key_v2(
      sqlite3 *db,         /* The connection from sqlite3_open() */
      const char *zDbName, /* Which ATTACHed database to key */
      const void *pKey,    /* The key */
      int nKey             /* Number of bytes in the key */
   );

如果 pKey 參數為 NULL 或 nKey 為 0,則假設資料庫未加密。nKey 參數可以任意大,但只會使用前 256 個位元組 (RC4) 或 16 個位元組 (AES128) 或 32 個位元組 (AES256)。在 SEE 3.15.0 及更高版本中,如果 nKey 為負數,則假設 pKey 是一個以零結尾的通行片語字串。在這種情況下,通行片語會被雜湊,並且雜湊值會被用作 AES 演算法的金鑰。通行片語本身會被用作 RC4 的金鑰。

注意:在 3.15.0 版中新增了 nKey<0 時使用通行片語雜湊的功能。如果您在 3.15.0 之前的任何 SEE 版本中使用 nKey<0,加密將會被靜默停用,就像您設定了 nKey=0 一樣。

see-ccrypt.c 模組預設使用 AES128 加密。但是,如果 see-ccrypt.c 使用 -DCCCRYPT256 編譯,並且以 nKey==32 呼叫 sqlite3_key_v2() 介面,則會改用 AES256 加密。

如果您指定了錯誤的金鑰,您不會立即收到錯誤訊息。但是,當您第一次嘗試存取資料庫時,您會收到 SQLITE_NOTADB 錯誤,訊息為「檔案已加密或不是資料庫」。

zDbName 參數指定哪個 ATTACH 的資料庫應該取得金鑰。通常這是「main」。您可以傳遞 NULL 指標作為「main」的別名。除非您有充分的理由,否則最好傳遞 NULL 指標作為 zDbName 參數。

您可以使用 sqlite3_rekey() 常式更改資料庫的金鑰:

   int sqlite3_rekey_v2(
      sqlite *db,                    /* Database to be rekeyed */
      const char *zDbName,           /* Which ATTACHed database to rekey */
      const void *pKey, int nKey     /* The new key */
   );

NULL 金鑰會解密資料庫。

重新設定金鑰需要讀取資料庫檔案的每一頁,解密,使用新金鑰重新加密,然後再次寫出。因此,在較大的資料庫上重新設定金鑰可能需要很長時間。

大多數 SEE 變體允許您加密使用 SQLite 公開版本建立的現有資料庫。當使用 see-aes128-ccm.c 中的驗證版本的加密擴充功能時,這是不可行的。如果您確實加密了使用 SQLite 公開版本建立的資料庫,則不會使用 nonce,並且該檔案容易受到選擇明文攻擊。如果您在第一次建立資料庫時,在 sqlite3_open() 之後立即呼叫 sqlite3_key_v2(),則會在資料庫中保留 nonce 的空間,並且加密會更強。如果您不想立即加密,請仍然使用 NULL 金鑰呼叫 sqlite3_key_v2(),即使最初沒有進行加密,也會在資料庫中保留 nonce 的空間。

SQLite 函式庫的公開版本可以使用 NULL 金鑰讀取和寫入加密的資料庫。只有在金鑰非 NULL 時才需要加密擴充功能。

6.0 使用「key」PRAGMA

除了呼叫 sqlite3_key_v2() 來設定資料庫的解密金鑰之外,您也可以使用 pragma 指令。

    PRAGMA key='your-secret-key';

您必須在嘗試與資料庫進行任何其他互動之前呼叫此 pragma。 key pragma 僅適用於字串金鑰。如果您使用二進位金鑰,請改用 hexkey pragma。

    PRAGMA hexkey='796f75722d7365637265742d6b6579';

對於相當於 --textkey 選項的功能,其中文字密碼會經過雜湊運算以計算實際的加密金鑰,請使用:

    PRAGMA textkey='your-secret-key';

使用 rekey、hexrekey 或 textrekey pragmas 來更改金鑰。例如,要將金鑰更改為 'demo2',請使用以下其中一種:

    PRAGMA rekey='demo2';
    PRAGMA hexrekey='64656d6f32';
    PRAGMA textrekey='long-passphrase';

透過使用這些 pragmas,就不需要直接呼叫 sqlite3_key_v2() 或 sqlite3_rekey_v2() 介面。這表示 SEE 可以與不了解這些介面的語言包裝器一起使用。

「key」、「hexkey」和「textkey」PRAGMA 陳述式預期使用與命令列 shell 的「-key」、「-hexkey」和「-textkey」參數相同的金鑰字串。

如果 key PRAGMAs 成功將加密金鑰載入 SEE,則會傳回字串「ok」。如果您在不支援加密的系統上呼叫這些 pragmas 之一,或者金鑰載入操作因任何原因失敗,則不會傳回任何內容。請注意,載入任何金鑰時都會傳回「ok」字串,不一定是正確的金鑰。判斷金鑰是否正確的唯一方法是嘗試從資料庫檔案讀取。錯誤的金鑰將導致讀取錯誤。

7.0 使用 ATTACH 命令

附加資料庫的金鑰是使用 ATTACH 陳述式結尾的 KEY 子句指定的。像這樣:

    ATTACH DATABASE 'file2.db' AS two KEY 'xyzzy';

如果省略 KEY 子句,則會使用目前主資料庫正在使用的相同金鑰。如果附加的資料庫未加密,請指定空字串作為金鑰。 KEY 關鍵字的參數可以是 BLOB 常數。例如:

    ATTACH DATABASE 'file2.db' AS two KEY X'78797a7a79';

在 ATTACH 陳述式上使用文字作為 KEY,預期會使用與提供給命令列 shell 的「-key」選項相同的金鑰。 KEY 的 BLOB 值表示使用與提供給命令列 shell 的「-hexkey」選項相同的金鑰。沒有機制可以在 ATTACH 陳述式上指定要雜湊的密碼。如果您使用雜湊金鑰,則必須自行計算雜湊並將其作為 BLOB 提供。

8.0 金鑰素材

加密擴充功能實際使用的金鑰素材量取決於您使用的 SEE 變體。使用 see-rc4.c 時,會使用金鑰的前 256 個位元組。使用 see-aes128-ofb 和 see-aes128-ccm 變體時,會使用金鑰的前 16 個位元組。使用 see-aes256-ofb 時,會使用金鑰的前 32 個位元組。

如果您指定的金鑰短於最大金鑰長度,則會根據需要重複金鑰素材以完成金鑰。如果您指定的金鑰長於最大金鑰長度,則會忽略多餘的金鑰素材。

對於「-textkey」選項,最多會使用 RC4 雜湊密碼的 256 個位元組,雜湊值會成為加密金鑰。請注意,在此情況下,RC4 演算法被用作雜湊函數,而不是加密函數,因此 RC4 是加密強度較弱的演算法的事實無關緊要。

8.1 使用金鑰前綴選擇加密演算法

對於「sqlite3-see.c」SEE 變體,金鑰可以以一個前綴開頭來指定要使用的演算法。前綴必須*恰好*是「rc4:」、「aes128:」或「aes256:」其中之一。前綴不會作為傳送到加密演算法的金鑰的一部分使用。因此,真正的金鑰應該從前綴後的第一個位元組開始。請注意以下重要細節:

9.0 隨機數的重要性

如果資料庫的每個頁面上都有隨機的隨機數值,則加密會更加安全。如果沒有隨機數,則可以使用選擇明文攻擊破解加密。純粹主義者會(正確地)認為沒有隨機數的加密是薄弱的。

資料庫每個頁面上隨機數的位元組數由資料庫檔案的第 20 個位元組決定。在由公共領域版本的 SQLite 建立的資料庫中,此值預設設定為零。您可以透過使用啟用 SEE 的 SQLite 版本執行 VACUUM 命令來將此位元組更改為正值。

您可以使用一般 sqlite3.exe 命令列 shell 程式中的「.dbinfo」命令來檢查資料庫的隨機數大小。「.dbinfo」命令的輸出看起來像這樣

database page size:  4096
write format:        1
read format:         1
reserved bytes:      12    ← Nonce size
file change counter: 3504448735
database page count: 14190
freelist page count: 0
schema cookie:       107
schema format:       4
default cache size:  0
autovacuum top root: 0
incremental vacuum:  0
text encoding:       1 (utf8)
user version:        0
application id:      0
software version:    3008008
number of tables:    53
number of indexes:   53
number of triggers:  0
number of views:     0
schema size:         14257

資料庫的第 16 到 23 個位元組未加密。因此,您始終可以透過查看第 20 個位元組來檢查正在使用多少隨機數,即使是在加密的資料庫檔案上也是如此。建議使用加密的任何產品都檢查此位元組,以確保其設定為 4、12 或 32,而不是 0。

隨機數大小可以增加,但不能減少,可以使用以下方法之一:

從命令列 shell

    .filectrl reserve_bytes 32
    VACUUM;

使用 C API

    int nNonce = 32;
    sqlite3_file_control(db, "main", SQLITE_FCNTL_RESERVE_BYTES, &nNonce);
    sqlite3_exec(db, "VACUUM;", 0, 0, 0);

10.0 安全性檢查清單

在應用程式中使用 SEE 時,建議您至少執行以下測試,仔細檢查所有內容是否都已正確實作,以及您是否獲得了強加密:

  1. 使用啟用 SEE 的 CLI 執行「sqlite3 $DATABASE .dbinfo」命令(新增適當的 -key、-hexkey 或 -textkey 參數),並確認您的加密資料庫檔案包含隨機數。隨機數應至少為 12 個位元組。
  1. 使用啟用 SEE 的 CLI 讀取加密的資料庫,但將提供的金鑰的最後一個字元更改一個字元值。確認像這樣對金鑰末尾的微小更改會使資料庫無法讀取。錯誤訊息應為「檔案不是資料庫」。使用金鑰的多個變體重複此測試。確認只有在金鑰完全正確的情況下才能存取資料庫。
  1. 嘗試壓縮加密的資料庫檔案,並確認該檔案不可壓縮。換句話說,對加密的資料庫執行像「zip」或「gzip」這樣的程式,並確認壓縮不會使檔案大小減少超過幾個位元組。

限制

  1. TEMP 表格未加密。
  1. 記憶體中(「:memory:」)的資料庫未加密。
  1. 資料庫檔案的第 16 到 23 個位元組包含未加密的標頭資訊。

11.0 SEE 的運作方式

每個頁面分別加密。加密金鑰是頁碼、隨機隨機數(如果有的話)和資料庫金鑰的組合。資料在主資料庫和回滾日誌或 WAL 檔案中都經過加密,但在記憶體中時未加密。這表示如果攻擊者能夠查看您的程式使用的記憶體,她將能夠看到未加密的資料。

隨機數值會因回滾而更改。

see-aes128-ccm.c 變體使用 CCM 模式的 AES 加密,每頁使用 16 位元組的隨機 Nonce 和 16 位元組的訊息鑑別碼 (MAC)。因此,使用 crypto3ccm.c 時,每個資料庫頁面會有 32 位元組用於加密和驗證的額外開銷。 因此,使用 crypto3ccm.c 建立的資料庫檔案可能會稍微大一些。此外,由於每次修改頁面時都會計算 MAC,並且在讀取頁面時會驗證 MAC,因此 crypto3ccm.c 通常會稍微慢一些。這就是驗證的代價。