# 🆘 SeedPGP Offline Recovery Playbook **EMERGENCY SEED RECOVERY WITHOUT THE SEEDPGP WEB APP** --- ## 📋 What This Document Is For You created an encrypted backup of your cryptocurrency seed phrase using SeedPGP. This document explains **how to decrypt that backup if the SeedPGP web app is no longer available** (website down, GitHub deleted, domain expired, etc.). **Print this document and store it with your encrypted QR backup.** --- ## 🎯 Quick Reference: What You Need Depending on how you encrypted your backup, you need: | Encryption Method | What You Need to Decrypt | |-------------------|--------------------------| | **Password-only** | Password + this playbook + any computer with GPG | | **PGP Public Key** | Private key + private key passphrase + this playbook + any computer with GPG | | **Krux KEF format** | Passphrase + Python 3 + this playbook | --- ## 🔍 Step 1: Identify Your Backup Format Look at your encrypted backup. The format determines which recovery method to use: ### **Format A: SeedPGP Standard (PGP)** Your QR code or text starts with: ``` SEEDPGP1:0:A1B2:CDEFG... ``` **OR** your backup is a PGP armored message: ``` -----BEGIN PGP MESSAGE----- hQEMA... -----END PGP MESSAGE----- ``` ➜ **Use Method 1: GPG Command-Line Recovery** (see below) --- ### **Format B: Krux KEF** Your QR code or text starts with: ``` KEF:1234+ABCD... ``` **OR** it's a Base43-encoded string using only these characters: ``` 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ$%*+-./: ``` ➜ **Use Method 2: Python Krux Decryption** (see below) --- ### **Format C: Plain SeedQR** (NOT ENCRYPTED) Your QR is all digits (48 or 96 digits for 12/24 words): ``` 0216007100420461... ``` **OR** all hex (32 or 64 hex characters): ``` 1a2b3c4d5e6f... ``` ➜ **Use Method 3: SeedQR Decoder** (see below) --- ## 📖 Method 1: GPG Command-Line Recovery (PGP Format) ### **What You Need:** - ✅ Your encrypted backup (QR scan result or PGP armored text) - ✅ Your password (if password-encrypted) - ✅ Your PGP private key + passphrase (if key-encrypted) - ✅ A computer with GPG installed (Linux/Mac: pre-installed, Windows: download Gpg4win) --- ### **Step 1A: Extract the PGP Message** If your backup is a `SEEDPGP1:...` QR string, you need to decode it first: ```bash # Your QR scan result looks like: # SEEDPGP1:0:A1B2:BASE45_ENCODED_DATA_HERE # Extract just the Base45 part (everything after the third colon) echo "SEEDPGP1:0:A1B2:BASE45DATA" | cut -d: -f4- > base45.txt # Decode Base45 to binary PGP (requires Python script - see Appendix A) python3 decode_base45.py base45.txt > encrypted.pgp ``` If your backup is already a PGP armored message (`-----BEGIN PGP MESSAGE-----`), save it to a file: ```bash cat > encrypted.asc << 'EOF' -----BEGIN PGP MESSAGE----- hQEMA... -----END PGP MESSAGE----- EOF ``` --- ### **Step 1B: Decrypt with GPG** **If encrypted with PASSWORD only:** ```bash # Decrypt using password gpg --decrypt encrypted.pgp # OR if you have the armored version: gpg --decrypt encrypted.asc # GPG will prompt: "Enter passphrase:" # Type your password exactly as you created it # Output will be JSON like: {"v":1,"t":"bip39","w":"word1 word2 word3...","l":"en","pp":0} ``` **If encrypted with PGP PUBLIC KEY:** ```bash # First, import your private key (if not already in your GPG keyring) gpg --import my-private-key.asc # Decrypt gpg --decrypt encrypted.pgp # GPG will prompt for your PRIVATE KEY PASSPHRASE (not the backup password) # Output will be JSON like: {"v":1,"t":"bip39","w":"word1 word2 word3...","l":"en","pp":0} ``` --- ### **Step 1C: Extract Your Seed Phrase** The decrypted output is JSON format: ```json {"v":1,"t":"bip39","w":"abandon ability able about above...","l":"en","pp":0} ``` **Your seed phrase is the value of the `"w"` field.** Extract it: ```bash # If output is in a file: cat decrypted.json | grep -o '"w":"[^"]*"' | cut -d'"' -f4 # Or use Python: python3 -c 'import json; print(json.load(open("decrypted.json"))["w"])' ``` **Write down your seed phrase immediately on paper.** --- ### **JSON Field Meanings:** | Field | Meaning | Example Value | |-------|---------|---------------| | `v` | Format version | `1` | | `t` | Mnemonic type | `"bip39"` | | `w` | **Your seed phrase (words)** | `"abandon ability able..."` | | `l` | Language | `"en"` (English) | | `pp` | BIP39 passphrase used? | `0` (no) or `1` (yes) | | `fpr` | Recipient PGP fingerprints | `["ABC123..."]` (optional) | **If `pp` is `1`:** You used a BIP39 passphrase in addition to your seed words. You need BOTH to restore your wallet. --- ## 🐍 Method 2: Python Krux Decryption (KEF Format) ### **What You Need:** - ✅ Your Krux KEF backup (QR scan result starting with `KEF:` or Base43 string) - ✅ Your passphrase - ✅ A computer with Python 3 --- ### **Step 2A: Prepare the Decryption Script** Save this Python script as `decrypt_krux.py`: ```python #!/usr/bin/env python3 """ Krux KEF (Krux Encryption Format) Offline Decryption Tool For emergency recovery when SeedPGP is unavailable """ import hashlib import hmac from getpass import getpass # Base43 alphabet (Krux standard) B43_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ$%*+-./:" def base43_decode(s): """Decode Base43 string to bytes""" n = 0 for c in s: n = n * 43 + B43_CHARS.index(c) byte_len = (n.bit_length() + 7) // 8 return n.to_bytes(byte_len, 'big') def unwrap_kef(kef_bytes): """Extract label, version, iterations, and payload from KEF envelope""" if len(kef_bytes) < 5: raise ValueError("Invalid KEF: too short") label_len = kef_bytes[0] if label_len > 252 or len(kef_bytes) < 1 + label_len + 4: raise ValueError("Invalid KEF: malformed header") label = kef_bytes[1:1+label_len].decode('utf-8') version = kef_bytes[1+label_len] iter_bytes = kef_bytes[2+label_len:5+label_len] iterations = int.from_bytes(iter_bytes, 'big') if iterations <= 10000: iterations *= 10000 payload = kef_bytes[5+label_len:] return label, version, iterations, payload def pbkdf2_hmac_sha256(password, salt, iterations, dklen=32): """PBKDF2-HMAC-SHA256 key derivation""" return hashlib.pbkdf2_hmac('sha256', password.encode(), salt, iterations, dklen) def aes_gcm_decrypt(key, iv, ciphertext, tag): """AES-GCM decryption using cryptography library""" try: from cryptography.hazmat.primitives.ciphers.aead import AESGCM aesgcm = AESGCM(key) return aesgcm.decrypt(iv, ciphertext + tag, None) except ImportError: print("ERROR: 'cryptography' library not found.") print("Install with: pip3 install cryptography") raise def entropy_to_mnemonic(entropy_bytes): """Convert entropy to BIP39 mnemonic (requires bip39 library)""" try: from mnemonic import Mnemonic mnemo = Mnemonic("english") return mnemo.to_mnemonic(entropy_bytes) except ImportError: print("ERROR: 'mnemonic' library not found.") print("Install with: pip3 install mnemonic") raise def decrypt_krux(kef_data, passphrase): """Main decryption function""" # Step 1: Decode from Base43 or hex if kef_data.startswith('KEF:'): kef_data = kef_data[4:].strip() try: kef_bytes = base43_decode(kef_data) except: try: kef_bytes = bytes.fromhex(kef_data) except: raise ValueError("Invalid KEF format: not Base43 or hex") # Step 2: Unwrap KEF envelope label, version, iterations, payload = unwrap_kef(kef_bytes) if version not in [20, 21]: raise ValueError(f"Unsupported KEF version: {version}") print(f"KEF Label: {label}") print(f"Version: {version} (AES-GCM{' +compress' if version == 21 else ''})") print(f"Iterations: {iterations}") # Step 3: Derive key from passphrase salt = label.encode('utf-8') key = pbkdf2_hmac_sha256(passphrase, salt, iterations, 32) # Step 4: Extract IV, ciphertext, and tag iv = payload[:12] ciphertext = payload[12:-4] tag = payload[-4:] # Step 5: Decrypt decrypted = aes_gcm_decrypt(key, iv, ciphertext, tag) # Step 6: Decompress if needed if version == 21: import zlib decrypted = zlib.decompress(decrypted) # Step 7: Convert to mnemonic mnemonic = entropy_to_mnemonic(decrypted) return mnemonic if __name__ == "__main__": print("=" * 60) print("KRUX KEF EMERGENCY DECRYPTION TOOL") print("=" * 60) print() kef_input = input("Paste your KEF backup (Base43 or hex): ").strip() passphrase = getpass("Enter passphrase: ") try: mnemonic = decrypt_krux(kef_input, passphrase) print() print("=" * 60) print("SUCCESS! Your seed phrase:") print("=" * 60) print(mnemonic) print("=" * 60) print() print("⚠️ Write this down on paper immediately!") print("⚠️ Never save to disk or take a screenshot!") except Exception as e: print() print(f"ERROR: {e}") print() print("Common issues:") print("1. Wrong passphrase") print("2. Missing Python libraries (run: pip3 install cryptography mnemonic)") print("3. Corrupted KEF data") ``` --- ### **Step 2B: Install Dependencies** ```bash # Install required Python libraries pip3 install cryptography mnemonic # Or on Ubuntu/Debian: sudo apt install python3-pip pip3 install cryptography mnemonic ``` --- ### **Step 2C: Run the Decryption** ```bash # Make script executable chmod +x decrypt_krux.py # Run it python3 decrypt_krux.py # It will prompt: # "Paste your KEF backup (Base43 or hex):" # → Paste your full QR scan result or KEF string # "Enter passphrase:" # → Type your passphrase (won't show on screen) # Output: # SUCCESS! Your seed phrase: # abandon ability able about above absent absorb... ``` **Write down your seed phrase immediately.** --- ## 📱 Method 3: SeedQR Decoder (Unencrypted Format) ### **What You Need:** - ✅ Your SeedQR backup (all digits or hex) - ✅ BIP39 wordlist (see Appendix B) - ✅ Optional: Python script (see below) --- ### **Step 3A: Identify SeedQR Type** **Standard SeedQR (all digits):** - 48 digits = 12-word seed - 96 digits = 24-word seed - Each 4 digits = one BIP39 word index (0000-2047) **Compact SeedQR (hex):** - 32 hex chars = 12-word seed - 64 hex chars = 24-word seed - Raw entropy encoded as hexadecimal --- ### **Step 3B: Manual Decoding (Standard SeedQR)** **Example:** `0216007100420461...` (48 digits for 12 words) 1. Split into 4-digit chunks: `0216`, `0071`, `0042`, `0461`, ... 2. Each chunk is a word index (0-2047) 3. Look up each index in the BIP39 wordlist (Appendix B) ``` 0216 → word #216 = "brick" 0071 → word #71 = "appear" 0042 → word #42 = "advise" 0461 → word #461 = "dove" ... ``` **Your seed phrase is the words in order.** --- ### **Step 3C: Python Script (Standard SeedQR)** Save as `decode_seedqr.py`: ```python #!/usr/bin/env python3 """SeedQR to BIP39 Mnemonic Decoder""" # BIP39 English wordlist (2048 words) # Download from: https://github.com/bitcoin/bips/blob/master/bip-0039/english.txt # Or see Appendix B of this document def load_wordlist(filepath='bip39_wordlist.txt'): with open(filepath) as f: return [line.strip() for line in f] def decode_standard_seedqr(qr_digits): """Decode standard SeedQR (4-digit word indices)""" if len(qr_digits) not in [48, 96]: raise ValueError(f"Invalid length: {len(qr_digits)} (expected 48 or 96)") wordlist = load_wordlist() mnemonic = [] for i in range(0, len(qr_digits), 4): index = int(qr_digits[i:i+4]) if index >= 2048: raise ValueError(f"Invalid word index: {index} (max 2047)") mnemonic.append(wordlist[index]) return ' '.join(mnemonic) def decode_compact_seedqr(qr_hex): """Decode compact SeedQR (hex-encoded entropy)""" if len(qr_hex) not in [32, 64]: raise ValueError(f"Invalid hex length: {len(qr_hex)} (expected 32 or 64)") try: from mnemonic import Mnemonic mnemo = Mnemonic("english") entropy = bytes.fromhex(qr_hex) return mnemo.to_mnemonic(entropy) except ImportError: print("ERROR: 'mnemonic' library required for compact SeedQR") print("Install: pip3 install mnemonic") raise if __name__ == "__main__": qr_data = input("Paste your SeedQR data: ").strip() if qr_data.isdigit(): mnemonic = decode_standard_seedqr(qr_data) elif all(c in '0123456789abcdefABCDEF' for c in qr_data): mnemonic = decode_compact_seedqr(qr_data) else: print("ERROR: Not a valid SeedQR (must be all digits or all hex)") exit(1) print() print("Your seed phrase:") print(mnemonic) print() ``` --- ## 📚 Appendix A: Base45 Decoder If you need to decode SeedPGP's Base45 format manually, save this as `decode_base45.py`: ```python #!/usr/bin/env python3 """Base45 Decoder for SeedPGP Recovery""" B45_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:" def base45_decode(s): """Decode Base45 string to bytes""" result = [] i = 0 while i < len(s): if i + 2 < len(s): # Process 3 characters → 2 bytes c = B45_CHARS.index(s[i]) d = B45_CHARS.index(s[i+1]) e = B45_CHARS.index(s[i+2]) x = c + d * 45 + e * 45 * 45 result.append(x // 256) result.append(x % 256) i += 3 else: # Process 2 characters → 1 byte c = B45_CHARS.index(s[i]) d = B45_CHARS.index(s[i+1]) x = c + d * 45 if x > 255: raise ValueError("Invalid Base45 encoding") result.append(x) i += 2 return bytes(result) if __name__ == "__main__": import sys if len(sys.argv) > 1: # Read from file with open(sys.argv[1]) as f: b45_input = f.read().strip() else: # Read from stdin b45_input = input("Paste Base45 data: ").strip() try: decoded = base45_decode(b45_input) sys.stdout.buffer.write(decoded) except Exception as e: print(f"ERROR: {e}", file=sys.stderr) exit(1) ``` --- ## 📚 Appendix B: BIP39 English Wordlist **Download the official wordlist:** ```bash wget https://raw.githubusercontent.com/bitcoin/bips/master/bip-0039/english.txt ``` **Or manually:** The BIP39 English wordlist contains exactly 2048 words, indexed 0-2047: ``` 0000: abandon 0001: ability 0002: able 0003: about ... 2045: zero 2046: zone 2047: zoo ``` **Full wordlist:** https://github.com/bitcoin/bips/blob/master/bip-0039/english.txt --- ## 🛡️ Security Recommendations ### **When Recovering Your Seed:** 1. ✅ **Use an air-gapped computer** (TailsOS or Ubuntu Live USB) 2. ✅ **Disconnect from internet** before decrypting 3. ✅ **Write seed on paper immediately** after decryption 4. ✅ **Never screenshot or save to disk** 5. ✅ **Verify your seed** by importing to a test wallet (small amount first) 6. ✅ **Destroy digital traces** after recovery (shutdown amnesic OS) ### **Storage Best Practices:** - 📄 **Print this document** and store with your encrypted backup - 🔐 Store backup + recovery instructions in different locations - 💾 Keep a copy of Python scripts on offline USB - 📦 Include a copy of the BIP39 wordlist (offline reference) - 🗂️ Label everything clearly: "SEEDPGP BACKUP + RECOVERY GUIDE - DO NOT LOSE" --- ## 🆘 Emergency Contact **If you're stuck:** 1. Search GitHub for "seedpgp-web" (project may still exist) 2. Check Internet Archive: https://web.archive.org/ 3. Ask in Bitcoin/crypto forums (describe format, don't share actual data!) 4. Hire a professional cryptocurrency recovery service (last resort) **Never share:** - ❌ Your encrypted backup data with strangers - ❌ Your password or passphrase - ❌ Your PGP private key - ❌ Your decrypted seed phrase --- ## ✅ Recovery Checklist Before attempting recovery, verify you have: - [ ] This printed playbook - [ ] Your encrypted backup (QR code or text file) - [ ] Your password/passphrase written down - [ ] Your PGP private key (if used) + passphrase - [ ] An air-gapped computer (TailsOS/Ubuntu Live recommended) - [ ] GPG installed (for PGP decryption) - [ ] Python 3 + libraries (for Krux/SeedQR decryption) - [ ] BIP39 wordlist (for manual SeedQR decoding) - [ ] Paper and pen (to write recovered seed) **If missing any item above, DO NOT PROCEED. Secure it first.** --- ## 📅 Recommended Practice Schedule **Every 6 months:** 1. Test that you can still decrypt your backup 2. Verify the recovery tools still work 3. Update this playbook if formats change 4. Check that your passwords/keys are still accessible **Test with a dummy backup first!** Create a test seed, encrypt it, then practice recovery. --- **Document Version:** 1.0 **Last Updated:** February 2026 **Compatible with:** SeedPGP v1.4.7+ --- **🔒 KEEP THIS DOCUMENT SAFE AND ACCESSIBLE 🔒** Your encrypted backup is worthless without the ability to decrypt it. Print this. Store it with your backup. Test it regularly. ---