Core Architecture
Persistence & SQLite DB
Encryption at rest, SQLite tables, KDF key derivation, and metadata caches.
Overview
Wiltkey implements strict encryption at rest to protect chat histories if a physical device is seized. Plaintext messages are never stored in the database. Instead, they are encrypted with a 256-bit AES master key derived on-the-fly from the user's PIN. When the app is locked or closed, the master key is purged from RAM, making the database undecryptable.
How it Works
-
Master Key Derivation (KDF):
When a user enters their PIN, the app derives the master key via a custom key derivation function (KDF) that hashes the concatenated PIN + Salt with 5000 iterations of SHA-256:
lib/core/state_auth.dart DART
static String deriveKey(String pin, String saltHex) { List<int> key = utf8.encode(pin + saltHex); for (int i = 0; i < 5000; i++) { key = sha256.convert(key).bytes; } return hexEncode(key); } -
AES-256-CBC Encryption at Rest:
Wiltkey uses AES-256 in Cipher Block Chaining (CBC) mode with a secure random 16-byte initialization vector (IV) prefixed to the ciphertext (format:
iv_base64:ciphertext_base64). The derived key encrypts the message payload before it is written to the SQLite column:text_encrypted_master = AES_Encrypt(plaintext, key=masterKeyHex) -
SQLite DB Schema:
The SQLite database contains 5 tables. Key details:
contacts: Stores unified 1:1 and group profiles, offsets, and slot information.messages: Stores message IDs, timestamps, content type, raw OTP ciphertext (text_otp), and local master-encrypted plaintext (text_encrypted_master).group_info,group_lanes,group_profiles: Store shared-pad lane slot indices and peer arrival orders.
SQLite Schema Fields Reference
| Table | Field | Type | Description |
|---|---|---|---|
contacts |
is_archived |
INTEGER | Soft-nuke indicator (1 = archived read-only state). Added in DB version 2. |
contacts |
is_pinned |
INTEGER | Float to top of list flag. Added in DB version 3. |
messages |
text_otp |
TEXT | Base64 encoded ciphertext as received over the wire. |
messages |
text_encrypted_master |
TEXT | AES-256-CBC encrypted plaintext stashed locally. Null for unopened background messages. |
messages |
is_failed |
INTEGER | 1 when a message send failed (checked for auto-refunds on launch). Added in version 5. |
Gotchas & Edge Cases
⚠️ THE UNOPENED MESSAGE GAP
Messages arriving in the background while the device is locked exist only as
Messages arriving in the background while the device is locked exist only as
text_otp ciphertext (since the master key is not in RAM to encrypt them for at-rest storage). If the OTP pad is deleted or regenerated before the user unlocks and opens the chat, these messages become permanently undecryptable.