Roadmap
- Option to save and load not encrypted backup file.
- Replace both HKDF and PBKDF2 with a slow enough Argon2id.
- Move local data from multiple IndexedDBs to a single OPFS file to fix deniability.
Structure of the encrypted file
- First 24 bytes - random HKDF salt-2.
- Next 12 bytes - random AES initialization vector (IV).
- Data below,
- encrypted and protected from tampering by AES-GCM
- using the IV and a 256-bit key-2,
- which is derived with HKDF from the random HKDF salt-2 and a 256-bit key-1,
- which is derived with 1,000,000 iterations of PBKDF2
- from your passphrase (empty by default)
- and the "whyolet-text-const-export-salt-1",
- which is used to avoid re-entering or keeping your passphrase in memory.
- Data below,
- compressed with gzip,
- if your browser supports CompressionStream API,
- else uncompressed.
- A JSON array of "page" objects, each having:
- "tag": string, title of the page, identifies the page.
- "text": string, main content of the page.
- "edited": string, ISO UTC timestamp of the last update of the "text".
- "done": boolean, true if all lines in "text" are marked done (──done line──) or follow an anchor (⚓).
- "selStart": number, characters before the selection starts.
- "selEnd": number, characters before the selection ends.
- "scroll": number, vertical scroll position in pixels from the top.
Local data encryption
- Local data is encrypted similar to the backup and sync files described above, but to keep auto-save fast, each piece of data is encrypted separately, without compression, using a salt of the database.
- The passphrase used for local data encryption is empty by default, even after you click the "Key" in the menu and set the passphrase (few words) for backup and sync files.
- However, if you set any passphrase starting with the dot (for example, ".Marvin in Wonderland"), it also opens the door to another world in the multiverse of local databases.
- Each world there is stored in its own IndexedDB database, named as a hex of 256 bits derived from the passphrase (without the dot prefix) and a shared random salt stored in localStorage as a hex.
- The non-secret world opened when the app starts uses an empty passphrase. You can return to it by either:
- closing and reopening the app,
- clicking the "Key" in the menu and entering a single dot,
- waiting for 5 minutes without editing text and clicking buttons.
- Each secret world is opened by its unique passphrase starting with the dot.
- Each database contains: pages with random ids, recent tags, undo history and position, font settings, random state to prevent CSRF on sync, and a random salt of this database.
- Each of these values (except random ids and the salt) is encrypted separately using the passphrase and this salt.
- Browser history stores random ids only. They are mapped to the pages in temporary memory only, not permanent storage. To disconnect the browser history from the pages you've opened, and to delete your decrypted data from memory (still keeping it encrypted in permanent storage), please either close the tab/app completely (not just switch to another tab/app/home screen) or click the "Key" in the menu and enter a single dot, or any other passphrase starting with the dot.
- You may use multiple passphrases to create fake worlds and keep updating plausible secrets there to be able to sacrifice them to attackers.
- You may sincerely forget few of these passphrases to explain existence of not sacrificed yet encrypted data.
- This should be enough for most cases when you want to protect your legitimate secrets.
- Please note it is still NOT fully deniable encryption yet, because advanced attacker with access to your device can analyze raw files implementing IndexedDB (LevelDB, SQLite, etc) to prove you have N encrypted databases, when they were updated, and then apply methods up to physical coercion until you reveal all the passphrases leading to each database.
- If you know how to improve encryption without breaking the frequent auto-save feature, please reach out to support@whyolet.com.