使用 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("QSQLITE", "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("QSQLITE", "read_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("QSQLITE", "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("QSQLITE", "read_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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值