SQLCipher 是一个在 SQLite 基础上扩展的数据库,它通过 256-bit AES 加密算法对数据库文件进行透明的、实时的加密,这意味着所有数据在写入磁盘前都会被加密,从磁盘读取时会被自动解密,对于 C 语言开发者来说,使用 SQLCipher 的核心流程与使用标准 SQLite 几乎完全相同,唯一的关键区别在于初始化时提供一个加密密钥。

准备工作:获取 SQLCipher C 库
你需要为你的平台(Windows, macOS, Linux)编译或下载 SQLCipher 的 C 库和头文件。
推荐方法:使用 vcpkg (跨平台包管理器)
这是最简单、最推荐的方法,可以自动处理依赖和编译。
-
安装 vcpkg:
(图片来源网络,侵删)git clone https://github.com/microsoft/vcpkg.git cd vcpkg ./bootstrap-vcpkg.sh # 或 bootstrap-vcpkg.bat on Windows
-
安装 SQLCipher:
# 在 vcpkg 目录下执行 ./vcpkg install sqlcipher
这个命令会自动下载 SQLCipher 的源码,并使用你系统的 OpenSSL 库进行编译,生成所需的库文件(如
sqlcipher.lib,libsqlcipher.a)和头文件。 -
集成到你的项目:
-
Visual Studio (Windows): 在你的项目属性中,将
vcpkg\installed\<your-triplet>(x64-windows) 添加到 "VC++ 目录" -> "包含目录" 和 "库目录",然后在 "链接器" -> "输入" -> "附加依赖项" 中添加sqlcipher.lib。
(图片来源网络,侵删) -
CMake (推荐): 这是最佳实践,在你的
CMakeLists.txt中添加以下内容:# 设置 vcpkg 工具链文件 set(CMAKE_TOOLCHAIN_FILE "path/to/vcpkg/scripts/buildsystems/vcpkg.cmake" CACHE STRING "Vcpkg toolchain file") # 查找 sqlcipher 包 find_package(sqlcipher CONFIG REQUIRED) # 你的可执行文件 add_executable(your_app main.c) # 链接 sqlcipher target_link_libraries(your_app PRIVATE sqlcipher)
-
核心概念:加密流程
SQLCipher 的加密流程非常简单:
- 打开数据库: 使用
sqlite3_open()函数,就像打开普通 SQLite 数据库一样。 - 设置密钥: 在数据库打开后,立即调用
sqlite3_key()函数,传入你的加密密钥,这个密钥用于派生出加密所需的密钥。 - 执行操作: 之后,所有标准的 SQLite 操作(
sqlite3_exec,sqlite3_prepare_v2,sqlite3_step等)都会在加密/解密层自动进行,你无需关心数据是明文还是密文。 - 关闭数据库: 使用
sqlite3_close()关闭数据库。
注意: 如果数据库文件不存在,sqlite3_open() 会创建一个新的数据库文件,此时调用 sqlite3_key() 会用这个密钥来初始化新数据库的加密,如果数据库文件已存在且已加密,sqlite3_open() 会打开失败,除非你提供了正确的密钥。
C 语言代码示例
下面是一个完整的 C 语言示例,演示了如何创建一个加密数据库、创建表、插入数据、查询数据,以及如何使用错误的密钥来验证加密效果。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 包含 SQLite/SQLCipher 头文件
#include <sqlite3.h>
// 回调函数,用于处理 sqlite3_exec 的查询结果
int callback(void *NotUsed, int argc, char **argv, char **azColName) {
int i;
for (i = 0; i < argc; i++) {
printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
}
printf("\n");
return 0;
}
int main() {
sqlite3 *db;
char *zErrMsg = 0;
int rc;
// 1. 打开或创建数据库文件
// 如果文件不存在,sqlite3_open 会创建它
rc = sqlite3_open("my_secret.db", &db);
if (rc) {
fprintf(stderr, "无法打开数据库: %s\n", sqlite3_errmsg(db));
return 1;
}
printf("数据库已成功打开,\n");
// 2. 设置加密密钥
// 这是使用 SQLCipher 的关键步骤!
// 密钥可以是任何字符串,SQLCipher 会将其用于派生加密密钥。
const char *key = "my-super-secret-password-123";
rc = sqlite3_key(db, key, strlen(key));
if (rc != SQLITE_OK) {
fprintf(stderr, "设置密钥失败: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return 1;
}
printf("密钥已设置,\n");
// 3. 执行 SQL 语句 (创建表)
const char *sql = "CREATE TABLE IF NOT EXISTS users ("
"id INTEGER PRIMARY KEY,"
"name TEXT NOT NULL,"
"email TEXT NOT NULL);";
rc = sqlite3_exec(db, sql, callback, 0, &zErrMsg);
if (rc != SQLITE_OK) {
fprintf(stderr, "SQL 错误: %s\n", zErrMsg);
sqlite3_free(zErrMsg);
sqlite3_close(db);
return 1;
}
printf("表 'users' 创建成功,\n");
// 4. 插入数据
sql = "INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com');"
"INSERT INTO users (name, email) VALUES ('Bob', 'bob@example.com');";
rc = sqlite3_exec(db, sql, callback, 0, &zErrMsg);
if (rc != SQLITE_OK) {
fprintf(stderr, "插入数据失败: %s\n", zErrMsg);
sqlite3_free(zErrMsg);
} else {
printf("数据插入成功,\n");
}
// 5. 查询数据
sql = "SELECT id, name, email FROM users;";
printf("--- 查询结果 ---\n");
rc = sqlite3_exec(db, sql, callback, 0, &zErrMsg);
if (rc != SQLITE_OK) {
fprintf(stderr, "查询失败: %s\n", zErrMsg);
sqlite3_free(zErrMsg);
}
// 6. 关闭数据库
sqlite3_close(db);
printf("数据库已关闭,\n");
// --- 验证加密 ---
printf("\n--- 验证加密 ---\n");
printf("我们尝试用一个错误的密钥打开同一个数据库文件,\n");
sqlite3 *db_fail;
rc = sqlite3_open("my_secret.db", &db_fail);
if (rc) {
fprintf(stderr, "无法打开数据库: %s\n", sqlite3_errmsg(db_fail));
return 1;
}
// 使用错误的密钥
const char *wrong_key = "this-is-the-wrong-password";
rc = sqlite3_key(db_fail, wrong_key, strlen(wrong_key));
if (rc != SQLITE_OK) {
fprintf(stderr, "使用错误密钥打开数据库失败: %s\n", sqlite3_errmsg(db_fail));
// 这正是我们期望的结果!
} else {
// 如果成功,说明密钥错误或数据库未加密
printf("警告:使用错误密钥成功打开了数据库!\n");
}
sqlite3_close(db_fail);
return 0;
}
如何编译和运行
假设你已经使用 vcpkg 和 CMake 设置好了项目。
-
将上面的代码保存为
main.c。 -
创建
CMakeLists.txt文件,内容如下:cmake_minimum_required(VERSION 3.10) project(sqlcipher_c_example C) # 设置 C 标准 set(CMAKE_C_STANDARD 11) # 查找 sqlcipher 包 find_package(sqlcipher CONFIG REQUIRED) # 添加可执行文件 add_executable(sqlcipher_app main.c) # 链接 sqlcipher target_link_libraries(sqlcipher_app PRIVATE sqlcipher)
-
在包含
main.c和CMakeLists.txt的目录下,执行编译:# 创建构建目录 mkdir build cd build # 配置 CMake,它会自动找到 vcpkg cmake .. # 编译 make
-
运行生成的可执行文件:
./sqlcipher_app
预期输出:
数据库已成功打开。
密钥已设置。
表 'users' 创建成功。
数据插入成功。
--- 查询结果 ---
id = 1
name = Alice
email = alice@example.com
id = 2
name = Bob
email = bob@example.com
数据库已关闭。
--- 验证加密 ---
我们尝试用一个错误的密钥打开同一个数据库文件。
使用错误密钥打开数据库失败: file is not a database
重要注意事项和最佳实践
-
密钥管理: 这是最重要的一点。绝对不要将硬编码的密钥(如示例中的
"my-super-secret-password-123")直接放在你的源代码中,密钥应该从安全的地方获取,- 系统的密钥库(Keychain on macOS, Credential Manager on Windows)。
- 环境变量。
- 由用户在首次使用时输入。
- 从远程安全的服务器获取。
-
数据库文件权限: 即使数据库文件是加密的,也应确保文件系统的权限设置得当,在 Linux/macOS 上,设置数据库文件权限为
600(-rw-------),确保只有当前用户可以读写。 -
数据库重密钥: 如果需要更改数据库的密钥,SQLCipher 提供了
sqlite3_rekey()函数,它会在不重新写入整个文件的情况下,用新密钥重新加密数据库。// 假设 db 已经用旧密钥打开 const char *new_key = "my-new-super-secret-password"; rc = sqlite3_rekey(db, new_key, strlen(new_key)); if (rc != SQLITE_OK) { fprintf(stderr, "更改密钥失败: %s\n", sqlite3_errmsg(db)); } else { printf("密钥已成功更改,\n"); } -
错误处理: 示例中包含了基本的错误处理,在实际应用中,你应该对所有 SQLite API 调用进行错误检查,并根据错误类型采取适当的恢复或日志记录措施。
-
性能: 加密/解密会带来一定的性能开销,对于性能极其敏感的应用,需要进行基准测试,但对于大多数应用来说,这个开销是可以接受的。
通过以上步骤和注意事项,你就可以在你的 C 语言项目中成功集成和使用 SQLCipher 来保护你的敏感数据了。
