SQLCipher C语言如何实现数据加密与解密?

99ANYc3cd6
预计阅读时长 25 分钟
位置: 首页 C语言 正文

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

sqlcipher c语言
(图片来源网络,侵删)

准备工作:获取 SQLCipher C 库

你需要为你的平台(Windows, macOS, Linux)编译或下载 SQLCipher 的 C 库和头文件。

推荐方法:使用 vcpkg (跨平台包管理器)

这是最简单、最推荐的方法,可以自动处理依赖和编译。

  1. 安装 vcpkg:

    sqlcipher c语言
    (图片来源网络,侵删)
    git clone https://github.com/microsoft/vcpkg.git
    cd vcpkg
    ./bootstrap-vcpkg.sh # 或 bootstrap-vcpkg.bat on Windows
  2. 安装 SQLCipher:

    # 在 vcpkg 目录下执行
    ./vcpkg install sqlcipher

    这个命令会自动下载 SQLCipher 的源码,并使用你系统的 OpenSSL 库进行编译,生成所需的库文件(如 sqlcipher.lib, libsqlcipher.a)和头文件。

  3. 集成到你的项目:

    • Visual Studio (Windows): 在你的项目属性中,将 vcpkg\installed\<your-triplet> (x64-windows) 添加到 "VC++ 目录" -> "包含目录" 和 "库目录",然后在 "链接器" -> "输入" -> "附加依赖项" 中添加 sqlcipher.lib

      sqlcipher c语言
      (图片来源网络,侵删)
    • 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 的加密流程非常简单:

  1. 打开数据库: 使用 sqlite3_open() 函数,就像打开普通 SQLite 数据库一样。
  2. 设置密钥: 在数据库打开后,立即调用 sqlite3_key() 函数,传入你的加密密钥,这个密钥用于派生出加密所需的密钥。
  3. 执行操作: 之后,所有标准的 SQLite 操作(sqlite3_exec, sqlite3_prepare_v2, sqlite3_step 等)都会在加密/解密层自动进行,你无需关心数据是明文还是密文。
  4. 关闭数据库: 使用 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 设置好了项目。

  1. 将上面的代码保存为 main.c

  2. 创建 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)
  3. 在包含 main.cCMakeLists.txt 的目录下,执行编译:

    # 创建构建目录
    mkdir build
    cd build
    # 配置 CMake,它会自动找到 vcpkg
    cmake ..
    # 编译
    make
  4. 运行生成的可执行文件:

    ./sqlcipher_app

预期输出:

数据库已成功打开。
密钥已设置。
表 'users' 创建成功。
数据插入成功。
--- 查询结果 ---
id = 1
name = Alice
email = alice@example.com
id = 2
name = Bob
email = bob@example.com
数据库已关闭。
--- 验证加密 ---
我们尝试用一个错误的密钥打开同一个数据库文件。
使用错误密钥打开数据库失败: file is not a database

重要注意事项和最佳实践

  1. 密钥管理: 这是最重要的一点。绝对不要将硬编码的密钥(如示例中的 "my-super-secret-password-123")直接放在你的源代码中,密钥应该从安全的地方获取,

    • 系统的密钥库(Keychain on macOS, Credential Manager on Windows)。
    • 环境变量。
    • 由用户在首次使用时输入。
    • 从远程安全的服务器获取。
  2. 数据库文件权限: 即使数据库文件是加密的,也应确保文件系统的权限设置得当,在 Linux/macOS 上,设置数据库文件权限为 600 (-rw-------),确保只有当前用户可以读写。

  3. 数据库重密钥: 如果需要更改数据库的密钥,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");
    }
  4. 错误处理: 示例中包含了基本的错误处理,在实际应用中,你应该对所有 SQLite API 调用进行错误检查,并根据错误类型采取适当的恢复或日志记录措施。

  5. 性能: 加密/解密会带来一定的性能开销,对于性能极其敏感的应用,需要进行基准测试,但对于大多数应用来说,这个开销是可以接受的。

通过以上步骤和注意事项,你就可以在你的 C 语言项目中成功集成和使用 SQLCipher 来保护你的敏感数据了。

-- 展开阅读全文 --
头像
creatfile c语言
« 上一篇 03-02
织梦多图发布为何显示乱码?
下一篇 » 03-02

相关文章

取消
微信二维码
支付宝二维码

目录[+]