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

  1. 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);
    }
  2. 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)
  3. 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 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.