Overview
Context: Note content is currently stored as plain text in SQLite (typed_content column in the pages table, nexanote/storage/database.py). There is no encryption anywhere in the codebase. Ink stroke data (JSON in the strokes table) is also stored in plain text.
This issue implements symmetric encryption of note content at rest using the cryptography Python library (Fernet), which is already listed in requirements.txt.
What needs to be done
Step 1 — Key management
Step 2 — Encrypt/decrypt in the storage layer
Step 3 — Migration
Step 4 — Tests
Goal
After this change, opening the SQLite file with a plain text viewer shows ciphertext for all note content. The Flutter app sees no difference — it still receives decrypted content from the API as before.
What NOT to do (out of scope for this issue)
- No passphrase input from the user yet (key file is enough for now)
- No end-to-end encryption between Flutter and backend (that's a separate issue)
- No encryption of notebook metadata (names, titles) yet
Where to look
nexanote/storage/database.py — only file that needs changing
requirements.txt — cryptography is already listed
tests/test_models_and_db.py — add new tests here
Testing steps
Related to: #8
Overview
Context: Note content is currently stored as plain text in SQLite (
typed_contentcolumn in thepagestable,nexanote/storage/database.py). There is no encryption anywhere in the codebase. Ink stroke data (JSON in thestrokestable) is also stored in plain text.This issue implements symmetric encryption of note content at rest using the
cryptographyPython library (Fernet), which is already listed inrequirements.txt.What needs to be done
Step 1 — Key management
nexanote.keyin the data directory, permissions set to 600)Step 2 — Encrypt/decrypt in the storage layer
nexanote/storage/database.py, add two helper functions:_encrypt(text: str) -> strand_decrypt(text: str) -> strusing the loaded Fernet keyNexaNoteDB.save_page(), call_encrypt()ontyped_contentbefore writing to SQLiteNexaNoteDBpage read methods, call_decrypt()ontyped_contentafter reading from SQLitepointsJSON the same way (encrypt the JSON string before storing)Step 3 — Migration
InvalidToken, the row is unencrypted) and re-encrypt themStep 4 — Tests
tests/test_models_and_db.pyto confirm that raw SQLite content is ciphertext and that reading returns the original plaintextGoal
After this change, opening the SQLite file with a plain text viewer shows ciphertext for all note content. The Flutter app sees no difference — it still receives decrypted content from the API as before.
What NOT to do (out of scope for this issue)
Where to look
nexanote/storage/database.py— only file that needs changingrequirements.txt—cryptographyis already listedtests/test_models_and_db.py— add new tests hereTesting steps
nexanote.dbwith a SQLite browser — verifytyped_contentis NOT "hello world"python -m pytest tests/test_models_and_db.py— all tests passRelated to: #8