Skip to content
Generate a PGP key from a passphrase
Branch: master
Clone or download
Type Name Latest commit message Commit time
Failed to load latest commit information. Add note about passphrase cracking costs Jul 18, 2019
armor.go Including padding in ASCII armor Jul 14, 2019
encryptkey.go Add readPacket() function Jul 14, 2019
passphrase2pgp.go Adjust --verbose format Jul 17, 2019
passphrase2pgp_test.go Completely overhaul the internals Jul 13, 2019
userid.go Add readPacket() function Jul 14, 2019
utils.go Add readPacket() function Jul 14, 2019

Predictable, passphrase-based PGP key generator

passphrase2pgp generates, in OpenPGP format, an EdDSA signing key and Curve25519 encryption subkey entirely from a passphrase, essentially allowing you to store a backup of your PGP keys in your brain. At any time you can re-run the tool and re-enter the passphrase to reproduce the original keys.

The keys are derived from the passphrase and User ID (as salt) using Argon2id (memory=1GB and time=8) and RFC 8032. It's aggressive enough to protect from offline brute force attacks passphrases short enough to be memorable by humans. Always use a strong passphrase.

Requires Go 1.9 or later.


$ go get -u


Quick start: Provide a user ID (-u) and pipe the output into GnuPG.

$ passphrase2pgp -u "Real Name <>" | gpg --import

Either --uid (-u) or --load (-l) is required.

  • The --uid (-u) option supplies the user ID string for the key to be generated. If --uid is missing, the REALNAME and EMAIL environmental variables are used to construct a user ID, but only if both are present.

  • The --load (-l) option loads a previously generated key for use in other operations (signature creation, ASCII-armored public key, etc.).

There are two commands:

  • Key generation (--key, -K) [default]: Writes a key to standard output. This is a secret key by default, but --public (-p) restricts it to a public key.

  • Detached signatures (--sign, -S): Signs one or more input files. Unless --load is used, also generates a key, but that key is not output. If no files are given, signs standard input to standard output. Otherwise for each argument file creates file.sig with a detached signature. If armor is enabled (--armor, -a), the file is named file.asc.

Use --help (-h) for a full option listing:

   passphrase2pgp -K <-u id|-l key> [-anpsvx] [-i ppfile] [-r n] [-t time]
   passphrase2pgp -S <-u id|-l key> [-av] [-i ppfile] [-r n] [files...]
   -K, --key              output a key (default)
   -S, --sign             output detached signatures
   -a, --armor            encode output in ASCII armor
   -c, --check KEYID      require last Key ID bytes to match
   -h, --help             print this help message
   -i, --input FILE       read passphrase from file
   -l, --load FILE        load key from file instead of generating
   -n, --now              use current time as creation date
   -p, --public           only output the public key
   -r, --repeat N         number of repeated passphrase prompts
   -s, --subkey           also output an encryption subkey
   -t, --time SECONDS     key creation date (unix epoch seconds)
   -u, --uid USERID       user ID for the key
   -v, --verbose          print additional information
   -x, --paranoid         increase key generation costs

Per the OpenPGP specification, the Key ID is a hash over both the key and its creation date. Therefore using a different date with the same passphrase/ID will result in a different Key ID, despite the underlying key being the same. For this reason, passphrase2pgp uses Unix epoch 0 (January 1, 1970) as the default creation date. You can override this with --time (-t) or --now (-n), but, to regenerate the same key in the future, you will need to use --time to reenter the exact time. If 1970 is a problem, then choose another memorable date.

The --check (-c) causes passphrase2pgp to abort if the final bytes of the Key ID do not match the hexadecimal argument. If this option is not provided, the KEYID environment variable is used if available. In either case, --repeat (-r) is set to zero unless it was explicitly provided. The additional passphrase check is unnecessary if they Key ID is being checked.

The --paranoid (-x) setting quadruples the KDF difficulty. This will result in a different key for the same passphrase.


Generate a private key and send it to GnuPG:

$ passphrase2pgp --uid "..." | gnupg --import

Create an armored public key for publishing and sharing:

$ passphrase2pgp --uid "..." --armor --public > Real-Name.asc

Determine --uid from the environment so that you don't need to type it out every time you use passphrase2pgp:

$ export REALNAME="Real Name"
$ export EMAIL=""
$ passphrase2pgp -ap > Real-Name.asc

Generate a private key and save it to a file in OpenPGP format for later use below:

$ passphrase2pgp --uid "..." > secret.pgp

Created detached signatures (-S) for some files:

$ passphrase2pgp -S --load secret.pgp document.txt avatar.jpg

This will create document.txt.sig and avatar.jpg.sig. The other end would use GnuPG to verify the signatures like so:

$ gpg --import Real-Name.asc
$ gpg --verify document.txt.sig
$ gpg --verify avatar.jpg.sig

Or, in order to avoid entering the passphrase again and waiting on key generation, use the previously saved private key to sign some files without entering your passphrase:

$ passphrase2pgp -S --load secret.pgp document.txt avatar.jpg

Same, but now with ASCII-armored signatures:

$ passphrase2pgp -S --load secret.pgp --armor document.txt avatar.jpg
$ gpg --verify document.txt.asc
$ gpg --verify avatar.jpg.asc

Interacting with GnuPG

Once your key is generated, you may want to secure it with a protection passphrase on your GnuPG keyring in order to protect it at rest:

$ gpg --edit-key "Real Name"
gpg> passwd

Trust is stored external to keys, so imported keys are always initially untrusted. You will likely want to mark your newly-imported primary key as trusted.

$ gpg --edit-key "Real Name"
gpg> trust

Or use the --trusted-key option in gpg.conf.

Signing Git tags and commits

It's even possible to use passphrase2pgp directly to sign your Git tags and commits. Just configure gpg.program to passphrase2pgp:

$ git config --global gpg.program passphrase2pgp

However, with this setting you will be unable to verify commits and tags. To work around this problem, wrap passphrase2pgp in a script like the following, with options adjusted to taste (add --repeat, --time, --paranoid, etc.):

#!/bin/sh -e
if [ "$2" != -bsau ]; then
    exec gpg "$@"  # fallback GnuPG when not signing
passphrase2pgp --sign --armor --uid "$3"
printf '\n[GNUPG:] SIG_CREATED ' >&2

Then set gpg.program to this script instead:

$ git config --global gpg.program path/to/script

This does just enough to convince Git that passphrase2pgp is actually GnuPG. Example session of signing a tag with passphrase2pgp:

$ git tag -s tagname -m 'Tag message'
passphrase (repeat): 

Tag verification (via script to fallback to GnuPG):

$ passphrase2pgp -u "..." -p | gpg --import
passphrase (repeat): 
$ git verify-tag tagname
gpg: Good signature from ...


Isn't generating a key from a passphrase foolish? If you can reproduce your key from a passphrase, so can any one else!

In 2019, the fastest available implementation of Argon2id running on the best available cloud hardware takes just over 6 seconds with passphrase2pgp's default parameters. That's 6 seconds of a dedicated single CPU core and 1GB of RAM for a single guess. This means that at the current cloud computing rates it costs around US$50 to make 2^20 (~1 million) passphrase guesses.

A randomly-generated password of length 8 composed of the 95 printable ASCII characters has ~52.6 bits of entropy. Therefore it would cost around US$ 158 billion to for just a 50% chance of cracking that passphrase. If your passphrase is generated by a random process, and it's at least this long, it is not the weak point in this system.


Since OpenPGP encryption is neither good nor useful anymore, I considered not generating an encryption subkey. The "privacy" portion of OpenPGP has become the least important part.

OpenPGP digital signatures still have some limited use, mostly due to momentum and lack of alternatives. The OpenPGP specification is over-engineered, loaded with useless legacy cruft, and ambiguous in many places. GnuPG is honestly not that great of an OpenPGP implementation, and I have low confidence in it. I stubbed my toe on a number of minor GnuPG bugs when hammering it with (usually invalid) output from passphrase2pgp while it was being developed.


You can’t perform that action at this time.