Back to Blog
By AriesZhou · · 4 min read

SQLCipher: Encrypting SQLite Databases

Android

Local database storage is a common requirement in mobile apps. But SQLite stores data in plaintext by default. Once a device is rooted, an attacker can easily read the data inside. SQLCipher solves this security problem through transparent encryption.

SQLCipher is an open-source library developed by Zetetic that adds transparent AES-256 encryption to SQLite. It is a security extension of SQLite, and all encryption operations are completely transparent to the application layer. You can continue using the standard SQLite API without modifying existing code.

FeatureDescription
Encryption algorithmAES-256-CBC
Key derivationPBKDF2
Encryption unitPage-level encryption (each page independently)
Performance overheadAbout 5-15%
AuthenticationHMAC-SHA256

Encryption principles

Key derivation

The user only needs to provide a password, and SQLCipher converts it into an encryption key through PBKDF2 (Password-Based Key Derivation Function 2).

// SQLCipher 密钥派生简化流程
PBKDF2(
    password,           // 用户口令
    salt,              // 随机盐值(数据库生成)
    256000,           // 迭代次数(SQLCipher 默认)
    SHA256,            // 伪随机函数
    32,                // 输出密钥长度(256 bits)
    -> aes_key         // AES-256 加密密钥
)

Security note: A random salt is generated every time a database is created. This means that even if two users use the same password, the derived keys will be different.

Page-level encryption

SQLCipher does not encrypt the entire database file. Instead, it encrypts page by page:

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/>密文]
  • Default page size: 4096 bytes
  • Each page uses an independent IV (initialization vector)
  • Decrypts the entire page on read, encrypts the entire page on write

Message authentication: tamper-proofing

Each encrypted page ends with an HMAC-SHA256 message authentication code:

// 写入时的处理流程
1. 生成随机 IV
2. AES-256-CBC 加密页数据
3. 计算密文的 HMAC-SHA256
4. 将 IV + 密文 + HMAC 写入磁盘

HMAC is verified on read; decryption is refused if the data has been tampered with.

Encryption comparison

Use the strings command to inspect SQLite vs SQLCipher files:

Plain SQLite database (plaintext is readable):

SQLite format 3...
users
id
name
email
admin
test@example.com

SQLCipher encrypted database (completely unreadable):

  ¬í™²±…º§æ®™²±…º§æ®™²±…º§æ®™²±…º§æ
­í™²±…º§æ®™²±…º§æ®™²±…º§æ®™²±…º§æ

Android integration

Add dependencies

// build.gradle (app)
dependencies {
    implementation 'net.zetetic:android-database-sqlcipher:4.5.4'
    implementation 'androidx.sqlite:sqlite:2.4.0'
}

Using 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()

Storing keys securely

❌ Practices to avoid:

// 硬编码密钥 - 极不安全
val key = "my-secret-key".toByteArray()

✅ Recommended approach:

// 使用 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))

Performance optimization

Configure page size appropriately

// SQLite 页大小与性能
// 较小页:查询灵活,但加密开销高
// 较大页:顺序读性能好,加密开销低

PRAGMA page_size = 4096;  // 默认,适合大多数场景
PRAGMA page_size = 8192;  // 大型顺序读场景

Use WAL mode

// 启用 WAL 模式提升并发性能
val db = room.openHelper.writableDatabase
db.execSQL("PRAGMA journal_mode = WAL")

Cache optimization

// 调整缓存大小(单位:页)
db.execSQL("PRAGMA cache_size = 10000")

Migration guide

Migrating from plaintext 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")

FAQ

  • What if I forget the key?

Unrecoverable. SQLCipher is designed so that even the developer cannot decrypt the data. Be sure to back up the key or use a recoverable key storage scheme.

  • How do I verify the database is encrypted?
// 检查文件头
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")
  • Which platforms are supported?
PlatformSupport status
Android✅ Full support
iOS✅ Full support
macOS✅ Full support
Windows✅ Full support
Linux✅ Full support

Related reading