使用 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

相关推荐
秋已杰爱3 小时前
Qt显示一个hello world
开发语言·qt
暴怒的代码3 小时前
云原生监控篇——全链路可观测性与AIOps实战
数据库·sql·mysql
小张-森林人5 小时前
Oracle 字符串分割革命:正则表达式与 Lateral Join 的优雅解法
数据库·oracle·正则表达式
m0_748250937 小时前
SQL Server Management Studio的使用
数据库·oracle·性能优化
车载诊断技术7 小时前
人工智能AI在汽车设计领域的应用探索
数据库·人工智能·网络协议·架构·汽车·是诊断功能配置的核心
没有十八岁7 小时前
云创智城YunCharge 新能源二轮、四轮充电解决方案(云快充、万马爱充、中电联、OCPP1.6J等多个私有单车、汽车充电协议)之新能源充电行业系统说明书
java·数据库·spring·汽车
爱搞技术的猫猫8 小时前
微店商品详情API接口实战指南:从零实现商品数据自动化获取
大数据·linux·运维·数据库·自动化
若云止水9 小时前
Ubuntu 下 nginx-1.24.0 源码分析 - ngx_init_cycle 函数 - 详解(1)
数据库·nginx·ubuntu
WannaRunning10 小时前
MySQL中的共享锁和排他锁
数据库·mysql
lusklusklusk10 小时前
Sqlserver安全篇之_启用TLS即配置SQL Server 数据库引擎以加密连接
数据库·安全·sqlserver