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

打印 上一主题 下一主题

主题 985|帖子 985|积分 2955

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

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


  • 简介
  • 前置条件
  • 项目配置
  • 代码实现

    • 创建加密数据库并插入数据
    • 读取加密数据库并查询数据

  • 常见题目与解决
  • 总结
简介

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
      1. brew install sqlcipher
      复制代码
    • Linux
      1. sudo apt-get install sqlcipher
      复制代码

  • 从源代码编译
    访问 SQLCipher GitHub 页面,按照说明举行编译。
2. 配置 Qt 项目

创建一个新的 Qt 控制台应用项目,或在现有项目中举行配置。
在项目标 .pro 文件中添加以下内容,以确保链接 SQLCipher 和 Qt SQL 模块:
  1. QT += core sql
  2. CONFIG += console c++11
  3. # 根据实际安装路径配置 SQLCipher 库
  4. INCLUDEPATH += /usr/local/include
  5. LIBS += -L/usr/local/lib -lsqlcipher
复制代码
  注意:请根据您的系统和 SQLCipher 的安装路径调整 INCLUDEPATH 和 LIBS。
  代码实现

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

  1. #include <QtSql>
  2. #include <QCoreApplication>
  3. #include <QStandardPaths>
  4. #include <QDir>
  5. #include <QFile>
  6. #include <QDebug>
  7. // 定义加密密钥
  8. const QString DB_PASSWORD = "pass";
  9. // 定义数据库文件名
  10. const QString DB_FILENAME = "local.db";
  11. // 定义表名和示例数据
  12. const QString TABLE_NAME = "test";
  13. const QList<QPair<int, QString>> SAMPLE_DATA = {
  14.     {1, "AAA"},
  15.     {2, "BBB"},
  16.     {3, "CCC"},
  17.     {4, "DDD"},
  18.     {5, "EEE"},
  19.     {6, "FFF"},
  20.     {7, "GGG"}
  21. };
  22. // 函数声明
  23. bool createAndInsertData(const QString &dbPath);
  24. bool readData(const QString &dbPath);
  25. int main(int argc, char *argv[])
  26. {
  27.     QCoreApplication app(argc, argv);
  28.     // 获取数据库文件路径
  29.     QString dir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
  30.     QString dbPath = QDir(dir).absoluteFilePath(DB_FILENAME);
  31.     qDebug() << "DB File Path is:" << dbPath;
  32.     // 检查数据库文件是否存在
  33.     bool dbExists = QFile::exists(dbPath);
  34.     if (!dbExists) {
  35.         qDebug() << "数据库不存在,正在创建并插入数据...";
  36.         if (!createAndInsertData(dbPath)) {
  37.             qDebug() << "创建数据库或插入数据失败。";
  38.             return -1;
  39.         }
  40.         qDebug() << "数据库创建并成功插入数据。";
  41.     } else {
  42.         qDebug() << "数据库已存在,跳过创建步骤。";
  43.     }
  44.     // 读取数据
  45.     qDebug() << "正在读取数据库中的数据...";
  46.     if (!readData(dbPath)) {
  47.         qDebug() << "读取数据库数据失败。";
  48.         return -1;
  49.     }
  50.     qDebug() << "数据读取成功。";
  51.     return 0;
  52. }
  53. /**
  54. * @brief 创建加密数据库并插入数据
  55. * @param dbPath 数据库文件路径
  56. * @return 成功返回 true,否则返回 false
  57. */
  58. bool createAndInsertData(const QString &dbPath)
  59. {
  60.     // 添加 SQLITECIPHER 驱动
  61.     QSqlDatabase db = QSqlDatabase::addDatabase("SQLITECIPHER", "create_connection");
  62.     db.setDatabaseName(dbPath);
  63.     db.setPassword(DB_PASSWORD);
  64.     db.setConnectOptions("QSQLITE_USE_CIPHER=aes256cbc;"); // 使用 AES-256-CBC 加密
  65.     if (!db.open()) {
  66.         qDebug() << "打开数据库失败(创建):" << db.lastError().text();
  67.         QSqlDatabase::removeDatabase("create_connection");
  68.         return false;
  69.     }
  70.     qDebug() << "数据库已打开,用于创建和插入数据。";
  71.     QSqlQuery query(db);
  72.     // 创建表
  73.     QString createTableSQL = QString("CREATE TABLE %1 (id INTEGER PRIMARY KEY, name TEXT);").arg(TABLE_NAME);
  74.     if (!query.exec(createTableSQL)) {
  75.         qDebug() << "创建表失败:" << query.lastError().text();
  76.         db.close();
  77.         QSqlDatabase::removeDatabase("create_connection");
  78.         return false;
  79.     }
  80.     qDebug() << "表创建成功。";
  81.     // 插入数据
  82.     query.prepare(QString("INSERT INTO %1 (id, name) VALUES (:id, :name);").arg(TABLE_NAME));
  83.     foreach (const QPair<int, QString> &entry, SAMPLE_DATA) {
  84.         query.bindValue(":id", entry.first);
  85.         query.bindValue(":name", entry.second);
  86.         if (!query.exec()) {
  87.             qDebug() << "插入数据失败 (" << entry.first << "," << entry.second << "):" << query.lastError().text();
  88.             db.close();
  89.             QSqlDatabase::removeDatabase("create_connection");
  90.             return false;
  91.         }
  92.     }
  93.     qDebug() << "数据插入成功。";
  94.     db.close();
  95.     QSqlDatabase::removeDatabase("create_connection");
  96.     return true;
  97. }
复制代码
读取加密数据库并查询数据

  1. /**
  2. * @brief 读取加密数据库中的数据
  3. * @param dbPath 数据库文件路径
  4. * @return 成功返回 true,否则返回 false
  5. */
  6. bool readData(const QString &dbPath)
  7. {
  8.     // 添加 SQLITECIPHER 驱动
  9.     QSqlDatabase db = QSqlDatabase::addDatabase("SQLITECIPHER", "create_connection");
  10.     db.setDatabaseName(dbPath);
  11.     db.setPassword(DB_PASSWORD);
  12.     db.setConnectOptions("QSQLITE_USE_CIPHER=aes256cbc;"); // 使用 AES-256-CBC 加密
  13.     if (!db.open()) {
  14.         qDebug() << "打开数据库失败(读取):" << db.lastError().text();
  15.         QSqlDatabase::removeDatabase("read_connection");
  16.         return false;
  17.     }
  18.     qDebug() << "数据库已打开,用于读取数据。";
  19.     QSqlQuery query(db);
  20.     // 验证 SQLCipher 版本(可选)
  21.     if (query.exec("PRAGMA cipher_version;")) {
  22.         if (query.next()) {
  23.             QString cipher_version = query.value(0).toString();
  24.             qDebug() << "SQLCipher 版本:" << cipher_version;
  25.         } else {
  26.             qDebug() << "无法获取 SQLCipher 版本。";
  27.         }
  28.     } else {
  29.         qDebug() << "执行 PRAGMA cipher_version 失败:" << query.lastError().text();
  30.     }
  31.     // 查询数据
  32.     QString selectSQL = QString("SELECT id, name FROM %1 ORDER BY id;").arg(TABLE_NAME);
  33.     if (!query.exec(selectSQL)) {
  34.         qDebug() << "执行 SELECT 查询失败:" << query.lastError().text();
  35.         db.close();
  36.         QSqlDatabase::removeDatabase("read_connection");
  37.         return false;
  38.     }
  39.     // 读取并输出数据
  40.     while (query.next()) {
  41.         int id = query.value(0).toInt();
  42.         QString name = query.value(1).toString();
  43.         qDebug() << id << ":" << name;
  44.     }
  45.     db.close();
  46.     QSqlDatabase::removeDatabase("read_connection");
  47.     return true;
  48. }
复制代码
完整代码汇总

将上述两个函数和 main 函数归并,即可得到一个完整的示例程序:
  1. #include <QtSql>
  2. #include <QCoreApplication>
  3. #include <QStandardPaths>
  4. #include <QDir>
  5. #include <QFile>
  6. #include <QDebug>
  7. // 定义加密密钥
  8. const QString DB_PASSWORD = "pass";
  9. // 定义数据库文件名
  10. const QString DB_FILENAME = "local.db";
  11. // 定义表名和示例数据
  12. const QString TABLE_NAME = "test";
  13. const QList<QPair<int, QString>> SAMPLE_DATA = {
  14.     {1, "AAA"},
  15.     {2, "BBB"},
  16.     {3, "CCC"},
  17.     {4, "DDD"},
  18.     {5, "EEE"},
  19.     {6, "FFF"},
  20.     {7, "GGG"}
  21. };
  22. // 函数声明
  23. bool createAndInsertData(const QString &dbPath);
  24. bool readData(const QString &dbPath);
  25. int main(int argc, char *argv[])
  26. {
  27.     QCoreApplication app(argc, argv);
  28.     // 获取数据库文件路径
  29.     QString dir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
  30.     QString dbPath = QDir(dir).absoluteFilePath(DB_FILENAME);
  31.     qDebug() << "DB File Path is:" << dbPath;
  32.     // 检查数据库文件是否存在
  33.     bool dbExists = QFile::exists(dbPath);
  34.     if (!dbExists) {
  35.         qDebug() << "数据库不存在,正在创建并插入数据...";
  36.         if (!createAndInsertData(dbPath)) {
  37.             qDebug() << "创建数据库或插入数据失败。";
  38.             return -1;
  39.         }
  40.         qDebug() << "数据库创建并成功插入数据。";
  41.     } else {
  42.         qDebug() << "数据库已存在,跳过创建步骤。";
  43.     }
  44.     // 读取数据
  45.     qDebug() << "正在读取数据库中的数据...";
  46.     if (!readData(dbPath)) {
  47.         qDebug() << "读取数据库数据失败。";
  48.         return -1;
  49.     }
  50.     qDebug() << "数据读取成功。";
  51.     return 0;
  52. }
  53. /**
  54. * @brief 创建加密数据库并插入数据
  55. * @param dbPath 数据库文件路径
  56. * @return 成功返回 true,否则返回 false
  57. */
  58. bool createAndInsertData(const QString &dbPath)
  59. {
  60.     // 添加 SQLITECIPHER 驱动
  61.     QSqlDatabase db = QSqlDatabase::addDatabase("SQLITECIPHER", "create_connection");
  62.     db.setDatabaseName(dbPath);
  63.     db.setPassword(DB_PASSWORD);
  64.     db.setConnectOptions("QSQLITE_USE_CIPHER=aes256cbc;"); // 使用 AES-256-CBC 加密
  65.     if (!db.open()) {
  66.         qDebug() << "打开数据库失败(创建):" << db.lastError().text();
  67.         QSqlDatabase::removeDatabase("create_connection");
  68.         return false;
  69.     }
  70.     qDebug() << "数据库已打开,用于创建和插入数据。";
  71.     QSqlQuery query(db);
  72.     // 创建表
  73.     QString createTableSQL = QString("CREATE TABLE %1 (id INTEGER PRIMARY KEY, name TEXT);").arg(TABLE_NAME);
  74.     if (!query.exec(createTableSQL)) {
  75.         qDebug() << "创建表失败:" << query.lastError().text();
  76.         db.close();
  77.         QSqlDatabase::removeDatabase("create_connection");
  78.         return false;
  79.     }
  80.     qDebug() << "表创建成功。";
  81.     // 插入数据
  82.     query.prepare(QString("INSERT INTO %1 (id, name) VALUES (:id, :name);").arg(TABLE_NAME));
  83.     foreach (const QPair<int, QString> &entry, SAMPLE_DATA) {
  84.         query.bindValue(":id", entry.first);
  85.         query.bindValue(":name", entry.second);
  86.         if (!query.exec()) {
  87.             qDebug() << "插入数据失败 (" << entry.first << "," << entry.second << "):" << query.lastError().text();
  88.             db.close();
  89.             QSqlDatabase::removeDatabase("create_connection");
  90.             return false;
  91.         }
  92.     }
  93.     qDebug() << "数据插入成功。";
  94.     db.close();
  95.     QSqlDatabase::removeDatabase("create_connection");
  96.     return true;
  97. }
  98. /**
  99. * @brief 读取加密数据库中的数据
  100. * @param dbPath 数据库文件路径
  101. * @return 成功返回 true,否则返回 false
  102. */
  103. bool readData(const QString &dbPath)
  104. {
  105.     // 添加 SQLITECIPHER 驱动
  106.     QSqlDatabase db = QSqlDatabase::addDatabase("SQLITECIPHER", "create_connection");
  107.     db.setDatabaseName(dbPath);
  108.     db.setPassword(DB_PASSWORD);
  109.     db.setConnectOptions("QSQLITE_USE_CIPHER=aes256cbc;"); // 使用 AES-256-CBC 加密
  110.     if (!db.open()) {
  111.         qDebug() << "打开数据库失败(读取):" << db.lastError().text();
  112.         QSqlDatabase::removeDatabase("read_connection");
  113.         return false;
  114.     }
  115.     qDebug() << "数据库已打开,用于读取数据。";
  116.     QSqlQuery query(db);
  117.     // 验证 SQLCipher 版本(可选)
  118.     if (query.exec("PRAGMA cipher_version;")) {
  119.         if (query.next()) {
  120.             QString cipher_version = query.value(0).toString();
  121.             qDebug() << "SQLCipher 版本:" << cipher_version;
  122.         } else {
  123.             qDebug() << "无法获取 SQLCipher 版本。";
  124.         }
  125.     } else {
  126.         qDebug() << "执行 PRAGMA cipher_version 失败:" << query.lastError().text();
  127.     }
  128.     // 查询数据
  129.     QString selectSQL = QString("SELECT id, name FROM %1 ORDER BY id;").arg(TABLE_NAME);
  130.     if (!query.exec(selectSQL)) {
  131.         qDebug() << "执行 SELECT 查询失败:" << query.lastError().text();
  132.         db.close();
  133.         QSqlDatabase::removeDatabase("read_connection");
  134.         return false;
  135.     }
  136.     // 读取并输出数据
  137.     while (query.next()) {
  138.         int id = query.value(0).toInt();
  139.         QString name = query.value(1).toString();
  140.         qDebug() << id << ":" << name;
  141.     }
  142.     db.close();
  143.     QSqlDatabase::removeDatabase("read_connection");
  144.     return true;
  145. }
复制代码
运行结果

假设 local.db 文件之前不存在,运行程序后将输出如下内容:
  1. DB File Path is: "C:/Users/用户名/Documents/local.db"
  2. 数据库不存在,正在创建并插入数据...
  3. 数据库已打开,用于创建和插入数据。
  4. 表创建成功。
  5. 数据插入成功。
  6. 数据库创建并成功插入数据。
  7. 正在读取数据库中的数据...
  8. 数据库已打开,用于读取数据。
  9. SQLCipher 版本: "4.5.0"
  10. 1 : "AAA"
  11. 2 : "BBB"
  12. 3 : "CCC"
  13. 4 : "DDD"
  14. 5 : "EEE"
  15. 6 : "FFF"
  16. 7 : "GGG"
  17. 数据读取成功。
复制代码
常见题目与解决

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

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

小秦哥

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表