SQLCipher在Go中需通过cgo+mattn/go-sqlite3的sqlite3_with_sqlcipher标签编译,并显式设置PRAGMA key与cipher参数;密钥必须在首次Open后立即执行,且需匹配cipher-page-size;调试须用sqlcipher命令行工具。

SQLCipher 在 Go 中不原生支持,必须用 cgo + 自定义编译
Go 官方 database/sql 驱动(如 mattn/go-sqlite3)默认链接的是 vanilla SQLite,不带 SQLCipher 加密能力。想用加密数据库,就得让底层 SQLite 库启用 SQLITE_ENABLE_CODEC 并链接 OpenSSL 或 libsqlcipher。
常见错误现象:PRAGMA key = 'xxx' 执行成功但数据未加密,或直接报错 no such function: sqlcipher_export —— 这说明驱动根本没编译进 SQLCipher 支持。
- 必须用
mattn/go-sqlite3的sqlite3_with_sqlcipher构建标签,例如:go build -tags "sqlite3_with_sqlcipher" - 系统需提前装好
libsqlcipher开发包(Ubuntu/Debian:安装libsqlcipher-dev;macOS:用brew install sqlcipher) - 若用静态链接(比如交叉编译),得额外指定
-ldflags "-linkmode external -extldflags '-lsqlcipher'",否则运行时找不到 codec
打开加密数据库前必须先设 key,且只能在第一次 sqlite.Open 时生效
SQLCipher 的密钥不是连接参数,也不是 PRAGMA 设置后就全局生效——它必须在数据库文件首次被打开(即第一次执行 sqlite.Open)时,通过 PRAGMA key 立即设置,且不能延迟到建表之后。
典型翻车场景:先 Open 空数据库、再 Exec("PRAGMA key = ...")、再建表 —— 此时表仍是明文;或者用 sqlite.Open("db.sqlite", &sqlite.Config{...}) 传参方式试图塞 key,无效。
立即学习“go语言免费学习笔记(深入)”;
- 正确做法:用
sqlite.Open打开后,**立刻**执行db.Exec("PRAGMA key = ?)", password),且该语句必须是该连接上的第一个操作 - 如果数据库已存在且加密过,这里填错密码会静默失败(后续查询返回空或报
file is encrypted or is not a database) - 不建议在 Open 后做其他任何操作(比如
Ping或Exec("PRAGMA encoding")),它们可能触发内部初始化,导致 key 设置失效
PRAGMA cipher 和 PRAGMA cipher_page_size 要配对使用,否则迁移或重加密失败
SQLCipher v4 默认用 4096 字节页大小和 AES-256-CBC,而老版本(v3)默认是 1024。如果你从旧项目升级、或要兼容其他平台(如 Android/iOS 的 SQLCipher SDK),页大小不一致会导致 PRAGMA rekey 失败或读取出错。
错误信息示例:file is not a database 或 bad parameter or other API misuse,尤其出现在调用 PRAGMA rekey 之后。
- 新建库时显式指定:执行
db.Exec("PRAGMA cipher = 'aes-256-cbc'; PRAGMA cipher_page_size = 4096;")(注意分号分隔,不能合并成一条) - 重加密已有库:先
PRAGMA key,再PRAGMA rekey,但必须确保源库和目标 cipher 参数完全一致,否则 dump/load 会出错 - Go 驱动不自动识别 cipher 版本,所以即使你用 v4 编译,也得靠 PRAGMA 显式声明,否则默认降级为 v3 兼容模式
加密数据库无法用普通 sqlite3 命令行工具查看,调试时容易误判“数据没写进去”
用 sqlite3 db.sqlite 打开加密库,输入 .dump 或 SELECT * FROM ... 会直接报错 file is encrypted or is not a database,这不是 Go 程序写错了,而是命令行工具压根没加载 SQLCipher 插件。
这导致很多人反复检查 Go 代码、怀疑 Exec 没执行、甚至重写事务逻辑——其实数据早就加密写进去了,只是你看不见。
- 调试建议:用带 SQLCipher 的 CLI,例如 macOS 上
sqlcipher db.sqlite,然后手动PRAGMA key = 'xxx'; SELECT * FROM ...; - 或在 Go 程序里加一句验证:
var count int; db.QueryRow("SELECT COUNT(*) FROM sqlite_master").Scan(&count),能查到 >0 就说明库结构正常 - 别依赖文件大小判断是否加密——加密前后 .sqlite 文件体积几乎一样,二进制头也看不出区别
最麻烦的其实是密钥管理:硬编码在代码里、存在 config 文件中、或由环境变量注入,每种都有泄露风险,而且一旦密钥丢失,数据彻底不可恢复。这点没法靠驱动解决,得你自己兜底。










