mirror of
https://github.com/kccleoc/seedpgp-web.git
synced 2026-03-07 09:57:50 +08:00
feat: add comprehensive recovery playbook and update documentation
- Added RECOVERY_PLAYBOOK.md with complete offline recovery guide - Updated README.md to reference manual restore method - Added RECOVERY_PLAYBOOK.md to project structure - Removed test.pgp file
This commit is contained in:
422
RECOVERY_PLAYBOOK.md
Normal file
422
RECOVERY_PLAYBOOK.md
Normal file
@@ -0,0 +1,422 @@
|
||||
## SeedPGP Recovery Playbook - Offline Recovery Guide
|
||||
|
||||
**Generated:** Feb 1, 2026 | **SeedPGP v1.4.3** | **Frame Format:** `SEEDPGP1:0:CRC16:BASE45_PAYLOAD`
|
||||
|
||||
***
|
||||
|
||||
## 📋 Recovery Requirements
|
||||
|
||||
```
|
||||
✅ SEEDPGP1 QR code or printed text
|
||||
✅ PGP Private Key (.asc file) OR Message Password (if symmetric encryption used)
|
||||
✅ Offline computer with terminal access
|
||||
✅ gpg command line tool (GNU Privacy Guard)
|
||||
```
|
||||
|
||||
**⚠️ Important:** This playbook assumes you have the original encryption parameters:
|
||||
|
||||
- PGP private key (if PGP encryption was used)
|
||||
- Private key passphrase (if the key is encrypted)
|
||||
- Message password (if symmetric encryption was used)
|
||||
- BIP39 passphrase (if 25th word was used during backup)
|
||||
|
||||
***
|
||||
|
||||
## 🔓 Step 1: Understand Frame Format
|
||||
|
||||
**SeedPGP Frame Structure:**
|
||||
|
||||
```
|
||||
SEEDPGP1:0:CRC16:BASE45_PAYLOAD
|
||||
```
|
||||
|
||||
- **SEEDPGP1:** Protocol identifier
|
||||
- **0:** Frame version (single frame)
|
||||
- **CRC16:** 4-character hexadecimal CRC16-CCITT checksum
|
||||
- **BASE45_PAYLOAD:** Base45-encoded PGP binary data
|
||||
|
||||
**Example Frame:**
|
||||
|
||||
```
|
||||
SEEDPGP1:0:58B5:2KO K0S-U. M:E1T*A%50%886N2SDITXSQVE VV$BA7.FZ+I01N%ISK$KBGESBRNOHYIK%A8N1FUOE.Z1T:8JBHDNNBV2AVJRGC1-OY67AU777I07UB88TQN0B5033IJOGG7$2ID/QNIR.:UGUO/M0BH0O94468TXM 0RGSIYT FNSQGNJKDCHP3JV/V-77:%KVZG+6VA7P826W0N0TBI5AMSQX60A%2E$OMWF1TV/J0SJJ 0M-VF0TH60W4TL1/519HS7BO%OT-QGZ5.AS.18AWSGF9O5E%MCYLM4STPI5+.3A5K7ZULFQM.JO:J3/C.IOB1819L8*ME027S9DJ0+18WCVTC30928T72W5D4P0UHC4O11IPRQ I5T39RSI9BTVT6LK6A9PWUF7B2CBEI43M%TT47%I4KBT-0H44L.RP$U02F8-7A*LH2$G44Q.880WF0BJ5SB5OR*39W/N3T9 -DQ4C
|
||||
```
|
||||
|
||||
### Extract Base45 Payload
|
||||
|
||||
```bash
|
||||
# Extract everything after the 3rd colon
|
||||
FRAME="SEEDPGP1:0:58B5:2KO K0S-U. M:E1T*A%50%886N2SDITXSQVE VV$BA7.FZ+I01N%ISK$KBGESBRNOHYIK%A8N1FUOE.Z1T:8JBHDNNBV2AVJRGC1-OY67AU777I07UB88TQN0B5033IJOGG7$2ID/QNIR.:UGUO/M0BH0O94468TXM 0RGSIYT FNSQGNJKDCHP3JV/V-77:%KVZG+6VA7P826W0N0TBI5AMSQX60A%2E$OMWF1TV/J0SJJ 0M-VF0TH60W4TL1/519HS7BO%OT-QGZ5.AS.18AWSGF9O5E%MCYLM4STPI5+.3A5K7ZULFQM.JO:J3/C.IOB1819L8*ME027S9DJ0+18WCVTC30928T72W5D4P0UHC4O11IPRQ I5T39RSI9BTVT6LK6A9PWUF7B2CBEI43M%TT47%I4KBT-0H44L.RP$U02F8-7A*LH2$G44Q.880WF0BJ5SB5OR*39W/N3T9 -DQ4C"
|
||||
PAYLOAD=$(echo "$FRAME" | cut -d: -f4-)
|
||||
echo "$PAYLOAD" > payload.b45
|
||||
```
|
||||
|
||||
***
|
||||
|
||||
## 🔓 Step 2: Decode Base45 → PGP Binary
|
||||
|
||||
**Option A: Using base45 CLI tool:**
|
||||
|
||||
```bash
|
||||
# Install base45 if needed
|
||||
npm install -g base45
|
||||
|
||||
# Decode the payload
|
||||
base45decode < payload.b45 > encrypted.pgp
|
||||
```
|
||||
|
||||
**Option B: Using CyberChef (offline browser tool):**
|
||||
|
||||
1. Download CyberChef HTML from <https://gchq.github.io/CyberChef/>
|
||||
2. Open it in an offline browser
|
||||
3. Input → Paste your Base45 payload
|
||||
4. Operation → `From Base45`
|
||||
5. Save output as `encrypted.pgp`
|
||||
|
||||
**Option C: Manual verification (check CRC):**
|
||||
|
||||
```bash
|
||||
# Verify CRC16 checksum matches
|
||||
# The CRC16-CCITT-FALSE checksum should match the value in the frame (58B5 in example)
|
||||
# If using the web app, this is automatically verified during decryption
|
||||
```
|
||||
|
||||
***
|
||||
|
||||
## 🔓 Step 3: Decrypt PGP Binary
|
||||
|
||||
### Option A: PGP Private Key Decryption (PKESK)
|
||||
|
||||
If the backup was encrypted with a PGP public key:
|
||||
|
||||
```bash
|
||||
# Import your private key (if not already imported)
|
||||
gpg --import private-key.asc
|
||||
|
||||
# List keys to verify fingerprint
|
||||
gpg --list-secret-keys --keyid-format LONG
|
||||
|
||||
# Decrypt using your private key
|
||||
gpg --batch --yes --decrypt encrypted.pgp
|
||||
```
|
||||
|
||||
**Expected JSON Output:**
|
||||
|
||||
```json
|
||||
{"v":1,"t":"bip39","w":"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about","l":"en","pp":0}
|
||||
```
|
||||
|
||||
**If private key has a passphrase:**
|
||||
|
||||
```bash
|
||||
gpg --batch --yes --passphrase "YOUR-PGP-KEY-PASSPHRASE" --decrypt encrypted.pgp
|
||||
```
|
||||
|
||||
### Option B: Message Password Decryption (SKESK)
|
||||
|
||||
If the backup was encrypted with a symmetric password:
|
||||
|
||||
```bash
|
||||
gpg --batch --yes --passphrase "YOUR-MESSAGE-PASSWORD" --decrypt encrypted.pgp
|
||||
```
|
||||
|
||||
**Expected JSON Output:**
|
||||
|
||||
```json
|
||||
{"v":1,"t":"bip39","w":"your seed phrase words here","l":"en","pp":1}
|
||||
```
|
||||
|
||||
***
|
||||
|
||||
## 🔓 Step 4: Parse Decrypted Data
|
||||
|
||||
The decrypted output is a JSON object with the following structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"v": 1, // Version (always 1)
|
||||
"t": "bip39", // Type (always "bip39")
|
||||
"w": "word1 word2 ...", // BIP39 mnemonic words (lowercase, single spaces)
|
||||
"l": "en", // Language (always "en" for English)
|
||||
"pp": 0 // BIP39 passphrase flag: 0 = no passphrase, 1 = passphrase used
|
||||
}
|
||||
```
|
||||
|
||||
**Extract the mnemonic:**
|
||||
|
||||
```bash
|
||||
# After decryption, extract the 'w' field
|
||||
DECRYPTED='{"v":1,"t":"bip39","w":"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about","l":"en","pp":0}'
|
||||
MNEMONIC=$(echo "$DECRYPTED" | grep -o '"w":"[^"]*"' | cut -d'"' -f4)
|
||||
echo "Mnemonic: $MNEMONIC"
|
||||
```
|
||||
|
||||
***
|
||||
|
||||
## 💰 Step 5: Wallet Recovery
|
||||
|
||||
### BIP39 Passphrase Status
|
||||
|
||||
Check the `pp` field in the decrypted JSON:
|
||||
|
||||
- `"pp": 0` → No BIP39 passphrase was used during backup
|
||||
- `"pp": 1` → **BIP39 passphrase was used** (25th word/extra passphrase)
|
||||
|
||||
### Recovery Instructions
|
||||
|
||||
**Without BIP39 Passphrase (`pp": 0`):**
|
||||
|
||||
```
|
||||
Seed Words: [extracted from 'w' field]
|
||||
BIP39 Passphrase: None required
|
||||
```
|
||||
|
||||
**With BIP39 Passphrase (`pp": 1`):**
|
||||
|
||||
```
|
||||
Seed Words: [extracted from 'w' field]
|
||||
BIP39 Passphrase: [Your original 25th word/extra passphrase]
|
||||
```
|
||||
|
||||
**Wallet Recovery Steps:**
|
||||
|
||||
1. **Hardware Wallets (Ledger/Trezor):**
|
||||
- Start recovery process
|
||||
- Enter 12/24 word mnemonic
|
||||
- **If `pp": 1`:** Enable passphrase option and enter your BIP39 passphrase
|
||||
|
||||
2. **Software Wallets (Electrum, MetaMask, etc.):**
|
||||
- Create/restore wallet
|
||||
- Enter mnemonic phrase
|
||||
- **If `pp": 1`:** Look for "Advanced options" or "Passphrase" field
|
||||
|
||||
3. **Bitcoin Core (using `hdseed`):**
|
||||
|
||||
```bash
|
||||
# Use the mnemonic with appropriate BIP39 passphrase
|
||||
# Consult your wallet's specific recovery documentation
|
||||
```
|
||||
|
||||
***
|
||||
|
||||
## 🛠️ GPG Setup (One-time)
|
||||
|
||||
**Mac (Homebrew):**
|
||||
|
||||
```bash
|
||||
brew install gnupg
|
||||
```
|
||||
|
||||
**Ubuntu/Debian:**
|
||||
|
||||
```bash
|
||||
sudo apt update && sudo apt install gnupg
|
||||
```
|
||||
|
||||
**Fedora/RHEL/CentOS:**
|
||||
|
||||
```bash
|
||||
sudo dnf install gnupg
|
||||
```
|
||||
|
||||
**Windows:**
|
||||
|
||||
- Download Gpg4win from <https://www.gpg4win.org/>
|
||||
- Install and use Kleopatra or command-line gpg
|
||||
|
||||
**Verify installation:**
|
||||
|
||||
```bash
|
||||
gpg --version
|
||||
```
|
||||
|
||||
***
|
||||
|
||||
## 🔍 Troubleshooting
|
||||
|
||||
| Error | Likely Cause | Solution |
|
||||
|-------|-------------|----------|
|
||||
| `gpg: decryption failed: No secret key` | Wrong PGP private key or key not imported | Import correct private key: `gpg --import private-key.asc` |
|
||||
| `gpg: BAD decrypt` | Wrong passphrase (key passphrase or message password) | Verify you're using the correct passphrase |
|
||||
| `base45decode: command not found` | base45 CLI tool not installed | Use CyberChef or install: `npm install -g base45` |
|
||||
| `gpg: no valid OpenPGP data found` | Invalid Base45 decoding or corrupted payload | Verify Base45 decoding step, check for scanning errors |
|
||||
| `gpg: CRC error` | Frame corrupted during scanning/printing | Rescan QR code or use backup copy |
|
||||
| `gpg: packet(3) too short` | Truncated PGP binary | Ensure complete frame was captured |
|
||||
| JSON parsing error after decryption | Output not valid JSON | Check if decryption succeeded, may need different passphrase |
|
||||
|
||||
**Common Issues:**
|
||||
|
||||
1. **Wrong encryption method:** Trying PGP decryption when symmetric password was used, or vice versa
|
||||
2. **BIP39 passphrase mismatch:** Forgetting the 25th word used during backup
|
||||
3. **Frame format errors:** Missing `SEEDPGP1:` prefix or incorrect colon separation
|
||||
|
||||
***
|
||||
|
||||
## 📦 Recovery Checklist
|
||||
|
||||
```
|
||||
[ ] Airgapped computer prepared (offline, clean OS)
|
||||
[ ] GPG installed and verified
|
||||
[ ] Base45 decoder available (CLI tool or CyberChef)
|
||||
[ ] SEEDPGP1 frame extracted and verified
|
||||
[ ] Base45 payload decoded to PGP binary
|
||||
[ ] CRC16 checksum verified (optional but recommended)
|
||||
[ ] Correct decryption method identified (PGP key vs password)
|
||||
[ ] Private key imported (if PGP encryption)
|
||||
[ ] Decryption successful with valid JSON output
|
||||
[ ] Mnemonic extracted from 'w' field
|
||||
[ ] BIP39 passphrase status checked ('pp' field)
|
||||
[ ] Appropriate BIP39 passphrase ready (if 'pp': 1)
|
||||
[ ] Wallet recovery tool selected (hardware/software wallet)
|
||||
[ ] Test recovery on testnet/small amount first
|
||||
[ ] Browser/terminal history cleared after recovery
|
||||
[ ] Original backup securely stored or destroyed after successful recovery
|
||||
[ ] Funds moved to new addresses after recovery
|
||||
```
|
||||
|
||||
***
|
||||
|
||||
## ⚠️ Security Best Practices
|
||||
|
||||
**Critical Security Measures:**
|
||||
|
||||
1. **Always use airgapped computer** for recovery operations
|
||||
2. **Never type mnemonics or passwords on internet-connected devices**
|
||||
3. **Clear clipboard and terminal history** after recovery
|
||||
4. **Test with small amounts** before recovering significant funds
|
||||
5. **Move funds to new addresses** after successful recovery
|
||||
6. **Destroy recovery materials** or store them separately from private keys
|
||||
|
||||
**Storage Recommendations:**
|
||||
|
||||
- Print QR code on archival paper or metal
|
||||
- Store playbook separately from private keys/passphrases
|
||||
- Use multiple geographically distributed backups
|
||||
- Consider Shamir's Secret Sharing for critical components
|
||||
|
||||
***
|
||||
|
||||
## 🔄 Alternative Recovery Methods
|
||||
|
||||
**Using the SeedPGP Web App (Online):**
|
||||
|
||||
1. Open <https://seedpgp.com> (or local instance)
|
||||
2. Switch to "Restore" tab
|
||||
3. Scan QR code or paste SEEDPGP1 frame
|
||||
4. Provide private key or message password
|
||||
5. App handles Base45 decoding, CRC verification, and decryption automatically
|
||||
|
||||
**Using Custom Script (Advanced):**
|
||||
|
||||
```python
|
||||
# Example Python recovery script (conceptual)
|
||||
import base45
|
||||
import gnupg
|
||||
import json
|
||||
|
||||
frame = "SEEDPGP1:0:58B5:2KO K0S-U. M:..."
|
||||
parts = frame.split(":", 3)
|
||||
crc_expected = parts[2]
|
||||
b45_payload = parts[3]
|
||||
|
||||
# Decode Base45
|
||||
pgp_binary = base45.b45decode(b45_payload)
|
||||
|
||||
# Decrypt with GPG
|
||||
gpg = gnupg.GPG()
|
||||
decrypted = gpg.decrypt(pgp_binary, passphrase="your-passphrase")
|
||||
|
||||
# Parse JSON
|
||||
data = json.loads(str(decrypted))
|
||||
print(f"Mnemonic: {data['w']}")
|
||||
print(f"BIP39 Passphrase used: {'YES' if data['pp'] == 1 else 'NO'}")
|
||||
```
|
||||
|
||||
***
|
||||
|
||||
## 📝 Technical Details
|
||||
|
||||
**Encryption Algorithms:**
|
||||
|
||||
- **PGP Encryption:** AES-256 (OpenPGP standard)
|
||||
- **Symmetric Encryption:** AES-256 with random session key
|
||||
- **CRC Algorithm:** CRC16-CCITT-FALSE (polynomial 0x1021)
|
||||
- **Encoding:** Base45 (RFC 9285)
|
||||
|
||||
**JSON Schema:**
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"required": ["v", "t", "w", "l", "pp"],
|
||||
"properties": {
|
||||
"v": {
|
||||
"type": "integer",
|
||||
"const": 1,
|
||||
"description": "Protocol version"
|
||||
},
|
||||
"t": {
|
||||
"type": "string",
|
||||
"const": "bip39",
|
||||
"description": "Data type (BIP39 mnemonic)"
|
||||
},
|
||||
"w": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-z]+( [a-z]+){11,23}$",
|
||||
"description": "BIP39 mnemonic words (lowercase, space-separated)"
|
||||
},
|
||||
"l": {
|
||||
"type": "string",
|
||||
"const": "en",
|
||||
"description": "Language (English)"
|
||||
},
|
||||
"pp": {
|
||||
"type": "integer",
|
||||
"enum": [0, 1],
|
||||
"description": "BIP39 passphrase flag: 0 = none, 1 = used"
|
||||
},
|
||||
"fpr": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "Optional: Recipient key fingerprints"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Frame Validation Rules:**
|
||||
|
||||
1. Must start with `SEEDPGP1:`
|
||||
2. Frame version must be `0` (single frame)
|
||||
3. CRC16 must be 4 hex characters `[0-9A-F]{4}`
|
||||
4. Base45 payload must use valid Base45 alphabet
|
||||
5. Decoded PGP binary must pass CRC16 verification
|
||||
|
||||
***
|
||||
|
||||
## 🆘 Emergency Contact & Support
|
||||
|
||||
**No Technical Support Available:**
|
||||
|
||||
- SeedPGP is a self-sovereign tool with no central authority
|
||||
- You are solely responsible for your recovery
|
||||
- Test backups regularly to ensure they work
|
||||
|
||||
**Community Resources:**
|
||||
|
||||
- GitHub Issues: <https://github.com/kccleoc/seedpgp-web/issues>
|
||||
- Bitcoin StackExchange: Use `seedpgp` tag
|
||||
- Local Bitcoin meetups for in-person help
|
||||
|
||||
**Remember:** The security of your funds depends on your ability to successfully execute this recovery process. Practice with test backups before relying on it for significant amounts.
|
||||
|
||||
***
|
||||
|
||||
**Print this playbook on archival paper or metal. Store separately from encrypted backups and private keys.** 🔒
|
||||
|
||||
**Last Updated:** February 1, 2026
|
||||
**SeedPGP Version:** 1.4.3
|
||||
**Frame Example CRC:** 58B5 ✓
|
||||
**Test Recovery:** [ ] Completed [ ] Not Tested
|
||||
|
||||
***
|
||||
Reference in New Issue
Block a user