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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/886503.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vmvare虚拟机centos 忘记超级管理员密码怎么办?

vmvare虚拟机centos 忘记超级管理员密码怎么办?如何重置密码呢? 一、前置操作 重启vmvare虚拟机的过程中,长按住Shift键 选择第一个的时候,按下按键 e 进入编辑状态。 然后就会进入到类似这个界面中。 在下方界面 添加 init=/bin/sh,然后按下Ctrl+x进行保存退出。 init=/bi…

Unity开发绘画板——04.笔刷大小调节

笔刷大小调节 上面的代码中其实我们已经提供了笔刷大小的字段&#xff0c;即brushSize&#xff0c;现在只需要将该字段和界面中的Slider绑定即可&#xff0c;Slider值的范围我们设置为1~20 代码中只需要做如下改动&#xff1a; public Slider brushSizeSlider; //控制笔刷大…

【hot100-java】【寻找两个正序数组的中位数】

二分查找篇 如果使用之前的两个指针分别遍历再合并的话就已经超过时间复杂度了。。 class Solution {public double findMedianSortedArrays(int[] nums1, int[] nums2) {int mnums1.length;int nnums2.length;if(m>n){return findMedianSortedArrays(nums2,nums1);}int tot…

Web3Auth 如何工作?

Web3Auth 用作钱包基础设施&#xff0c;为去中心化应用程序 (dApp) 和区块链钱包提供增强的灵活性和安全性。在本文档中&#xff0c;我们将探索 Web3Auth 的功能&#xff0c;展示它如何为每个用户和应用程序生成唯一的加密密钥提供程序。 高级架构 Web3Auth SDK 完全存在于用…

Cpp::STL—string类的模拟实现(12)

文章目录 前言一、string类各函数接口总览二、默认构造函数string(const char* str "");string(const string& str);传统拷贝写法现代拷贝写法 string& operator(const string& str);传统赋值构造现代赋值构造 ~string(); 三、迭代器相关函数begin &…

JS基础练习|动态创建多个input,并且支持删除功能

效果图 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>动态生成输入框并保存数据</title>&…

誉天Linux云计算课程学什么?为什么保障就业?

一个IT工程师相当于干了哪些职业? 其中置顶回答生动而形象地描绘道&#xff1a; 一个IT工程师宛如一个超级多面手&#xff0c;相当于——加班狂程序员测试工程师实施工程师网络工程师电工装卸工搬运工超人。 此中酸甜苦辣咸&#xff0c;相信很多小伙伴们都深有体会。除了典…

【微服务】注册中心 - Eureka(day3)

CAP理论 P是分区容错性。简单来说&#xff0c;分区容错性表示分布式服务中一个节点挂掉了&#xff0c;并不影响其他节点对外提供服务。也就是一台服务器出错了&#xff0c;仍然可以对外进行响应&#xff0c;不会因为某一台服务器出错而导致所有的请求都无法响应。综上所述&…

uniapp修改uni-ui组件样式(对微信小程序/H5有效,vue3)

寻找要修改的样式 使用开发者工具找到具体要修改的class类名 修改 <style lang"scss">//.nav为上一级的class.nav::v-deep .uni-navbar--border {border-bottom-style: none !important;} </style>完整代码 <template><view><uni-na…

Kafka学习笔记(三)Kafka分区和副本机制、自定义分区、消费者指定分区

文章目录 前言7 分区和副本机制7.1 生产者分区写入策略7.1.1 轮询分区策略7.1.2 随机分区策略7.1.3 按key分区分配策略7.1.4 自定义分区策略7.1.4.1 实现Partitioner接口7.1.4.2 实现分区逻辑7.1.4.3 配置使用自定义分区器7.1.4.4 分区测试 7.2 消费者分区分配策略7.2.1 RangeA…

【华为HCIP实战课程三】动态路由OSPF的NBMA环境建立邻居及排错,网络工程师

一、NBMA环境下的OSPF邻居建立问题 上节我们介绍了NBMA环境下OSPF邻居建立需要手动指定邻居,因为NBMA环境是不支持广播/组播的 上一节AR1的配置: ospf 1 peer 10.1.1.4 //手动指定邻居的接口地址,而不是RID peer 10.1.1.5 area 0.0.0.0 手动指定OSPF邻居后抓包查看OSP…

C语言的内存结构

在电脑中C语言编译器也像其他软件一样占用一块内存空间。 为了更好的利用好这块内存&#xff0c;C语言将他们分为 在C语言中&#xff0c;变量定义的位置不一样&#xff0c;那么在内存中所处的位置也是不一样的。&#xff08;变量在函数内部是存储在栈里&#xff0c;而在函数外部…

SPI通信——FPGA学习笔记14

一、简介 SPI(Serial Periphera Interface&#xff0c;串行外围设备接口)通讯协议&#xff0c;是 Motorola 公司提出的一种同步串行接口技术&#xff0c;是一种高速、全双工、同步通信总线&#xff0c;在芯片中只占用四根管脚用来控制及数据传输&#xff0c;广泛用于 EEPROM、F…

基于STM32的智能家居灯光控制系统设计

引言 本项目将使用STM32微控制器实现一个智能家居灯光控制系统&#xff0c;能够通过按键、遥控器或无线模块远程控制家庭照明。该项目展示了如何结合STM32的外设功能&#xff0c;实现对灯光的智能化控制&#xff0c;提升家居生活的便利性和节能效果。 环境准备 1. 硬件设备 …

C--编译和链接见解

欢迎各位看官&#xff01;如果您觉得这篇文章对您有帮助的话 欢迎您分享给更多人哦 感谢大家的点赞收藏评论 感谢各位看官的支持&#xff01;&#xff01;&#xff01; 一&#xff1a;翻译环境和运行环境 在ANSIIC的任何一种实现中&#xff0c;存在两个不同的环境1&#xff0c;…

戴尔电脑怎么开启vt虚拟化_戴尔电脑新旧机型开启vt虚拟化教程

最近使用戴尔电脑的小伙伴们问我&#xff0c;戴尔电脑怎么开启vt虚拟。大多数可以在Bios中开启vt虚拟化技术&#xff0c;当CPU支持VT-x虚拟化技术&#xff0c;有些电脑会自动开启VT-x虚拟化技术功能。而大部分的电脑则需要在Bios Setup界面中&#xff0c;手动进行设置&#xff…

SpringCloud入门(九)Feign实战应用和性能优化

一、Feign实战应用 Feign的客户端与服务提供者的controller代码非常相似&#xff1a; 有没有一种办法简化这种重复的代码编写呢&#xff1f; 方式一&#xff1a;继承 优点&#xff1a; 简单。实现了代码共享。 缺点&#xff1a;服务提供方、服务消费方紧耦合。参数列表中的注解…

vscode安装及c++配置编译

1、VScode下载 VS Code官网下载地址&#xff1a;Visual Studio Code - Code Editing. Redefined。 2、安装中文插件 搜索chinese&#xff0c;点击install下载安装中文插件。 3、VS Code配置C/C开发环境 3.1、MinGW-w64下载 VS Code是一个高级的编辑器&#xff0c;只能用来写代…

Coggle数据科学 | Kaggle赛题解析:CMI 体育损伤指数预测

本文来源公众号“Coggle数据科学”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;Kaggle赛题解析&#xff1a;CMI 体育损伤指数预测 赛题名称&#xff1a;Child Mind Institute — Problematic Internet Use 赛题类型&#xff1a…

Windows 环境上安装 NASM 和 YASM 教程

NASM 和 YASM NASM NASM&#xff08;Netwide Assembler&#xff09;是一个开源的、可移植的汇编器&#xff0c;它支持多种平台和操作系统。它可以用来编写16位、32位以及64位的代码&#xff0c;并且支持多种输出格式&#xff0c;包括ELF、COFF、OMF、a.out、Mach-O等。NASM使用…