· 3 min read
SQLCipher:SQLite 数据库加密方案深度解析
Android
在移动应用中,本地数据库存储是常见需求。但 SQLite 默认是明文存储的——一旦设备被 Root,攻击者可以轻易读取其中的数据。SQLCipher 通过透明加密解决这一安全问题。
什么是 SQLCipher
SQLCipher 是 Zetetic 公司开发的一个开源库,为 SQLite 添加了透明的 AES-256 加密。它是 SQLite 的一个安全扩展,所有加密操作对应用层完全透明,你可以继续使用标准的 SQLite API,无需修改现有代码。
核心特性
| 特性 | 说明 |
|---|---|
| 加密算法 | AES-256-CBC |
| 密钥派生 | PBKDF2 |
| 加密单元 | 页级加密(每页独立) |
| 性能开销 | 约 5-15% |
| 认证 | HMAC-SHA256 |
加密原理简介
1. 密钥派生:从口令到密钥
用户只需要提供一个口令(password),SQLCipher 会通过 PBKDF2(Password-Based Key Derivation Function 2)将口令转换为加密密钥。
// SQLCipher 密钥派生简化流程
PBKDF2(
password, // 用户口令
salt, // 随机盐值(数据库生成)
256000, // 迭代次数(SQLCipher 默认)
SHA256, // 伪随机函数
32, // 输出密钥长度(256 bits)
-> aes_key // AES-256 加密密钥
)
安全性关键:每次创建数据库时都会生成随机的盐值。这意味着即使两个用户使用相同的口令,派生出的密钥也不同。
2. 页级加密
SQLCipher 不是对整个数据库文件加密,而是以页为单位进行加密:
graph TD
A[数据库文件] --> B[页1<br/>明文]
A --> C[页2<br/>明文]
A --> D[页3<br/>明文]
B --> B1[加密]
C --> C1[加密]
D --> D1[加密]
B1 --> F[AES-256-CBC]
C1 --> F
D1 --> F
F --> G[页1<br/>密文]
F --> H[页2<br/>密文]
F --> I[页3<br/>密文]
- 默认页大小:4096 字节
- 每页使用独立的 IV(初始化向量)
- 读取时解密整个页,写入时加密整个页
3. 消息认证:防篡改
每个加密页末尾附加 HMAC-SHA256 消息认证码:
// 写入时的处理流程
1. 生成随机 IV
2. AES-256-CBC 加密页数据
3. 计算密文的 HMAC-SHA256
4. 将 IV + 密文 + HMAC 写入磁盘
读取时验证 HMAC,如果数据被篡改则拒绝解密。
加密效果对比
使用 strings 命令查看 SQLite vs SQLCipher 文件:
普通 SQLite 数据库(可读出明文):
SQLite format 3...
users
id
name
email
admin
test@example.com
SQLCipher 加密数据库(完全不可读):
¬í™²±…º§æ®™²±…º§æ®™²±…º§æ®™²±…º§æ
홲±…º§æ®™²±…º§æ®™²±…º§æ®™²±…º§æ
Android 集成
1. 添加依赖
// build.gradle (app)
dependencies {
implementation 'net.zetetic:android-database-sqlcipher:4.5.4'
implementation 'androidx.sqlite:sqlite:2.4.0'
}
2. 使用 Room + SQLCipher
// 1. 创建加密的数据库
val passphrase = getOrCreateDatabaseKey()
val factory = SupportFactory(passphrase)
// 2. 配置 Room
val room = Room.databaseBuilder(
applicationContext,
MyDatabase::class.java,
"encrypted.db"
)
.openHelperFactory(factory)
.build()
// 3. 获取 DAO
val userDao = room.userDao()
3. 安全存储密钥
❌ 避免的做法:
// 硬编码密钥 - 极不安全
val key = "my-secret-key".toByteArray()
✅ 推荐的做法:
// 使用 Android Keystore 加密密钥
val secureKey = EncryptedSharedPreferences
.create(context, "db_key_prefs", masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
// 存储密钥
secureKey.edit().putString("db_key", Base64.encodeToString(key, Base64.DEFAULT))
性能优化
1. 合理配置页大小
// SQLite 页大小与性能
// 较小页:查询灵活,但加密开销高
// 较大页:顺序读性能好,加密开销低
PRAGMA page_size = 4096; // 默认,适合大多数场景
PRAGMA page_size = 8192; // 大型顺序读场景
2. 使用 WAL 模式
// 启用 WAL 模式提升并发性能
val db = room.openHelper.writableDatabase
db.execSQL("PRAGMA journal_mode = WAL")
3. 缓存优化
// 调整缓存大小(单位:页)
db.execSQL("PRAGMA cache_size = 10000")
迁移指南
从明文 SQLite 迁移
// 使用 SQLCipher 附带的迁移工具
val sourceDb = SQLiteDatabase.openDatabase(
"plain.db", null, SQLiteDatabase.OPEN_READWRITE
)
val destDb = SQLiteDatabase.openDatabase(
"encrypted.db",
SupportFactory(key).apply { loadLibs(context) },
SQLiteDatabase.CREATE_IF_NECESSARY
)
// 执行迁移
sourceDb.rawExecSQL("ATTACH DATABASE '${destDb.path}' AS encrypted KEY 'your-password'")
sourceDb.rawExecSQL("SELECT sqlcipher_export('encrypted')")
sourceDb.rawExecSQL("DETACH DATABASE encrypted")
常见问题
Q1: 忘记密钥怎么办?
无法恢复。SQLCipher 设计为即使开发者也无法解密数据。务必做好密钥备份或使用可恢复的密钥存储方案。
Q2: 如何验证数据库已加密?
// 检查文件头
val file = File(dbPath)
val header = ByteArray(16)
FileInputStream(file).use { it.read(header) }
// 明文 SQLite: "SQLite format 3"
// SQLCipher: 随机字节
val isEncrypted = !String(header).contains("SQLite")
Q3: 支持哪些平台?
| 平台 | 支持状态 |
|---|---|
| Android | ✅ 完整支持 |
| iOS | ✅ 完整支持 |
| macOS | ✅ 完整支持 |
| Windows | ✅ 完整支持 |
| Linux | ✅ 完整支持 |
总结
SQLCipher 为 SQLite 提供了企业级安全保护:
- AES-256-CBC: 军事级别加密标准
- PBKDF2: 安全的密钥派生(256000 次迭代)
- 页级加密 + HMAC: 防止数据篡改
- 零感知 API: 原有代码无需修改
- 5-15% 性能开销: 可接受的安全代价
任何存放敏感用户数据的 App,都应该使用 SQLCipher 替代普通 SQLite。