Skip to content

NakliTechie/rotor

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

28 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Rotor

Try it now → https://rotor.naklitechie.com/ 📖 Guide & trust notes → https://rotor.naklitechie.com/guide/

A TOTP authenticator in one HTML file.

No install, no account, no server, no sync service. Your vault is a folder. Move the folder and your vault moves with it.

Rotor does one thing: rotating TOTP codes. No passwords, no cards, no notes, ever. If you want those, use its sibling Tijori. They share the same engine.


What it does

  • Rotating 6/7/8-digit TOTP codes (RFC 6238) — SHA-1, SHA-256, SHA-512, 30s or 60s periods
  • Add codes by pasting an otpauth:// URI or entering the Base32 secret manually
  • Glance-grid view with per-code countdown rings, tap-to-copy, next-code preview, pinning
  • Every entry is individually encrypted: AES-256-GCM with a random 12-byte nonce
  • Key derived via PBKDF2-SHA-256, 600,000 iterations (OWASP 2023 level)
  • Vault format is an append-only event log — one .jsonl file per device, SHA-256 hash-chained
  • Air-gapped sync via QR flash — beam the encrypted vault to another device as a stream of animated QR codes. No cable, no Wi-Fi, no cloud, no account. Works for both first-time bootstrap (empty receiving vault) and ongoing updates (existing vault merges new events only — dedupes by device_id:seq). Two phones in airplane mode can sync.
  • Multi-device sync by any file transport: cloud folder, Syncthing, Git, USB, encrypted archive, or QR flash — same deterministic merge, same file format
  • Per-field last-writer-wins merge — deterministic, no conflicts, works with events arriving out of order
  • Device revocation (soft — skips future events from the revoked device on merge)
  • FSA primary + OPFS fallback — vault lives in a folder on desktop, in browser storage on iOS / mobile
  • Clipboard auto-clears, idle lock, lock-on-tab-hide
  • Self-test on boot — RFC 6238 Appendix B test vectors verified in DevTools console

What it deliberately isn't

  • No passwords. No cards. No notes. No secure text. No file attachments. No password manager features of any kind. If you want those, use Tijori.
  • No server. Vault files never leave the folder (or browser storage) you choose.
  • No account. There is nothing to log in to.
  • No recovery. Forget the master password and your vault is gone. Back up.
  • No telemetry, no analytics, no network requests of any kind after page load.
  • No framework, no build step. One HTML file plus a 25-line service worker. Open it in a text editor, read every line.

How it works

Concern Solution
TOTP Pure Web Crypto HMAC-SHA-{1,256,512}, ~20 lines. Base32 inline, ~15 lines.
KDF PBKDF2-SHA-256, 600,000 iterations
Per-event encryption AES-256-GCM, random 12-byte nonce
Hash chain SHA-256 over previous raw event-line string; genesis for first
Merge Union all device streams, sort by (ts, device_id), per-field last-writer-wins
Storage FileSystemDirectoryHandle (File System Access API) on desktop; navigator.storage.getDirectory() (OPFS) on iOS / mobile
Reconnect FSA handle persisted in IndexedDB (permission re-requested on next visit); OPFS vault name in IndexedDB (no permission needed)
QR flash Archive chunked into TJ1|total|index|b64 frames, Nayuki qrcodegen inlined, receiver via BarcodeDetector API. Out-of-order and duplicate frames are fine; receiver waits for all indices.
Offline Service worker caches app shell (cache-first, no telemetry)
Dependencies Zero
Build step None

Vault format

vault-folder/
  rotor-meta.json                   — plaintext: KDF params, device roster
  rotor-events-<deviceId>.jsonl     — one per device, append-only, hash-chained

Each event line:

{
  "seq": 3,
  "prev_hash": "<sha256-of-previous-line>",
  "ts": "2026-04-24T10:22:31.000Z",
  "device_id": "abc123…",
  "event_type": "entry_created",
  "payload_ct": "<base64-aes-gcm-ciphertext>",
  "nonce": "<base64-12-byte-nonce>"
}

payload_ct is AES-256-GCM ciphertext of the entry payload (JSON). prev_hash is SHA-256 of the preceding raw line string. Tampering any byte breaks the chain — verifiable from Settings → Vault → Verify log integrity.

Event types: device_registered, device_revoked, entry_created, entry_updated, entry_deleted.

Details in BACKUP-FORMAT.md.

Usage

  1. Open index.html in Chrome, Edge, Firefox, or Safari 16.4+.
  2. Create new vault → pick an empty folder (desktop) or name a browser vault (iOS / Android) → set a device name and master password.
  3. + Add code → paste an otpauth:// URI to auto-fill everything, or enter the Base32 secret manually.
  4. Tap any tile to copy the current code. The ring counts down to the next rotation; the small number below is the next code.
  5. Long-press or right-click a tile to pin it to the top.
  6. Back up regularly. Settings → Data → Export encrypted archive → store the .rotor file somewhere safe (a cloud folder is fine — it is AES-encrypted with your master password). On mobile (OPFS) this is critical: clearing site data wipes the vault.
  7. Add a second device — two ways:
    • Share the folder (desktop only). Open the same vault folder from the new browser, enter the master password, device registers itself.
    • QR flash (any device, including iPhone). On the source: Settings → Data → Send vault via QR. On the receiver: Import → QR sequence → point the camera at the screen. Works for both first-time setup and periodic updates.

Sync

Each device writes only its own .jsonl file. Sync is whatever moves files between devices — Rotor never implements a sync protocol, it just merges what it finds. On import, events are deduped by (device_id, seq) and appended, so the same archive is both a full bootstrap and an incremental update: an empty vault receives everything, an existing vault receives only what it's missing.

Air-gapped sync — QR flash

The transport worth calling out: Settings → Data → Send vault via QR. Rotor builds the full encrypted archive in memory, chunks it into TJ1|total|index|b64 frames, and loops an animated QR on screen. On the receiving device (Import → QR sequence), the camera picks up frames with BarcodeDetector, and a grid of dots fills in as each chunk arrives. Out-of-order and duplicate frames are normal — the receiver waits for all indices, reassembles, decrypts, and merges.

Use it for:

  • First-time bootstrap — pair a fresh device with no other infrastructure. Two phones in airplane mode can sync your codes.
  • Periodic updates — after adding or editing codes on device A, re-send. Device B's existing events are deduped; only the new ones are appended.
  • Air-gapped environments — no cloud vendor, no P2P software, no cables. Just two screens and a camera.

Because TOTP secrets are the thing you most want to never leak, QR flash is the ideal transport for Rotor: the secrets never touch the internet, never sit in a cloud bucket encrypted-at-rest, never pass through a vendor's servers.

Supported where BarcodeDetector is available (Chrome, Safari 17+).

Other transports

Transport Notes
Cloud folder (iCloud Drive / Dropbox / Google Drive) Easiest for continuous multi-device use. Each device's browser points at its local copy.
Syncthing P2P, no cloud vendor.
Git Each device's log is a separate file — git merge never produces conflicts on event logs.
USB / manual Export encrypted archive, import on other device. Same .rotor file, same dedup-on-import semantics as QR.

Relationship to Tijori

Rotor and Tijori share the same vault engine — same event-log format, same crypto, same sync transports. They differ only in scope: Tijori stores logins, cards, notes, and codes under one master password; Rotor stores only codes.

Why both exist: TOTP is a second factor. The point of a second factor is to live on a different footing from the first. Combined storage is convenient but a breach of the master password loses both factors at once. Rotor exists for users who want the second factor to stay strictly separate — a different folder, a different master password, a different blast radius.

Format compatibility is a byproduct of engine reuse, not a feature. Do not point both tools at the same vault folder. Use separate folders with separate master passwords.

Self-test

On every boot Rotor runs four RFC 6238 Appendix B test vectors (SHA-1, SHA-256, SHA-512 at T=59, plus SHA-1 at T=1111111109) against its own TOTP implementation and logs PASS / FAIL to the console. Open DevTools on first load to see Rotor TOTP self-test: 4/4 passed.

Why two files

Rotor is a single HTML file in every meaningful sense — index.html contains all the UI, all the logic, the TOTP engine, the vault, the QR codec, everything. The one exception is sw.js, the service worker. It exists because browsers refuse to register a service worker from a blob: or data: URL, and a service worker is the only mechanism that lets Rotor run with the network fully disabled after the first load. sw.js is ~25 lines, has zero logic beyond cache-first routing, and you can read it end-to-end in thirty seconds.

Browser support

  • Desktop — Chrome, Edge, Firefox for the full folder-vault experience (File System Access API).
  • Mobile / iOS — Safari 16.4+, Chrome on iOS. Vault lives in the browser's Origin Private File System instead of a user-visible folder. Export regularly to a desktop vault or via QR sync.

Palette

Coloured with japan-10 · 漆 URUSHI — Wajima lacquerware with kintsugi gold repair. Lacquer-black body, kintsugi-cream ink, cinnabar accent. Same palette as its sibling Hue & Cry; both reference Sanzo Wada and Wajima craft.

Palette pulled from Rangrez, the global colour-palette library that backs all NakliTechie projects.

License

MIT. See LICENSE.


Part of the NakliTechie series — single-file, browser-native, no-backend tools.

About

A TOTP authenticator in one HTML file. No account, no cloud, no sync.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors