使用 Qt 插件和 SQLCipher 实现 SQLite 数据库加密与解密

SQLite 作为一种轻量级的数据库,被广泛应用于各种桌面和移动应用中。然而,SQLite 本身并不支持数据加密,这时 SQLCipher 成为一个理想的解决方案。本文将详细介绍如何在 Qt 项目中集成 SQLCipher,实现 SQLite 数据库的加密与解密,包括创建加密数据库、插入数据以及查询数据的完整流程。

目录

  1. 简介
  2. 前置条件
  3. 项目配置
  4. 代码实现
  5. 常见问题与解决
  6. 总结

简介

SQLCipher 是一个开源的扩展,提供了透明的 AES-256 加密功能,使得 SQLite 数据库文件的内容能够被加密和解密。通过将 SQLCipher 与 Qt 结合使用,开发者可以轻松地在 Qt 应用中实现数据加密,确保敏感信息的安全性。

前置条件

在开始之前,请确保您的开发环境满足以下条件:

  • Qt 开发环境:建议使用 Qt 5 或 Qt 6。
  • SQLCipher 库:需要编译或安装 SQLCipher,并确保其与 Qt 兼容。
  • C++ 基础知识:了解基本的 C++ 和 Qt 编程。

项目配置

1. 安装 SQLCipher

首先,需要在系统中安装 SQLCipher。可以通过以下方式进行安装:

  • 使用包管理器

    • Windows :建议使用 vcpkg 安装 SQLCipher。

    • macOS

      复制代码
      brew install sqlcipher
    • Linux

      复制代码
      sudo apt-get install sqlcipher
  • 从源代码编译

    访问 SQLCipher GitHub 页面,按照说明进行编译。

2. 配置 Qt 项目

创建一个新的 Qt 控制台应用项目,或在现有项目中进行配置。

在项目的 .pro 文件中添加以下内容,以确保链接 SQLCipher 和 Qt SQL 模块:

复制代码
QT += core sql
CONFIG += console c++11

# 根据实际安装路径配置 SQLCipher 库
INCLUDEPATH += /usr/local/include
LIBS += -L/usr/local/lib -lsqlcipher

注意 :请根据您的系统和 SQLCipher 的安装路径调整 INCLUDEPATHLIBS

代码实现

以下是一个完整的 Qt 控制台应用程序示例,演示如何使用 SQLCipher 创建加密数据库、插入数据以及读取数据。

创建加密数据库并插入数据
复制代码
#include <QtSql>
#include <QCoreApplication>
#include <QStandardPaths>
#include <QDir>
#include <QFile>
#include <QDebug>

// 定义加密密钥
const QString DB_PASSWORD = "pass";

// 定义数据库文件名
const QString DB_FILENAME = "local.db";

// 定义表名和示例数据
const QString TABLE_NAME = "test";
const QList<QPair<int, QString>> SAMPLE_DATA = {
    {1, "AAA"},
    {2, "BBB"},
    {3, "CCC"},
    {4, "DDD"},
    {5, "EEE"},
    {6, "FFF"},
    {7, "GGG"}
};

// 函数声明
bool createAndInsertData(const QString &dbPath);
bool readData(const QString &dbPath);

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);

    // 获取数据库文件路径
    QString dir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
    QString dbPath = QDir(dir).absoluteFilePath(DB_FILENAME);
    qDebug() << "DB File Path is:" << dbPath;

    // 检查数据库文件是否存在
    bool dbExists = QFile::exists(dbPath);

    if (!dbExists) {
        qDebug() << "数据库不存在,正在创建并插入数据...";
        if (!createAndInsertData(dbPath)) {
            qDebug() << "创建数据库或插入数据失败。";
            return -1;
        }
        qDebug() << "数据库创建并成功插入数据。";
    } else {
        qDebug() << "数据库已存在,跳过创建步骤。";
    }

    // 读取数据
    qDebug() << "正在读取数据库中的数据...";
    if (!readData(dbPath)) {
        qDebug() << "读取数据库数据失败。";
        return -1;
    }

    qDebug() << "数据读取成功。";

    return 0;
}

/**
 * @brief 创建加密数据库并插入数据
 * @param dbPath 数据库文件路径
 * @return 成功返回 true,否则返回 false
 */
bool createAndInsertData(const QString &dbPath)
{
    // 添加 SQLITECIPHER 驱动
    QSqlDatabase db = QSqlDatabase::addDatabase("SQLITECIPHER", "create_connection");
    db.setDatabaseName(dbPath);
    db.setPassword(DB_PASSWORD);
    db.setConnectOptions("QSQLITE_USE_CIPHER=aes256cbc;"); // 使用 AES-256-CBC 加密

    if (!db.open()) {
        qDebug() << "打开数据库失败(创建):" << db.lastError().text();
        QSqlDatabase::removeDatabase("create_connection");
        return false;
    }

    qDebug() << "数据库已打开,用于创建和插入数据。";

    QSqlQuery query(db);

    // 创建表
    QString createTableSQL = QString("CREATE TABLE %1 (id INTEGER PRIMARY KEY, name TEXT);").arg(TABLE_NAME);
    if (!query.exec(createTableSQL)) {
        qDebug() << "创建表失败:" << query.lastError().text();
        db.close();
        QSqlDatabase::removeDatabase("create_connection");
        return false;
    }
    qDebug() << "表创建成功。";

    // 插入数据
    query.prepare(QString("INSERT INTO %1 (id, name) VALUES (:id, :name);").arg(TABLE_NAME));
    foreach (const QPair<int, QString> &entry, SAMPLE_DATA) {
        query.bindValue(":id", entry.first);
        query.bindValue(":name", entry.second);
        if (!query.exec()) {
            qDebug() << "插入数据失败 (" << entry.first << "," << entry.second << "):" << query.lastError().text();
            db.close();
            QSqlDatabase::removeDatabase("create_connection");
            return false;
        }
    }
    qDebug() << "数据插入成功。";

    db.close();
    QSqlDatabase::removeDatabase("create_connection");
    return true;
}
读取加密数据库并查询数据
复制代码
/**
 * @brief 读取加密数据库中的数据
 * @param dbPath 数据库文件路径
 * @return 成功返回 true,否则返回 false
 */
bool readData(const QString &dbPath)
{
    // 添加 SQLITECIPHER 驱动
    QSqlDatabase db = QSqlDatabase::addDatabase("SQLITECIPHER", "create_connection");
    db.setDatabaseName(dbPath);
    db.setPassword(DB_PASSWORD);
    db.setConnectOptions("QSQLITE_USE_CIPHER=aes256cbc;"); // 使用 AES-256-CBC 加密

    if (!db.open()) {
        qDebug() << "打开数据库失败(读取):" << db.lastError().text();
        QSqlDatabase::removeDatabase("read_connection");
        return false;
    }

    qDebug() << "数据库已打开,用于读取数据。";

    QSqlQuery query(db);

    // 验证 SQLCipher 版本(可选)
    if (query.exec("PRAGMA cipher_version;")) {
        if (query.next()) {
            QString cipher_version = query.value(0).toString();
            qDebug() << "SQLCipher 版本:" << cipher_version;
        } else {
            qDebug() << "无法获取 SQLCipher 版本。";
        }
    } else {
        qDebug() << "执行 PRAGMA cipher_version 失败:" << query.lastError().text();
    }

    // 查询数据
    QString selectSQL = QString("SELECT id, name FROM %1 ORDER BY id;").arg(TABLE_NAME);
    if (!query.exec(selectSQL)) {
        qDebug() << "执行 SELECT 查询失败:" << query.lastError().text();
        db.close();
        QSqlDatabase::removeDatabase("read_connection");
        return false;
    }

    // 读取并输出数据
    while (query.next()) {
        int id = query.value(0).toInt();
        QString name = query.value(1).toString();
        qDebug() << id << ":" << name;
    }

    db.close();
    QSqlDatabase::removeDatabase("read_connection");
    return true;
}
完整代码汇总

将上述两个函数和 main 函数合并,即可得到一个完整的示例程序:

复制代码
#include <QtSql>
#include <QCoreApplication>
#include <QStandardPaths>
#include <QDir>
#include <QFile>
#include <QDebug>

// 定义加密密钥
const QString DB_PASSWORD = "pass";

// 定义数据库文件名
const QString DB_FILENAME = "local.db";

// 定义表名和示例数据
const QString TABLE_NAME = "test";
const QList<QPair<int, QString>> SAMPLE_DATA = {
    {1, "AAA"},
    {2, "BBB"},
    {3, "CCC"},
    {4, "DDD"},
    {5, "EEE"},
    {6, "FFF"},
    {7, "GGG"}
};

// 函数声明
bool createAndInsertData(const QString &dbPath);
bool readData(const QString &dbPath);

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);

    // 获取数据库文件路径
    QString dir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
    QString dbPath = QDir(dir).absoluteFilePath(DB_FILENAME);
    qDebug() << "DB File Path is:" << dbPath;

    // 检查数据库文件是否存在
    bool dbExists = QFile::exists(dbPath);

    if (!dbExists) {
        qDebug() << "数据库不存在,正在创建并插入数据...";
        if (!createAndInsertData(dbPath)) {
            qDebug() << "创建数据库或插入数据失败。";
            return -1;
        }
        qDebug() << "数据库创建并成功插入数据。";
    } else {
        qDebug() << "数据库已存在,跳过创建步骤。";
    }

    // 读取数据
    qDebug() << "正在读取数据库中的数据...";
    if (!readData(dbPath)) {
        qDebug() << "读取数据库数据失败。";
        return -1;
    }

    qDebug() << "数据读取成功。";

    return 0;
}

/**
 * @brief 创建加密数据库并插入数据
 * @param dbPath 数据库文件路径
 * @return 成功返回 true,否则返回 false
 */
bool createAndInsertData(const QString &dbPath)
{
    // 添加 SQLITECIPHER 驱动
    QSqlDatabase db = QSqlDatabase::addDatabase("SQLITECIPHER", "create_connection");
    db.setDatabaseName(dbPath);
    db.setPassword(DB_PASSWORD);
    db.setConnectOptions("QSQLITE_USE_CIPHER=aes256cbc;"); // 使用 AES-256-CBC 加密

    if (!db.open()) {
        qDebug() << "打开数据库失败(创建):" << db.lastError().text();
        QSqlDatabase::removeDatabase("create_connection");
        return false;
    }

    qDebug() << "数据库已打开,用于创建和插入数据。";

    QSqlQuery query(db);

    // 创建表
    QString createTableSQL = QString("CREATE TABLE %1 (id INTEGER PRIMARY KEY, name TEXT);").arg(TABLE_NAME);
    if (!query.exec(createTableSQL)) {
        qDebug() << "创建表失败:" << query.lastError().text();
        db.close();
        QSqlDatabase::removeDatabase("create_connection");
        return false;
    }
    qDebug() << "表创建成功。";

    // 插入数据
    query.prepare(QString("INSERT INTO %1 (id, name) VALUES (:id, :name);").arg(TABLE_NAME));
    foreach (const QPair<int, QString> &entry, SAMPLE_DATA) {
        query.bindValue(":id", entry.first);
        query.bindValue(":name", entry.second);
        if (!query.exec()) {
            qDebug() << "插入数据失败 (" << entry.first << "," << entry.second << "):" << query.lastError().text();
            db.close();
            QSqlDatabase::removeDatabase("create_connection");
            return false;
        }
    }
    qDebug() << "数据插入成功。";

    db.close();
    QSqlDatabase::removeDatabase("create_connection");
    return true;
}

/**
 * @brief 读取加密数据库中的数据
 * @param dbPath 数据库文件路径
 * @return 成功返回 true,否则返回 false
 */
bool readData(const QString &dbPath)
{
    // 添加 SQLITECIPHER 驱动
    QSqlDatabase db = QSqlDatabase::addDatabase("SQLITECIPHER", "create_connection");
    db.setDatabaseName(dbPath);
    db.setPassword(DB_PASSWORD);
    db.setConnectOptions("QSQLITE_USE_CIPHER=aes256cbc;"); // 使用 AES-256-CBC 加密

    if (!db.open()) {
        qDebug() << "打开数据库失败(读取):" << db.lastError().text();
        QSqlDatabase::removeDatabase("read_connection");
        return false;
    }

    qDebug() << "数据库已打开,用于读取数据。";

    QSqlQuery query(db);

    // 验证 SQLCipher 版本(可选)
    if (query.exec("PRAGMA cipher_version;")) {
        if (query.next()) {
            QString cipher_version = query.value(0).toString();
            qDebug() << "SQLCipher 版本:" << cipher_version;
        } else {
            qDebug() << "无法获取 SQLCipher 版本。";
        }
    } else {
        qDebug() << "执行 PRAGMA cipher_version 失败:" << query.lastError().text();
    }

    // 查询数据
    QString selectSQL = QString("SELECT id, name FROM %1 ORDER BY id;").arg(TABLE_NAME);
    if (!query.exec(selectSQL)) {
        qDebug() << "执行 SELECT 查询失败:" << query.lastError().text();
        db.close();
        QSqlDatabase::removeDatabase("read_connection");
        return false;
    }

    // 读取并输出数据
    while (query.next()) {
        int id = query.value(0).toInt();
        QString name = query.value(1).toString();
        qDebug() << id << ":" << name;
    }

    db.close();
    QSqlDatabase::removeDatabase("read_connection");
    return true;
}
运行结果

假设 local.db 文件之前不存在,运行程序后将输出如下内容:

复制代码
DB File Path is: "C:/Users/用户名/Documents/local.db"
数据库不存在,正在创建并插入数据...
数据库已打开,用于创建和插入数据。
表创建成功。
数据插入成功。
数据库创建并成功插入数据。
正在读取数据库中的数据...
数据库已打开,用于读取数据。
SQLCipher 版本: "4.5.0"
1 : "AAA"
2 : "BBB"
3 : "CCC"
4 : "DDD"
5 : "EEE"
6 : "FFF"
7 : "GGG"
数据读取成功。

常见问题与解决

1. 数据库打开失败,显示"file is not a database"

原因:解密密钥不正确或加密参数不匹配。

解决方法

  • 确保在打开数据库时使用的密码与创建时一致。
  • 确保加密算法和参数(如 QSQLITE_USE_CIPHER)一致。
  • 检查 SQLCipher 插件是否正确加载。
2. 无法加载 SQLITECIPHER 驱动

原因:驱动未正确编译或路径配置错误。

解决方法

  • 确保 SQLCipher 驱动已正确编译并与 Qt 版本兼容。
  • 检查驱动插件路径是否在 Qt 的插件搜索路径中。
  • 使用 qDebug() << QSqlDatabase::drivers(); 查看可用驱动,确认 SQLITECIPHER 是否存在。
3. 插入或查询数据失败

原因:表未正确创建、SQL 语句有误或加密设置不当。

解决方法

  • 检查表名和字段是否正确。
  • 使用 SQL 工具(如 sqlcipher 命令行工具)验证数据库内容。
  • 确认 SQL 语句的语法正确。

总结

在实际应用中,建议进一步优化密码管理机制,避免将密码硬编码在代码中,可以考虑使用更安全的存储方式。此外,根据具体需求,您还可以探索 SQLCipher 提供的更多高级功能,如动态更改密码、密钥派生等。

如果在集成过程中遇到任何问题,欢迎参考 SQLCipher 的官方文档或社区资源,以获得更多支持。

参考

带有加密功能的 SQLite Qt 插件(v1.0)
QtCipherSqlitePlugin插件使用 (2)
GitHub - devbean/QtCipherSqlitePlugin

相关推荐
2501_9336707910 分钟前
2026秋招数据分析岗备考路线:SQL、BI、项目与面试题拆解
数据库
躺不平的理查德1 小时前
SQL Lite 备忘录2
sqlite
我要见SA姐13 小时前
告别 Copilot?Codex 本地化部署指南
运维·数据库·机器学习·oracle·回归
xcLeigh3 小时前
聊聊国产化替换:好用数据迁移工具KDMS怎么帮咱们搞定评估难
数据库·sql·数据迁移·kes·kdms
Elastic 中国社区官方博客4 小时前
列式存储并不等同于列式数据库。Columnar 模式为 Elasticsearch 带来了什么
大数据·运维·数据库·elasticsearch·搜索引擎
我要见SA姐14 小时前
用 Claude Code 重构遗留系统:从评估到落地的完整实践指南
数据库·ide·vscode·oracle·编辑器
Quz5 小时前
QML 常见的四种导航布局
qt
Nturmoils6 小时前
一份 KDMS 评估报告,怎样排出迁移先后顺序
数据库
这个DBA有点耶6 小时前
异构数据集成怎么做?5 种同步方案对比 + 金融级 CDC 实战解析
数据库·oracle·架构
独泪了无痕6 小时前
SQL函数实战:GREATEST与LEAST的技巧
数据库·sql·mysql