{"id":50486921,"url":"https://github.com/systemslibrarian/dad-mode-morse2","last_synced_at":"2026-06-01T23:02:52.441Z","repository":{"id":344106702,"uuid":"1180468480","full_name":"systemslibrarian/dad-mode-morse2","owner":"systemslibrarian","description":"Browser-based demo of encrypted Morse code — AES-GCM encrypted messages transmitted as beep audio and copy/paste Morse. Where 1940s radio meets 2024 authenticated encryption.","archived":false,"fork":false,"pushed_at":"2026-04-06T20:52:46.000Z","size":4577,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-06T21:25:48.062Z","etag":null,"topics":["aead","aes-gcm-encryption","argon2id","ed25519","encryption-decryption","hkdf","morse-code","offline-first","pwa","webcrypto"],"latest_commit_sha":null,"homepage":"https://systemslibrarian.github.io/dad-mode-morse2/","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/systemslibrarian.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-13T04:24:48.000Z","updated_at":"2026-04-06T20:52:51.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/systemslibrarian/dad-mode-morse2","commit_stats":null,"previous_names":["systemslibrarian/dad-morse2"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/systemslibrarian/dad-mode-morse2","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/systemslibrarian%2Fdad-mode-morse2","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/systemslibrarian%2Fdad-mode-morse2/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/systemslibrarian%2Fdad-mode-morse2/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/systemslibrarian%2Fdad-mode-morse2/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/systemslibrarian","download_url":"https://codeload.github.com/systemslibrarian/dad-mode-morse2/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/systemslibrarian%2Fdad-mode-morse2/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33797128,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-01T02:00:06.963Z","response_time":115,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aead","aes-gcm-encryption","argon2id","ed25519","encryption-decryption","hkdf","morse-code","offline-first","pwa","webcrypto"],"created_at":"2026-06-01T23:02:51.491Z","updated_at":"2026-06-01T23:02:52.434Z","avatar_url":"https://github.com/systemslibrarian.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Encrypted Morse Messenger\n\nAn **offline-first** encrypted Morse communication tool. Encrypts messages with\nArgon2id + HKDF + AES-256-GCM, converts the ciphertext to Morse code, and lets\nyou play, download, or QR-share the result — all in the browser, with no server,\nno CDN, and no network calls.\n\nInstallable as a PWA. Works fully offline after first load.\n\n\u003e No servers. No accounts. No CDN dependencies. No data leaves your browser.\n\n---\n\n\u003e [!IMPORTANT]\n\u003e **Note for Morse hobbyists:**\n\u003e This app does **not** use standard alphanumeric Morse for your message. Because\n\u003e the message is encrypted first, the sounds you hear are a Morse-encoded version\n\u003e of the **AES ciphertext in hex**. Typing \"SOS\" will not produce `... --- ...`;\n\u003e it will produce the Morse equivalent of the encrypted hex string.\n\n---\n\n## Live Demo\n\n✅ [https://systemslibrarian.github.io/crypto-lab-dad-mode-morse2/](https://systemslibrarian.github.io/crypto-lab-dad-mode-morse2/)\n\n---\n\n## Full Workflow\n\n```\nSender\n  1. Type message + password (and optional Signal Key) → click Encrypt \u0026 Encode\n  2. App derives key via Argon2id → HKDF → AES-256-GCM encrypts with random salt + IV\n  3. Ciphertext → hex → Morse code → audio beeps\n  4. Share via: Copy Morse, Copy Base64, Download WAV, or scan the QR code\n\nRecipient\n  1. Upload the .wav file (or paste Morse / Base64 / hex)\n  2. Enter the shared password (and Signal Key, if used) → click Decrypt\n  3. If signed, signature is verified BEFORE decryption\n  4. Read the message\n```\n\nThe WAV file is **safe to share publicly** — without the correct password it is\nunreadable. The audio sounds like random beeps to anyone without the key.\n\n---\n\n## Step-by-step\n\n### Sender — Encrypt \u0026 transmit\n\n1. Enter your **message** and a strong **password** (14+ characters or a 4–5 word passphrase).\n2. *(Optional)* Enter a **Signal Key** (pepper) — a second shared secret for extra resistance to offline guessing.\n3. *(Optional)* Enable **Ed25519 Signing** and paste or generate a private key.\n4. Click **Encrypt \u0026 Encode**.\n5. Choose one or more ways to share:\n   - **Copy Morse** — the Morse text string (hex-encoded ciphertext)\n   - **Copy Base64** — compact text encoding for pasting\n   - **Download WAV** — audio file to share over any channel\n   - **QR Code** — scan with any QR reader to get the base64 payload\n   - **Play** — listen to the Morse beeps in-browser\n\n\u003e The **Signal Quality** slider adds radio-style noise to the WAV. Keep it at\n\u003e 100% for reliable decoding by the recipient.\n\n### Recipient — Decode \u0026 decrypt\n\n**From a WAV file:**\n1. Switch to the **Decrypt** tab\n2. Upload the WAV → click **Decode WAV** — the Morse string auto-fills\n3. Enter the shared password (and Signal Key, if used)\n4. *(Optional)* Paste the sender's Ed25519 public key to verify the signature\n5. Click **Decrypt**\n\n**From text (Morse, Base64, or hex):**\n1. Paste into the input field — the app auto-detects the format\n2. Enter password → click **Decrypt**\n\n**Input format detection:**\n- Contains `.` or `-` → treated as Morse → decoded to hex → bytes\n- Only `0-9 A-F` → treated as raw hex → bytes\n- Anything else → treated as Base64 → bytes\n\n**From a QR code image:**\n1. Switch to the **Decrypt** tab\n2. Under \"Upload QR Code Image\", select a screenshot or photo of a QR code\n3. The app reads the QR and fills in the Base64 payload automatically\n4. Enter password → click **Decrypt**\n\n---\n\n## QR Code — What It Does\n\nAfter you encrypt a message, the app generates a **QR code** on screen. This QR code contains the **Base64-encoded ciphertext** — the same encrypted data you'd get from \"Copy Base64\", just in a scannable image.\n\n**Why is this useful?**\n- You can take a **screenshot** of the QR code and text it, email it, or AirDrop it to someone\n- The recipient can **upload that image** into the Decrypt tab — no typing or pasting needed\n- QR codes work even when printed on paper — scan with a phone camera to grab the payload\n- Like the WAV file, the QR code is **safe to share publicly** — without the password, it's unreadable\n\n**How it works under the hood:**\n1. Your message is encrypted → raw bytes\n2. The bytes are encoded as Base64 text (compact, URL-safe)\n3. That Base64 string is turned into a QR code image\n4. On the receiving end, the QR is decoded back to Base64 → bytes → decrypted with your password\n\n**Limits:** QR codes can hold about 4,000 characters. Very long messages may exceed this — the app will tell you and you can use \"Copy Base64\" or \"Download WAV\" instead.\n\nYou can also click **Download QR** to save the QR code as a PNG file.\n\n---\n\n## Cryptography — v5 Container\n\n### Container Format\n\n| Field | Bytes | Description |\n|---|---|---|\n| Magic | 0–3 | `DMM1` (0x44 0x4D 0x4D 0x31) |\n| Version | 4 | `0x05` |\n| Flags | 5 | Bit 0: pepper used; Bit 1: signed |\n| Salt | 6–21 | 16 bytes, CSPRNG |\n| IV / Nonce | 22–33 | 12 bytes, CSPRNG |\n| Signature | 34–97 | 64 bytes, Ed25519 *(only if FLAG_SIGNED)* |\n| Ciphertext | 34+ or 98+ | AES-256-GCM output (includes 16-byte auth tag) |\n\n**Total header: 34 bytes fixed.** Compact, unambiguous, strictly bounds-checked.\n\n### Key Derivation\n\n```\npassword [+ pepper] → Argon2id(t=4, m=64MiB, p=4, 16-byte salt) → 32-byte master key\nmaster key → HKDF-SHA256(salt, \"dmm1/aes-key\") → 256-bit AES key\n```\n\n- **Argon2id** is the primary password-hardening KDF (memory-hard, resists GPU/ASIC attacks)\n- **HKDF** provides domain-separated key derivation from the master material\n- The raw password is never used directly as key material\n- No SHA-256-as-KDF path exists — Argon2id is the only KDF\n\n### Authenticated Encryption\n\n| Property | Value |\n|---|---|\n| Algorithm | AES-256-GCM (128-bit auth tag) |\n| AAD (Additional Authenticated Data) | The 34-byte header |\n| Effect | Any tampering with magic, version, flags, salt, or IV causes GCM authentication failure |\n\nThe header is **not encrypted** but is **authenticated** — modifying any header byte causes decryption to fail, even if the ciphertext is untouched.\n\n**No padding.** AES-GCM is a stream-based AEAD mode that handles arbitrary-length plaintext natively. No PKCS#7 or custom padding is used.\n\n### Ed25519 Digital Signatures (optional)\n\n| Property | Value |\n|---|---|\n| Algorithm | Ed25519 (RFC 8032) via WebCrypto |\n| Key format | PKCS#8 (private), SPKI (public), both base64-encoded |\n| Signed data | `header ‖ ciphertext` (the encrypted container, NOT the plaintext) |\n| Signature location | Embedded in the binary container between header and ciphertext |\n| Verification | **Before** decryption — a failed signature aborts without attempting decrypt |\n\nThis is the correct signing model: sign the ciphertext container, verify before decrypting. The sender's identity is confirmed before any key material is derived.\n\n### Signal Key (Pepper)\n\nAn optional second shared secret. If the sender uses a Signal Key, the recipient **must** enter the same one to decrypt. It is never stored in the payload or transmitted. Combined with the password using length-prefixed domain separation to prevent concatenation collisions.\n\n### Encoding Paths\n\n| Output | Encoding | Alphabet |\n|---|---|---|\n| Morse / WAV / Audio | Hex (2 chars per byte) | `0-9`, `A-F` — matches the 16-symbol Morse table |\n| Base64 / QR / Clipboard | Base64 | `A-Z`, `a-z`, `0-9`, `+`, `/`, `=` |\n\nBoth paths encode the same ciphertext bytes. Hex is used for Morse because the Morse table maps exactly 16 symbols. Base64 is used for QR/clipboard because it's more compact.\n\n### Security Properties\n\n- **Confidentiality** — AES-256-GCM; computationally infeasible to brute-force\n- **Integrity** — GCM authentication tag detects any tampering; wrong password or modified byte always throws, never silently returns garbage\n- **Authenticated metadata** — header bytes bound as AAD; version/flags/salt/IV tampering detected\n- **Semantic security** — random salt + random IV; same plaintext + password produces different ciphertext every time\n- **Memory-hard KDF** — Argon2id with 64 MiB memory cost makes offline brute-force extremely expensive\n- **No key reuse** — fresh salt per message; HKDF derives the AES key from master material\n- **Signature-before-decrypt** — Ed25519 verification happens before any key derivation or decryption\n- **Fully auditable** — all code is in three files; inspect at any time\n\n---\n\n## Offline / PWA Architecture\n\nDad's Morse is a Progressive Web App with zero external dependencies at runtime.\n\n| Component | Status |\n|---|---|\n| Argon2id WASM | Inlined in index.html (base64-bundled, ~46KB) |\n| QR code generator | Inlined in index.html (~57KB) |\n| AES-256-GCM, HKDF, Ed25519 | Native WebCrypto API — built into the browser |\n| Morse audio synthesis | Web Audio API — built into the browser |\n| WAV encode/decode | Raw ArrayBuffer math — no library |\n| Service Worker | Cache-first; caches all assets on install |\n| PWA Manifest | `\"display\": \"standalone\"` — installable as app |\n\n**After first load, the app works with no network at all.** Airplane mode, airgapped machine, no WiFi — everything runs locally.\n\n### Install as App\n\n- **Android Chrome:** Menu → \"Add to Home Screen\"\n- **iOS Safari:** Share → \"Add to Home Screen\"\n- **Desktop Chrome/Edge:** Click install icon in address bar\n\n---\n\n## Files\n\n| File | Purpose |\n|---|---|\n| `index.html` | The entire app — HTML, CSS, JS, and inlined libraries (Argon2 WASM + QR generator) |\n| `sw.js` | Service worker — caches all assets for offline use |\n| `manifest.json` | PWA manifest — enables \"Add to Home Screen\" installation |\n| `turtle.png` | Header image and video poster |\n| `turtle.mp4` | Transmit animation |\n| `test_crypto.mjs` | Encryption/decryption test suite (Node.js, Web Crypto API) |\n| `test_decode.py` | Morse WAV decode test suite (Python 3, stdlib only) |\n\n\u003e The three core files (`index.html`, `sw.js`, `manifest.json`) are the complete\n\u003e app. Everything else is optional. Drop them in any HTTPS-served directory\n\u003e and the app works.\n\n---\n\n## WAV Decoder\n\nThe **Decode WAV** button analyses audio entirely in the browser:\n\n1. Decode audio to PCM (any sample rate, mono or stereo)\n2. Compute RMS energy in 5 ms frames\n3. Threshold at 15% of the 95th-percentile energy → binary tone-on/off\n4. Run-length encode the signal\n5. Estimate the dot (unit) length by finding the largest relative gap in sorted\n   on-durations — handles all-dots, all-dashes, and mixed inputs\n6. Classify each run as `.` `-` letter-gap or word-gap `/`\n7. Output the Morse string into the decrypt field\n\nWorks reliably on WAV files generated by this app at 100% signal quality.\nHeavily degraded (noisy) audio may introduce symbol errors.\n\n---\n\n## Run Locally\n\nYou don't need to install anything complicated. Pick the option that fits your comfort level.\n\n### Option A: Just open the file (simplest)\n\n1. Go to https://github.com/systemslibrarian/crypto-lab-dad-mode-morse2\n2. Click the green **Code** button → **Download ZIP**\n3. Unzip the folder anywhere on your computer\n4. Open the folder and double-click **index.html** — it opens in your browser\n5. That's it. You can encrypt and decrypt messages right away\n\n\u003e **What you'll miss:** The \"Install as App\" button and offline caching won't\n\u003e work when opened this way, because browsers require `https://` or\n\u003e `localhost` for those features. Everything else works fine.\n\n### Option B: Run a local web server (recommended)\n\nThis gives you the full experience — offline mode, \"Add to Home Screen\", and\nservice worker caching. You only need **one** of the tools below.\n\n#### Using Python (built into macOS and most Linux; easy to install on Windows)\n\n1. Download and unzip the repo (same as Option A steps 1–3)\n2. Open a terminal / command prompt\n3. Navigate to the folder:\n   ```bash\n   cd ~/Downloads/crypto-lab-dad-mode-morse2-main   # adjust the path to where you unzipped it\n   ```\n4. Start the server:\n   ```bash\n   python3 -m http.server 8080\n   ```\n5. Open your browser and go to **http://localhost:8080**\n6. To stop the server, press **Ctrl+C** in the terminal\n\n\u003e **Don't have Python?**\n\u003e - **Windows:** Download from https://www.python.org/downloads/ — check \"Add to PATH\" during install\n\u003e - **macOS:** It's pre-installed. Open Terminal and type `python3 --version` to confirm\n\u003e - **Linux:** It's pre-installed on most distros. If not: `sudo apt install python3`\n\n#### Using Node.js\n\n1. Download and unzip the repo\n2. Open a terminal and navigate to the folder\n3. Run:\n   ```bash\n   npx serve .\n   ```\n4. Open **http://localhost:3000** in your browser\n\n\u003e **Don't have Node.js?** Download from https://nodejs.org/ — the LTS version is fine.\n\n### Option C: Clone with Git (for developers)\n\n```bash\ngit clone https://github.com/systemslibrarian/crypto-lab-dad-mode-morse2.git\ncd crypto-lab-dad-mode-morse2\npython3 -m http.server 8080\n# open http://localhost:8080\n```\n\n---\n\n## Offline Integrity (Recommended)\n\nFor maximum trust, download the repo and run it locally with no network.\n\nTip: publish a SHA-256 hash for each release so users can verify `index.html`\nhasn't been tampered with:\n\n```bash\nshasum -a 256 index.html\n# Current: 47b3d5eebc6f99b338e1be4795ee753bfd9eb0faeeeed73f9d3537b12aa0bf1e\n```\n\n---\n\n## Tests\n\n### Encryption tests — `test_crypto.mjs`\n\n\u003e ⚠️ **Test suite needs updating for v5 container format.** Tests currently\n\u003e reference v2 header layout and may need adjustment for the 34-byte v5 header,\n\u003e Argon2id-as-actual-KDF path, AAD-authenticated header, and embedded signature\n\u003e model. Core crypto round-trip tests should still pass.\n\nUses the Web Crypto API built into Node.js 15+. No dependencies required.\n\n```bash\nnode test_crypto.mjs\n```\n\n| # | What is tested |\n|---|---|\n| 1 | DMM1 v5 encrypt + decrypt round-trip (correct password recovers plaintext) |\n| 2 | Wrong password is rejected (AES-GCM auth tag + AAD) |\n| 3 | Random salt + IV — same message/password produces different ciphertext every time |\n| 4 | Hex ↔ Morse conversion is perfectly lossless (all 16 hex digits) |\n| 5 | Full pipeline: encrypt → hex → Morse → hex → decrypt |\n| 6 | Unicode / emoji / CJK characters survive the full round-trip |\n| 7 | Empty string handled without errors |\n| 8 | Long message (1000 chars) round-trips correctly |\n| 9 | Tampered ciphertext is rejected (GCM + AAD integrity) |\n| 10 | DMM1 v5 payload binary layout — 34-byte header + ciphertext + tag |\n| 11 | Morse output contains only valid hex Morse symbols |\n| 12 | All 16 hex characters map to unique, non-overlapping Morse codes |\n| 13 | Signal Key (pepper) encrypt + decrypt round-trip |\n| 14 | Pepper used → decrypt without pepper throws |\n| 15 | Wrong pepper → decrypt fails |\n| 16 | No pepper → FLAG_PEPPER bit is 0 |\n| 17 | With pepper → FLAG_PEPPER bit is 1 |\n| 18 | Tampered header (AAD) is rejected — any header byte change causes GCM failure |\n| 19 | Non-DMM1 payload is rejected |\n| 20 | Argon2id is called as the actual KDF (not SHA-256) |\n| 21 | `concatPwPepper` uses length-prefixed domain separation |\n| 22 | HKDF key separation: `dmm1/aes-key` label produces distinct keys |\n| 23 | Ed25519 key generation produces valid keypair |\n| 24 | Ed25519 sign + verify round-trip (signature over header ‖ ciphertext) |\n| 25 | Ed25519 verification fails with wrong public key |\n| 26 | Ed25519 verification fails with tampered container |\n| 27 | Ed25519 signature is embedded in binary container, not appended as text |\n| 28 | Signature verified before decryption (verify-then-decrypt order) |\n| 29 | Full pipeline with signing: encrypt → sign → Morse → decrypt → verify |\n| 30 | Hex → bytes → hex round-trip is lossless |\n| 31 | Base64 input path produces same bytes as hex input path |\n\n### WAV decode tests — `test_decode.py`\n\nPython 3 standard library only.\n\n```bash\npython3 test_decode.py\n```\n\n### Run all tests\n\n```bash\nnode test_crypto.mjs \u0026\u0026 python3 test_decode.py\n```\n\nBoth suites exit with code `0` on success and `1` on failure.\n\n---\n\n## Security Considerations\n\n- **Use a strong, unique password (14+ chars or a passphrase).** The security of\n  your message depends entirely on the password. Short or common passwords are\n  the only real weakness in this system.\n- **Share the password out-of-band.** Don't send the password in the same\n  channel as the WAV/Morse/QR payload.\n- **Audio duration leaks approximate message length.** WAV duration is\n  proportional to ciphertext length. This is inherent to the Morse-audio\n  transport, not a flaw in the cryptography.\n- **Argon2id** with 64 MiB memory cost makes offline brute-force extremely\n  expensive. Use 14+ random characters for sensitive messages.\n- **The browser is the trust boundary.** All crypto runs via WebCrypto. A\n  compromised browser, malicious extension, or modified HTML file could extract\n  secrets. For maximum trust, verify the file hash and run on a clean machine.\n\n---\n\n## Version History\n\n| Version | Changes |\n|---|---|\n| **v5** (current) | Argon2id as actual KDF (not just declared); HKDF domain separation (`dmm1/aes-key`); header as AAD; no PKCS#7 padding (GCM is stream mode); dead ECDH scaffolding removed; Ed25519 signs `header‖ciphertext` embedded in container; verify-before-decrypt; hex encoding for Morse path (was broken with base64); PWA with inlined Argon2 WASM + QR library; service worker for offline; zero CDN dependencies |\n| **v4** | DMM1 container format; Ed25519 signing; ECDH ephemeral key (unused); SHA-256 as KDF (Argon2id declared but not called); PKCS#7 padding on GCM; base64 fed to hex-only Morse table (lossy) |\n| **v2** | Original DMM1 format; Argon2id via CDN; hex Morse encoding; single-file app |\n\n**v5 is not backward-compatible with v4 or v2 containers.** This is intentional — v5 fixes structural cryptographic issues that could not be patched without breaking the format.\n\n---\n\n## Browser Compatibility\n\nRequires a modern browser with:\n- **WebCrypto API** — AES-GCM, HKDF, Ed25519\n- **WebAssembly** — Argon2id WASM module\n- **Web Audio API** — Morse audio playback\n- **Service Worker** — offline caching (optional, degrades gracefully)\n\nAll evergreen browsers since ~2020: Chrome, Firefox, Safari, Edge.\n\n---\n\n## License\n\n[MIT](LICENSE) — do whatever you like, no warranty.\n\n---\n\n**Dedicated to my Dad — a Navy veteran who knew Morse code.\nWe love you and miss you.**\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsystemslibrarian%2Fdad-mode-morse2","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsystemslibrarian%2Fdad-mode-morse2","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsystemslibrarian%2Fdad-mode-morse2/lists"}