导读 :原生 SQLite 落盘是明文,拿到
.db就能打开。SQLCipher / sqlite3mc 可以在打开连接后立刻设密钥,新建库和旧明文库都能加密。本文按原文把时序、C++ 代码、sqlcipher_export迁移和 Navicat 建库走一遍,并把file is not a database、sqlite3_key未定义的常见坑列出来。
前言
做桌面客户端、嵌入式设备、轻量化后端开发的小伙伴,大概率都踩过 SQLite 的安全大坑:
原生 SQLite 数据库完全明文存储,只要别人拿到你的 .db 文件,无需密码、无需破解,直接用数据库工具就能查看所有数据,用户隐私、业务数据、核心配置全部裸奔。
很多人做SQLite文件加密时,都会被一个误区误导:数据库必须创建时加密,已有的明文数据库文件无法后期加密。
今天这篇文章,我结合多年项目实战经验,彻底破除这个误区,手把手带你实现:新建库即时加密 + 旧明文数据库无损转加密,附带完整可编译的 C++ 代码、以及实战可落地生产!
一、核心误区辟谣(重中之重)
先直接抛出颠覆新手认知的核心结论:
SQLCipher 不需要在创建数据库时加密!
✅ 支持两种场景全覆盖:
- 新数据库:打开空库后立即设置密钥,后续所有数据自动加密落盘
- 旧明文数据库:支持无损迁移,一键将明文 db 转为加密 db,不丢任何表结构、数据、索引
很多人数据库加密失败、报 file is not a database 错误,根本原因不是工具问题,而是操作时序错误!
这里给大家划死核心规则:
SQLCipher 加密的本质:密钥只激活当前连接的加密编解码器,仅对「设置密钥之后的IO操作」生效,不会自动加密历史明文数据!
❌ 错误操作:先建表、插数据、读写数据,再设置密钥(数据半明半密,数据库直接损坏)
✅ 正确操作:打开数据库后,第一行代码必须设置密钥,再执行所有业务SQL

二、SQLCipher 加密原理极简科普
SQLCipher 是 SQLite 官方主流开源加密分支,核心优势:
- 采用 AES-256-CBC 高强度加密,搭配 PBKDF2 密钥派生、HMAC 校验,防破解、防篡改
- 页级透明加密,业务代码零改动,原生SQL语法完全兼容
- 自动加密 WAL、日志临时文件,无明文泄露风险
- 跨平台适配:Windows/Linux/嵌入式/Qt 项目通用
唯一局限:仅保护磁盘静态文件,数据加载到内存后为明文,无法防进程内存抓包,满足绝大多数业务安全需求。

三、C++ 完整实战代码(可直接编译运行)
包含:新建加密库、数据读写、修改密码、异常捕获,纯原生C++。
这里需要依赖"sqlite3mc.lib"
示例代码资源链接:【免费】sqlite文件加密示例代码资源-CSDN下载
cpp
#include <iostream>
#include <string>
#include <cstring>
#include "sqlite3.h"
// SQL查询回调
static int sqlCallback(void* data, int argc, char** argv, char** azColName)
{
for (int i = 0; i < argc; i++)
{
std::cout << azColName[i] << " = " << (argv[i] ? argv[i] : "NULL") << "\t";
}
std::cout << "\n";
return 0;
}
int main()
{
sqlite3* db = nullptr;
char* errMsg = nullptr;
const char* dbPath = "secure_demo.db";
const char* pwd = "demo654321"; // 自定义密钥
// 1. 打开/创建数据库
int ret = sqlite3_open_v2(dbPath, &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr);
if (ret != SQLITE_OK)
{
std::cerr << "数据库打开失败:" << sqlite3_errmsg(db) << std::endl;
sqlite3_close(db);
return -1;
}
// wxSQLite3 AES-128 CBC,必须在 sqlite3_key 之前指定
sqlite3_exec(db, "PRAGMA cipher = 'aes128cbc';", nullptr, nullptr, &errMsg);
// 【核心必写】打开后立即设置密钥,所有业务操作之前执行!
ret = sqlite3_key(db, pwd, strlen(pwd));
if (ret != SQLITE_OK)
{
std::cerr << "密钥设置失败:" << sqlite3_errmsg(db) << std::endl;
sqlite3_close(db);
return -1;
}
// 开启WAL模式,提升并发性能,日志文件同步加密
sqlite3_exec(db, "PRAGMA journal_mode=WAL;", nullptr, nullptr, &errMsg);
// 2. 建表测试
const char* createSql = R"(
CREATE TABLE IF NOT EXISTS user_info(
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT,
phone TEXT
);
)";
sqlite3_exec(db, createSql, nullptr, nullptr, &errMsg);
// 3. 插入测试数据
sqlite3_exec(db, "INSERT INTO user_info(username,phone) VALUES('aaaa','13800138000');", nullptr, nullptr, &errMsg);
// 4. 查询数据
std::cout << "===== 加密数据库查询结果 =====\n";
sqlite3_exec(db, "SELECT * FROM user_info;", sqlCallback, nullptr, &errMsg);
// 5. 修改数据库密码(rekey 仅适用于已加密数据库)
//const char* newPwd = "demo123456";
//ret = sqlite3_rekey(db, newPwd, strlen(newPwd));
//if (ret == SQLITE_OK)
//{
// std::cout << "\n✅ 密码修改成功!新密码:" << newPwd << std::endl;
//}
// 关闭数据库
sqlite3_close(db);
std::cout << "\n✅ 加密流程执行完成\n";
return 0;
}
四、旧明文数据库 → 加密数据库无损转换
针对已有存量明文 SQLite 数据库,无需删库重建,通过 sqlcipher_export 一键全量迁移,零数据丢失:

cpp
// 打开旧明文数据库
sqlite3* plainDb = nullptr;
char* errMsg = nullptr;
sqlite3_open("old_plain.db", &plainDb);
// 新建加密数据库并设置密钥
sqlite3* encDb = nullptr;
sqlite3_open("new_secure.db", &encDb);
sqlite3_exec(db, "PRAGMA cipher = 'aes128cbc';", nullptr, nullptr, &errMsg);
sqlite3_key(encDb, "YourPwd@123", 11);
char* err = nullptr;
// 全量迁移:表、数据、索引、触发器全部复制
const char* transferSql = "ATTACH DATABASE 'old_plain.db' AS plain KEY ''; SELECT sqlcipher_export('plain'); DETACH DATABASE plain;";
sqlite3_exec(encDb, transferSql, nullptr, nullptr, &err);
执行完成后,new_secure.db 即为完整加密数据库,原明文数据库可备份删除。
五、加密库的另外创建方式,可以依赖第三方工具创建
这里可以依赖Navicat软件进行创建,如下图所示:


六、常见问题汇总
1、报错:file is not a database
可能原因有如下几种:
1.1、密码错误;
1.2、SQLCipher版本不兼容;
1.3、创建表的时候先写数据后加密;

2、报错:sqlite3_key 未定义
原因:链接了系统原生 SQLite 库,未使用编译后的 SQLCipher 库