Back to Blog
· 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。


相关阅读