使用 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 的安装路径调整 INCLUDEPATH 和 LIBS。

代码实现

以下是一个完整的 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

相关推荐
草莓熊Lotso1 小时前
【C++ 基于微服务的即时通讯系统】etcd + brpc 从安装到服务注册发现全链路实战
网络·数据库·c++·redis·微服务·etcd
Lethehong6 小时前
Oracle 加个字段,目标库就报错?KFS 用“先拆后组“打通异构 DDL
数据库·oracle
Web极客码7 小时前
锁定真正的磁盘卡顿:我用 OpenCode 配合 eBPF 诊断 NVMe 队列深度延迟与 I/O 写放大
数据库·oracle
cspttty7 小时前
2027用户研究求职准备:应用统计学如何补齐研究设计与业务表达
大数据·数据库
把我格子衫拿来8 小时前
VTK 9.5 与 Qt 6 联合开发:从嵌入三维窗口到可维护的桌面架构
c++·python·qt·vtk·三维可视化
roman_日积跬步-终至千里8 小时前
【数据Agent论文·查询与语义|05】Spider:为什么换一个数据库,模型就不会查了?
数据库·数据分析
猫咪很可爱9 小时前
Oracle RAC 启动失败排查:私网通信与内核参数优化实战
运维·数据库·oracle
XLYcmy9 小时前
《数字经济赋能城市低碳治理的路径研究检索报告》审阅与修改指导意见——基于完整检索报告体例的完整性诊断、学术规范核查与方法论纠偏 上
数据库·ai·chatgpt·文心一言·检索·知网·豆包
泡海椒9 小时前
JQuick-SQL 快速开始:注册内存表并执行查询
数据库·windows·sql
ss27310 小时前
PG实战笔记 | 09 PostgreSQL psql客户端基本操作
数据库·笔记·postgresql