// terminal-native · AES-256-GCM · Argon2id · Rust 1.75+

Qrpto:note Your secrets never leave RAM.

An encrypted terminal notebook for developers and power users. Every line independently sealed with AES-256-GCM. Cleartext lives in a single mlock'd 4 KiB buffer — volatile-zeroed on focus loss. No cloud. No heap residue. No compromise.

↗ View on GitHub How it works →
qrpto-note — vault.svt1
$ qrptonote open vault.svt1
Deriving key via Argon2id...
Entries loaded: 6 │ Format: SVT1
 
1 │ ████████████████████████████
2 │ ████████████████████
3 │ ██████████████████████████████████
4 │ ████████████████
5 │ ████████████████████████
6 │ ████████████████████████████████
 
[SPACE] reveal · [↑↓] navigate · [ESC] zero+lock
 
256 -bit AES-GCM
4 KiB mlock'd cleartext buffer
0 cloud dependencies
1 self-contained binary
// capabilities

Built for secrets that matter.

Per-Line Encryption
Every entry is sealed independently using AES-256-GCM with a unique randomly-generated 96-bit nonce and 128-bit authentication tag. No shared IV reuse. No block correlation.
AES-256-GCM · 96-bit nonce
Argon2id Key Derivation
Your master key is never stored. Derived fresh each session via Argon2id with 19 456 KiB memory (fills L3 cache), 2 iterations, parallelism 1 — yielding a 32-byte AES-256 key. GPU/ASIC attacks gain no edge.
19 MB memory-hard · never persisted
Need-to-See Cleartext
All entries always display as ████ blocks. Pressing Space decrypts exactly one line into a single mlock'd 4 KiB buffer. Press any arrow key or Escape — every byte is zeroed before the next action.
volatile-zero · MADV_DONTDUMP
Memory Hardening
The cleartext buffer is mlock'd to physical RAM (no OS paging to disk), flagged MADV_DONTDUMP (excluded from core dumps), and the AES round-key schedule is scrubbed via write_volatile on drop.
mlock · no swap · no core dump
Process Hardening
At startup, prctl(PR_SET_DUMPABLE, 0) blocks /proc/PID/mem reads and ptrace attachment from non-root processes. Your vault process cannot be inspected by other processes on the same host.
prctl · ptrace-hardened
SVT1 Binary Format
Magic bytes → 32-byte Argon2id salt → entry count → entries (12-byte nonce ‖ u32 ciphertext length ‖ N bytes ciphertext+tag). Simple, auditable, zero runtime dependencies. One self-contained Rust binary.
SVT1 · single binary · Rust 1.75
// how it works

From password to ciphertext — and back.

01
Open vault
You provide your password. Argon2id derives the 32-byte master key fresh — 19 456 KiB memory, 2 iterations, parallelism 1. No key is written anywhere. The 32-byte salt is stored in the vault header.
Argon2id(19MB) → master_key[32]
02
Entries load redacted
All ciphertext is read and authenticated. Every entry is rendered as a fixed-width ████ block. Nothing is decrypted into heap memory.
SVT1 → ████████████
03
Press Space to peek
The selected entry is decrypted into the mlock'd buffer only. It is displayed for exactly as long as your focus remains. The buffer holds at most one entry at a time.
mlock'd 4 KiB buffer
04
Focus moves — bytes zero
Before any cursor movement or Escape is processed, every byte of the cleartext buffer is volatile-zeroed. No residue. No timing window. No second chance for an attacker.
write_volatile([0u8; 4096])

What we protect against.

Cleartext scope is minimal by design.

// process memory — qrptonote running
ciphertext
[ encrypted · heap · safe ]
cleartext buf
[ 4 KiB · mlock'd · DONTDUMP ]
master key
✗ not stored
round keys
[ zeroed on drop ]
Cleartext exists in exactly one location: the mlock'd buffer. It is present for the minimum possible duration — zeroed before any cursor movement is processed. The rest of the process heap holds only authenticated ciphertext.
// first-run audit

Verify your OS before trusting it with secrets.

qrptonote's protections — mlock, prctl, ASLR, swap isolation — depend on your Linux kernel being correctly configured. Run the one-time audit script to generate a full security report for your machine.

// requires Python 3.8+ · no root · nothing written to system
$ python3 tools/audit/qrptonote_audit.py
$ open qrptonote_security_report.html
↗ View audit script on GitHub
// key bindings

Three modes. Full keyboard control.

Locked mode ████ all entries
↑ / k Move cursor up
↓ / j Move cursor down
Space Reveal entry → decrypt to buffer
n Add new empty entry
d Delete focused entry
s Save vault to disk
q Quit
Revealed mode cleartext visible
Enter Enter edit mode
Esc Lock → zeroize buffer
↑ / ↓ Navigate — immediately zeroizes
s Save vault
Edit mode cleartext editable
Esc Save → re-encrypt → lock
↑ / ↓ Discard edits, move, lock
← / → Move cursor within line
Home/End Jump to start / end
Backspace Delete char before cursor
Delete Delete char at cursor
// build & install

One binary. No runtime. No surprise.

// requires Rust 1.75+
$ cargo build --release
Binary: ./target/release/qrptonote
# Create a new vault
$ ./qrptonote my-secrets.sv
 
# Open an existing vault
$ ./qrptonote my-secrets.sv
Linux x86_64
Linux ARM64
macOS ARM
macOS x86_64

Tested with rustc 1.75 / cargo 1.75. No runtime dependencies. The vault file uses the SVT1 binary format — auditable and dependency-free.

↗ View on GitHub Read the README
// project structure

Clean separation of concerns.

src/
main.rsEntry point, password prompt, TUI event loop
app.rsState machine: Locked → Revealed → Editing
ui.rsratatui rendering (header, list, footer)
crypto.rsAES-256-GCM engine + Argon2id key derivation
secure_buf.rsmlock'd, zeroize-on-drop cleartext buffer
storage.rsBinary vault file format (SVT1)
// known limitations
  • ! The intermediate Vec<u8> from the AES-GCM decrypt call lives briefly on the heap before loading into SecureBuffer. A custom allocator (e.g. memsec) would close this window.
  • ! The AES key schedule inside Aes256Gcm is not explicitly zeroed on drop — the crate does not expose a Zeroize impl on the cipher.
  • ! mlock is best-effort: it can fail silently if RLIMIT_MEMLOCK is exhausted. Zeroize still runs regardless.