Skip to main content

Command Palette

Search for a command to run...

How I Hacked My Own GPG Key: A Developer's Forensic War Story

Local clipboard forensics, password pattern analysis, and a targeted dictionary attack — all against my own machine at 11 PM

Updated
9 min read
How I Hacked My Own GPG Key: A Developer's Forensic War Story
F
Architect and Creator of the DotUniverse—a massive open-source ecosystem dedicated to revolutionizing the Developer Experience (DX). With a specialized portfolio featuring over 20+ tools including DotShare, DotCommand, DotScramble, and DotGhostBoard, I focus on building high-performance, secure, and streamlined workflows for modern engineers. My mission is to empower the developer community by crafting modular tools that solve real-world problems. On this blog, I share deep dives into software architecture, cybersecurity insights, AI integration, and the journey of scaling a comprehensive suite of developer tools from the ground up.

TL;DR — I forgot my GPG passphrase mid-release. Instead of generating a new key, I treated it as a self-CTF: clipboard forensics → password vault analysis → targeted 4-candidate dictionary attack. Cracked in 3.2 seconds. Here's the full playbook.


It was 11 PM when my release pipeline stopped cold.

I was packaging a new build of DotScramble — an image privacy tool I'm building — when the terminal froze at the most inconvenient possible step:

Signing DotScramble-Linux-x86_64.deb with GPG...
[GPG Password Prompt Appears]

I typed my usual passphrase. Incorrect. A variation. Incorrect. Three more guesses. Incorrect, incorrect, incorrect.

The cursor blinked at me, completely indifferent.

Here's the brutal truth about GPG passphrases: there is no "Forgot Password" button. No recovery email. No support ticket. The passphrase is the key. Lose one, you've lost the other.

Most developers would generate a new key and move on. But I decided to do something more interesting: treat my own GPG key A7BC4921FE83D105 as a CTF target, and document every step of the recovery — not just what worked, but why.


Who is this for?

  • Developers who've ever had a "wait, what was that passphrase again?" moment

  • Security-curious engineers who want to see real OSINT methodology in practice

  • Anyone building release pipelines who wants to understand GPG signing better

No prior security background needed. If you know what a terminal is, you'll follow this.


Understanding the Attack Surface

Before diving in: why can't you just brute-force a GPG key?

GPG uses a String-to-Key (S2K) function to derive the AES encryption key from your passphrase. This KDF (Key Derivation Function) is intentionally slow — it iterates a hash function thousands of times to make each guess expensive.

On modern hardware, you can test roughly 50,000 GPG passphrases per second. Against an 8-character mixed-character passphrase, that means:

Keyspace: 72^8 ≈ 7.2 × 10¹⁴ combinations
Speed: 50,000/sec
Estimated time: ~455 years

Generic brute force is dead on arrival. The only viable path: attacking the human, not the cryptography.


Phase 1 — Forensics: Check Before You Break

The first rule of any credential recovery is passive recon. Check every place where the passphrase might already be stored before trying to crack anything.

1.1 Keyring Manager (Seahorse)

Linux desktop environments cache many secrets automatically. I opened Seahorse (GNOME's GUI for gnome-keyring) and searched every entry.

secret-tool search xdg:schema org.gnome.keyring.Note

Found: browser tokens, VS Code credentials, SSH passphrases. GPG passphrase: nowhere. It had never been saved here.

1.2 Shell History

grep -i "passphrase\|gpg\|gen-key" ~/.bash_history ~/.zsh_history

Nothing. The passphrase had been entered interactively at key generation — correct security practice, now working against me.

1.3 Clipboard Forensics — Querying ghost.db

This is where it got interesting.

I run DotGhostBoard, a clipboard manager I built that silently logs every Ctrl+C to a local SQLite database:

~/.config/dotghostboard/ghost.db

If I'd ever copied the passphrase, it would be fossilized here. I ran a targeted query:

sqlite3 ~/.config/dotghostboard/ghost.db \
  "SELECT content, created_at FROM clipboard_items
   WHERE length(content) BETWEEN 6 AND 60
   AND content NOT LIKE 'http%'
   AND content NOT LIKE '% %'
   ORDER BY created_at ASC"

Dozens of entries scrolled past — code snippets, hashes, short strings. No passphrase.

My database had a max_history = 200 prune limit. The key was generated months ago. Entry long gone.

Note for your own setup: Most clipboard managers (CopyQ, Parcellite, KDE Klipper) maintain similar local databases. Know your retention window — it has real security and recovery implications.


Phase 2 — Intelligence: Building the Targeted Wordlist

Forensics failed. Time to shift to active intelligence.

The insight: I wasn't going to find the password. I was going to deduce it.

I exported my browser password vault to a CSV and filtered for development-related credentials:

grep -i -E "gpg|sign|key|dev|local|tool" ~/vault_export.csv | \
  awk -F',' '{print $4}' | sort -u

A pattern emerged immediately. For local dev tools and signing keys, I consistently use a structural template: a numeric base, an alphabetic segment, and a rotating special character suffix.

This is the core vulnerability — not of GPG, but of the human who chose the passphrase. Even security-aware developers build patterns. I just needed to identify mine.

I compiled a shortlist of four candidates — the most likely variants of the base I'd been using at the time of key generation.


Phase 3 — Targeted Dictionary Attack

Armed with my four-candidate list, I needed a way to test passphrases programmatically — no GUI dialogs, fully automated.

The Key: --pinentry-mode loopback

GPG's default behavior is to spawn a graphical pinentry dialog for passphrase input. This is intentional security design — it prevents background processes from silently accessing the passphrase.

However, GPG exposes --pinentry-mode loopback for legitimate batch/automation use cases, which redirects passphrase input to stdin:

echo "test" | gpg \
  --batch \
  --yes \
  --pinentry-mode loopback \
  --passphrase "CANDIDATE" \
  -u KEY_ID \
  --sign > /dev/null 2>&1

Exit code 0 = passphrase correct. Non-zero = wrong guess.

The Script

#!/bin/bash

candidates=(
  "D3vSig@#"
  "d3vsig22"
  "D3vSig@"
  "qX7#mLP9sKRvZn2"
)

target_key="A7BC4921FE83D105"

for pwd in "${candidates[@]}"; do
  echo -n "[*] Testing: $pwd ... "

  echo "test" | gpg \
    --batch \
    --yes \
    --pinentry-mode loopback \
    --passphrase "$pwd" \
    -u "$target_key" \
    --sign > /dev/null 2>&1

  if [ $? -eq 0 ]; then
    echo ""
    echo "======================================="
    echo "[+] Recovered: $pwd"
    echo "======================================="
    exit 0
  else
    echo "FAILED"
  fi
done

echo "[-] All candidates exhausted."
exit 1

The Output

[*] Testing: D3vSig@#    ... FAILED
[*] Testing: d3vsig22    ... FAILED
[*] Testing: D3vSig@     ... SUCCESS ✅

Three seconds. Four candidates. One match.

The correct passphrase was the variant with a single trailing @ — not the double @# I use in other contexts, not the all-lowercase version, not the long random string. The variant I'd typed on a tired weeknight without looking up my usual suffix.


Phase 4 — Back in Business

python3 build.py
Signing DotScramble-Linux-x86_64.deb with GPG...
   ✅ Detached signature created: DotScramble-Linux-x86_64.deb.asc

Signing DotScramble-Linux-x86_64.AppImage with GPG...
   ✅ Detached signature created: DotScramble-Linux-x86_64.AppImage.asc

Build completed successfully! 🎉

I added the GPG public key to my GitHub account. Every commit and release now shows a green Verified badge.

At midnight, after all of that, it genuinely felt earned.


Full Recovery Summary

Phase Method Tool Result
Recon Keyring inspection Seahorse / secret-tool ❌ Not cached
Recon Shell history search grep ❌ Not found
Recon Clipboard forensics SQLite on ghost.db ❌ Pruned
Intelligence Vault export + pattern analysis grep + manual review ✅ Pattern identified
Exploitation 4-candidate dictionary attack Bash + loopback pinentry ✅ Cracked in 3.2s

Total time: ~30 minutes. The attack script itself ran in under 4 seconds.


Takeaways

1. Password managers are non-negotiable for GPG keys

The moment gpg --gen-key finishes — before you close the terminal — open Bitwarden, 1Password, or KeePassXC and save the passphrase. GPG has zero mercy for human memory. This is the only takeaway that would have prevented all of this.

2. You are your own biggest attack surface

Even with a passphrase that would take 455 years to brute-force, a targeted dictionary attack built from your own vault cracked it in seconds. We think our passwords are random. They're not — they're variations of patterns we've been using for years. Anyone with access to one of your credentials has partial access to all of them.

3. Clipboard managers are unsung forensic goldmines (and risks)

DotGhostBoard failed me here only because of the prune limit. In other scenarios it's saved me entirely. The point is: your clipboard manager silently knows a lot about your daily workflow. Secure its database. Know what it's storing. Know your retention policy.

4. Consider hardware-backed GPG storage

A YubiKey with GPG support stores the private key in hardware — it can never be exported. The PIN has hardware-enforced retry limits. A targeted dictionary attack against a .gnupg file becomes irrelevant. For production signing keys, this is worth the investment.


What's Next in the DotSuite Series

This build pipeline — the one that needed the GPG passphrase — signs every DotScramble release. In the next post, I'll walk through how DotScramble's freemium licensing system uses Ed25519 asymmetric signing to prevent license key sharing, with the private key living entirely server-side in the Rust/Axum backend.

Spoiler: it's the same principle that makes GPG hard to crack, applied to a custom activation flow with hardware fingerprinting and a PostgreSQL license_machines table.

If you're building dev tools with any kind of monetization layer, that one will be worth your time.


DotScramble is a standalone Python desktop app for image privacy — face detection, license plate blur, OCR text censoring, EXIF metadata spoofing, batch processing, and a full RTL/Arabic UI. Built as part of DotSuite, a suite of developer productivity tools.


Did you ever lose a GPG passphrase — or any critical dev credential? How did you handle it? Drop it in the comments 👇

If this helped you, a ❤️ goes a long way — it helps other developers find the article.

OSINT & Tech Teardowns

Part 3 of 3

Technical teardowns and OSINT investigations of developer platforms, tech scams, and deceptive infrastructure. I analyze source code, public records, and server architectures to separate marketing claims from technical reality. Verifiable facts, zero fluff.

Start from the beginning

PART 2: I Published a Scam Expose. NetEase Sent a Takedown Request. Then They Rewrote Their Entire Operation.

📌 Part 2 of an ongoing investigation. Part 1 → EXPOSED: The Youdao Ads Influencer Marketing Scam 18 days after I published a technical scam analysis, the company sent a takedown request, their trus