Documentation

Encryption architecture

DeepJournal uses end-to-end encryption (E2EE) to protect your writing. This means all encryption happens locally on your device before anything is synced to the cloud. DeepJournal never see plaintext data or any keys required to decrypt it.

Important: Your encryption password is different from your login password. Logging into your account does not give access to your encrypted data, only your encryption password can unlock your journal.

Warning: If you forget your encryption password, your data cannot be recovered unless you previously created and saved your Recovery Key.


1. Key Hierarchy Overview

DeepJournal uses three types of keys:

KeyPurposeWhere It ExistsStored in Plaintext?
DEK (Data Encryption Key)Encrypts all journal entriesOnly in main process memory when unlocked❌ Never
KEK (Key Encryption Key)Wraps/unwraps the DEKDerived from user password on demand❌ Never stored
Recovery KeyBackup key to unwrap the DEK if password is lostShown once to the user; stored wrapped version in DB❌ Never stored plaintext

2. The Data Encryption Key (DEK)

  • A 32-byte random key (256-bit) created on first login.
  • Used with XChaCha20-Poly1305 to encrypt:
    • Journal entries
    • States
    • Logs
  • Never leaves the device in plaintext.
  • Loaded only when the app is unlocked.
  • Securely cleared from memory when the app is locked.

Why a DEK?

Using a single symmetric key for all data is efficient and secure as long as the key itself is strongly protected, which is handled by the KEK.


3. The Key Encryption Key (KEK)

The KEK is derived from the user’s password using Argon2id, a state-of-the-art password-based key derivation function.

Argon2id configuration:

  • Memory: 256 MB
  • Iterations: 3
  • Parallelism: default value
  • Salt: 16 random bytes (stored in database; not secret)

What the KEK does:

  • Encrypts (wraps) the DEK → stored as wrapped_dek
  • Decrypts (unwraps) the DEK at unlock time
  • Is discarded from memory immediately after use

The password is never stored, sent, or synced.

Only the wrapped DEK and Argon2 salt are synced, both safe to store publicly.


4. The Recovery Key

If the user forgets their password, the Recovery Key allows them to regain access without data loss.

  • A random 32-byte (256-bit) value.
  • Presented to the user formatted in groups (e.g., ABCD-EFGH-IJKL-…).
  • Used directly as a KEK (no Argon2 needed).
  • Wraps the DEK and is stored as recovery_wrapped_dek.
  • Can be regenerated while the app is unlocked.

This means users can reset their password by:

  1. Unwrapping the DEK using the recovery key.
  2. Deriving a new KEK from the new password.
  3. Re-wrapping the DEK and updating storage.

DeepJournal never stores the plaintext recovery key.


5. Encryption Algorithm: XChaCha20-Poly1305

All encryption process uses XChaCha20-Poly1305, providing:

  • High security with built-in authentication
  • 192-bit nonces (extremely unlikely to repeat)
  • Resistance to nonce misuse bugs common in other ciphers
  • Fast performance across devices

All payload are Base64-encoded for storage.


6. What Gets Stored in the Remote Database

FieldDescription
wrapped_dekDEK encrypted using password-derived KEK
salt16-byte Argon2 salt (Base64)
recovery_wrapped_dekDEK encrypted using recovery key (nullable)

No plaintext DEK, KEK, or password is ever stored or transmitted.


7. Local Key Cache (Offline Unlock)

To support offline use, a local file mirrors the server data:

/deepjournal/databases/{userId}/keys.json

This file contains:

  • wrapped_dek
  • salt
  • recovery_wrapped_dek

Having this file means the user can unlock the app offline just by providing their password.


8. Unlocking Process

When the user enters their password:

  1. Argon2id derives KEK from password + stored salt.
  2. KEK unwraps the DEK from wrapped_dek.
  3. DEK is loaded into secure memory in the main process.
  4. App decrypts journal entries using DEK.
  5. KEK is immediately wiped from memory.

On lock/logout:

  • DEK is securely wiped from memory.
  • No encryption keys remain in RAM.

9. Encrypting Journal Entries

Each journal entry is encrypted locally using:

ciphertext = XChaCha20-Poly1305(DEK, plaintext, random_nonce)

10. Password Reset Workflow

If the user has the Recovery Key:

  1. User enters recovery key.
  2. App unwraps the DEK using recovery_wrapped_dek.
  3. User enters a new password.
  4. New password → Argon2id → new KEK.
  5. New KEK re-wraps the DEK.
  6. Database + local cache updated.

No data is re-encrypted; only the key wrapping changes.