Files
seedpgp-web/README.md
2026-02-12 19:08:46 +08:00

17 KiB

SeedPGP v1.4.5

Secure BIP39 mnemonic backup using PGP encryption and QR codes

A client-side web app for encrypting cryptocurrency seed phrases with OpenPGP and encoding them as QR-friendly Base45 frames with CRC16 integrity checking.

Live App: https://seedpgp-web.pages.dev


Quick Start

🔒 Backup Your Seed (in 30 seconds)

  1. Run locally (recommended for maximum security):

    git clone https://github.com/kccleoc/seedpgp-web.git
    cd seedpgp-web
    bun install
    bun run dev
    # Open http://localhost:5173
    
  2. Enter your 12/24-word BIP39 mnemonic

  3. Choose encryption method:

    • Option A: Upload your PGP public key (.asc file or paste)
    • Option B: Set a strong password (AES-256 encryption)
  4. Click "Generate QR Backup" → Save/print the QR code

🔓 Restore Your Seed

  1. Scan the QR code (camera or upload image)

  2. Provide decryption key:

    • PGP private key + passphrase (if using PGP)
    • Password (if using password encryption)
  3. Mnemonic appears for 10 seconds → auto-clears for security


🛡️ Explicit Threat Model Documentation

🎯 What SeedPGP Protects Against (Security Guarantees)

SeedPGP is designed to protect against specific threats when used correctly:

Threat Protection Implementation Details
Accidental browser storage Real-time monitoring & alerts for localStorage/sessionStorage StorageDetails component shows all browser storage activity
Clipboard exposure Clipboard tracking with warnings and history clearing ClipboardDetails tracks all copy operations, shows what/when
Network leaks Strict CSP headers blocking ALL external requests Cloudflare Pages enforces CSP: default-src 'self'; connect-src 'none'
Wrong-key usage Key fingerprint validation prevents wrong-key decryption OpenPGP.js validates recipient fingerprints before decryption
QR corruption CRC16-CCITT-FALSE checksum detects scanning/printing errors Frame format includes 4-digit hex CRC for integrity verification
Memory persistence Session-key encryption with auto-clear timers AES-GCM-256 session keys, 10-second auto-clear for restored mnemonics
Shoulder surfing Read-only mode blurs sensitive data, disables inputs Toggle blurs content, disables form inputs, prevents clipboard operations

⚠️ Critical Limitations & What SeedPGP CANNOT Protect Against

IMPORTANT: Understand these limitations before trusting SeedPGP with significant funds:

Threat Reason Recommended Mitigation
Browser extensions Malicious extensions can read DOM, memory, keystrokes Use dedicated browser with all extensions disabled; consider browser isolation
Memory analysis JavaScript cannot force immediate memory wiping; strings may persist in RAM Use airgapped device, reboot after use, consider hardware wallets
XSS attacks If hosting server is compromised, malicious JS could be injected Host locally from verified source, use Subresource Integrity (SRI) checks
Hardware keyloggers Physical device compromise at hardware/firmware level Use trusted hardware, consider hardware wallets for large amounts
Supply chain attacks Compromised dependencies (OpenPGP.js, React, etc.) Audit dependencies regularly, verify checksums, consider reproducible builds
Quantum computers Future threat to current elliptic curve cryptography Store encrypted backups physically, rotate periodically, monitor crypto developments
Browser bugs/exploits Zero-day vulnerabilities in browser rendering engine Keep browsers updated, use security-focused browsers (Brave, Tor)
Screen recording Malware or built-in OS screen recording Use privacy screens, be aware of surroundings during sensitive operations
Timing attacks Potential side-channel attacks on JavaScript execution Use constant-time algorithms where possible, though limited in browser context

🔬 Technical Security Architecture

Encryption Stack:

  • PGP Encryption: OpenPGP.js with AES-256 (OpenPGP standard)
  • Session Keys: Web Crypto API AES-GCM-256 with extractable: false
  • Key Derivation: PBKDF2 for password-based keys (when used)
  • Integrity: CRC16-CCITT-FALSE checksums on all frames
  • Encoding: Base45 (RFC 9285) for QR-friendly representation

Memory Management Limitations:

  • JavaScript strings are immutable and may persist in memory after "clearing"
  • Garbage collection timing is non-deterministic and implementation-dependent
  • Browser crash dumps may contain sensitive data in memory
  • The best practice is to minimize exposure time and use airgapped devices

Detailed Memory & Encryption Strategy: See MEMORY_STRATEGY.md for comprehensive documentation on:

  • Why JavaScript cannot guarantee memory zeroing
  • How SeedPGP's defense-in-depth approach mitigates memory risks
  • Optional React hook (useEncryptedState) for encrypting component state
  • Testing & validation procedures
  • Future enhancement recommendations

🏆 Best Practices for Maximum Security

  1. Airgapped Workflow (Recommended for large amounts):

    [Online Device] → Generate PGP keypair → Export public key
    [Airgapped Device] → Run SeedPGP locally → Encrypt with public key
    [Airgapped Device] → Print QR code → Store physically
    [Online Device] → Never touches private key or plaintext seed
    
  2. Local Execution (Next best):

    # Clone and run offline
    git clone https://github.com/kccleoc/seedpgp-web.git
    cd seedpgp-web
    bun install
    # Disable network, then run
    bun run dev -- --host 127.0.0.1
    
  3. Cloudflare Pages (Convenient but trust required):

    • Real CSP enforcement (blocks network at browser level)
    • Security headers (X-Frame-Options, X-Content-Type-Options)
    • ⚠️ Trusts Cloudflare infrastructure
    • ⚠️ Requires HTTPS connection

📚 Simple Usage Examples

Example 1: Password-only Encryption (Simplest)

import { encryptToSeed, decryptFromSeed } from "./lib/seedpgp";

// Backup with password
const mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
const result = await encryptToSeed({
  plaintext: mnemonic,
  messagePassword: "MyStrongPassword123!",
});

console.log(result.framed); // "SEEDPGP1:0:ABCD:BASE45DATA..."

// Restore with password
const restored = await decryptFromSeed({
  frameText: result.framed,
  messagePassword: "MyStrongPassword123!",
});

console.log(restored.w); // Original mnemonic

Example 2: PGP Key Encryption (More Secure)

import { encryptToSeed, decryptFromSeed } from "./lib/seedpgp";

const publicKey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
... your public key here ...
-----END PGP PUBLIC KEY BLOCK-----`;

const privateKey = `-----BEGIN PGP PRIVATE KEY BLOCK-----
... your private key here ...
-----END PGP PRIVATE KEY BLOCK-----`;

// Backup with PGP key
const result = await encryptToSeed({
  plaintext: mnemonic,
  publicKeyArmored: publicKey,
});

// Restore with PGP key
const restored = await decryptFromSeed({
  frameText: result.framed,
  privateKeyArmored: privateKey,
  privateKeyPassphrase: "your-key-password",
});

Example 3: Krux-Compatible Encryption (Hardware Wallet Users)

import { encryptToSeed, decryptFromSeed } from "./lib/seedpgp";

// Krux mode uses passphrase-only encryption
const result = await encryptToSeed({
  plaintext: mnemonic,
  messagePassword: "MyStrongPassphrase",
  mode: 'krux',
  kruxLabel: 'Main Wallet Backup',
  kruxIterations: 200000,
});

// Hex format compatible with Krux firmware
console.log(result.framed); // Hex string starting with KEF:

🔧 Installation & Development

Prerequisites

  • Bun v1.3.6+ (recommended) or Node.js 18+
  • Git

Quick Install

# Clone and install
git clone https://github.com/kccleoc/seedpgp-web.git
cd seedpgp-web
bun install

# Run tests
bun test

# Start development server
bun run dev
# Open http://localhost:5173

Production Build

bun run build          # Build to dist/
bun run preview        # Preview production build

🔐 Advanced Security Features

Session-Key Encryption

  • AES-GCM-256 ephemeral keys for in-memory protection
  • Auto-destroys on tab close/navigation
  • Manual lock/clear button for immediate wiping

Storage Monitoring

  • Real-time tracking of localStorage/sessionStorage
  • Alerts for sensitive data detection
  • Visual indicators of storage usage

Clipboard Protection

  • Tracks all copy operations
  • Shows what was copied and when
  • One-click history clearing

Read-Only Mode

  • Blurs all sensitive data
  • Disables all inputs
  • Prevents clipboard operations
  • Perfect for demonstrations or shared screens

📖 API Reference

Core Functions

encryptToSeed(params)

Encrypts a mnemonic to SeedPGP format.

interface EncryptionParams {
  plaintext: string | SeedPgpPlaintext;  // Mnemonic or plaintext object
  publicKeyArmored?: string;             // PGP public key (optional)
  messagePassword?: string;              // Password (optional)
  mode?: 'pgp' | 'krux';                 // Encryption mode
  kruxLabel?: string;                    // Label for Krux mode
  kruxIterations?: number;               // PBKDF2 iterations for Krux
}

const result = await encryptToSeed({
  plaintext: "your mnemonic here",
  messagePassword: "optional-password",
});
// Returns: { framed: string, pgpBytes?: Uint8Array, recipientFingerprint?: string }

decryptFromSeed(params)

Decrypts a SeedPGP frame.

interface DecryptionParams {
  frameText: string;                     // SEEDPGP1 frame or KEF hex
  privateKeyArmored?: string;            // PGP private key (optional)
  privateKeyPassphrase?: string;         // Key password (optional)
  messagePassword?: string;              // Message password (optional)
  mode?: 'pgp' | 'krux';                 // Encryption mode
}

const plaintext = await decryptFromSeed({
  frameText: "SEEDPGP1:0:ABCD:...",
  messagePassword: "your-password",
});
// Returns: SeedPgpPlaintext { v: 1, t: "bip39", w: string, l: "en", pp: number }

Frame Format

SEEDPGP1:FRAME:CRC16:BASE45DATA
└────────┬────────┘ └──┬──┘ └─────┬─────┘
   Protocol &     Frame  CRC16   Base45-encoded
     Version     Number  Check    PGP Message

Examples:
• SEEDPGP1:0:ABCD:J9ESODB...     # Single frame
• KEF:0123456789ABCDEF...         # Krux Encryption Format (hex)

🚀 Deployment Options

Option 1: Localhost (Most Secure)

# Run on airgapped machine
bun run dev -- --host 127.0.0.1
# Browser only connects to localhost, no external traffic

Option 2: Self-Hosted (Balanced)

  • Build: bun run build
  • Serve dist/ via NGINX/Apache with HTTPS
  • Set CSP headers (see public/_headers)

Option 3: Cloudflare Pages (Convenient)


🧪 Testing & Verification

Test Suite

# Run all tests (unit + integration)
bun test

# Run only unit tests
bun test src/**/*.test.ts

# Run integration tests (CSP, network, clipboard)
bun test:integration

# Run specific test categories
bun test --test-name-pattern="Trezor"  # BIP39 test vectors
bun test --test-name-pattern="CRC"     # Integrity checks
bun test --test-name-pattern="Krux"    # Krux compatibility
bun test --test-name-pattern="CSP Enforcement"  # Security policy tests

# Watch mode (development)
bun test --watch

Test Coverage

  • 20+ comprehensive tests including security and edge cases
  • 8 official Trezor BIP39 test vectors
  • CRC16 integrity validation (corruption detection)
  • CSP enforcement tests (restrictive headers verified)
  • Network blocking tests (all 5 network API mechanisms)
  • Clipboard security tests (auto-clear, event tracking)
  • Session key rotation tests (time + operation limits)
  • Wrong key/password rejection testing

Integration Tests

Security-focused integration tests verify:

CSP Enforcement (src/integration.test.ts)

  • Restrictive CSP headers present in HTML
  • connect-src 'none' blocks all external connections
  • script-src 'self' prevents external script injection
  • Additional security headers (X-Frame-Options, X-Content-Type-Options)

Network Blocking (src/integration.test.ts)

  • User-controlled network toggle blocks 5 API mechanisms:
    1. Fetch API
    2. XMLHttpRequest
    3. WebSocket
    4. Beacon API
    5. Image external resources
    6. Service Worker registration

Clipboard Behavior (src/integration.test.ts)

  • Sensitive field detection (mnemonic, seed, password, private, key)
  • Auto-clear after 10 seconds with random garbage
  • Clipboard event audit trail tracking
  • Warning alerts for sensitive data copies

Session Key Management (src/integration.test.ts)

  • Key rotation every 5 minutes
  • Key rotation after 1000 operations
  • Key destruction with page visibility change
  • AES-256-GCM blob format validation

📁 Project Structure

seedpgp-web/
├── src/
│   ├── components/          # React UI components
│   │   ├── PgpKeyInput.tsx  # PGP key import (drag & drop)
│   │   ├── QrDisplay.tsx    # QR code generation
│   │   ├── QRScanner.tsx    # Camera + file scanning
│   │   ├── SecurityWarnings.tsx # Threat model display
│   │   ├── StorageDetails.tsx   # Storage monitoring
│   │   └── ClipboardDetails.tsx # Clipboard tracking
│   ├── lib/
│   │   ├── seedpgp.ts       # Core encryption/decryption
│   │   ├── sessionCrypto.ts # AES-GCM session key management
│   │   ├── krux.ts          # Krux KEF compatibility
│   │   ├── bip39.ts         # BIP39 validation
│   │   ├── base45.ts        # Base45 encoding/decoding
│   │   └── crc16.ts         # CRC16-CCITT-FALSE checksums
│   ├── App.tsx              # Main application
│   └── main.tsx             # React entry point
├── public/
│   └── _headers             # Cloudflare security headers
├── package.json
├── vite.config.ts
├── RECOVERY_PLAYBOOK.md     # Offline recovery guide
└── README.md                # This file

🔄 Version History

v1.4.5 (2026-02-07)

  • Fixed QR Scanner bugs related to camera initialization and race conditions.
  • Improved error handling in the scanner to prevent crashes and provide better feedback.
  • Stabilized component props to prevent unnecessary re-renders and fix AbortError.

v1.4.4 (2026-02-03)

  • Enhanced security documentation with explicit threat model
  • Improved README with simple examples and best practices
  • Better air-gapped usage guidance for maximum security
  • Version bump with security audit improvements

v1.4.3 (2026-01-30)

  • Fixed textarea contrast for readability
  • Fixed overlapping floating boxes
  • Polished UI with modern crypto wallet design

v1.4.2 (2026-01-30)

  • Migrated to Cloudflare Pages for real CSP enforcement
  • Added "Encrypted in memory" badge
  • Improved security header configuration

v1.4.0 (2026-01-29)

  • Extended session-key encryption to Restore flow
  • Added 10-second auto-clear timer for restored mnemonic
  • Added manual Hide button for immediate clearing

View full version history...


🗺️ Roadmap

Short-term (v1.5.x)

  • Enhanced BIP39 validation (full wordlist + checksum)
  • Multi-frame support for larger payloads
  • Hardware wallet integration (Trezor/Keystone)

Medium-term

  • Shamir Secret Sharing support
  • Mobile companion app (React Native)
  • Printable paper backup templates
  • Encrypted cloud backup with PBKDF2

Long-term

  • BIP85 child mnemonic derivation
  • Quantum-resistant algorithm options
  • Cross-platform desktop app (Tauri)

⚖️ License

MIT License - see LICENSE file for details.

👤 Author

kccleoc - GitHub
Security Audit: v1.4.4 audited for vulnerabilities, no exploits found


⚠️ Important Disclaimer

CRYPTOGRAPHY IS HARD. USE AT YOUR OWN RISK.

This software is provided as-is, without warranty of any kind. Always:

  1. Test with small amounts before trusting with significant funds
  2. Verify decryption works immediately after creating backups
  3. Keep multiple backup copies in different physical locations
  4. Consider professional advice for large cryptocurrency holdings

The author is not responsible for lost funds due to software bugs, user error, or security breaches.


🆘 Getting Help

Remember: Your seed phrase is the key to your cryptocurrency. Guard it with your life.