Generating Keys

PGPy can generate most types keys as defined in the standard.

Generating Primary Keys

It is possible to generate most types of keys with PGPy now. The process is mostly straightforward:

from pgpy.constants import PubKeyAlgorithm, KeyFlags, HashAlgorithm, SymmetricKeyAlgorithm, CompressionAlgorithm

# we can start by generating a primary key. For this example, we'll use RSA, but it could be DSA or ECDSA as well
key = pgpy.PGPKey.new(PubKeyAlgorithm.RSAEncryptOrSign, 4096)

# we now have some key material, but our new key doesn't have a user ID yet, and therefore is not yet usable!
uid = pgpy.PGPUID.new('Abraham Lincoln', comment='Honest Abe', email='abraham.lincoln@whitehouse.gov')

# now we must add the new user id to the key. We'll need to specify all of our preferences at this point
# because PGPy doesn't have any built-in key preference defaults at this time
# this example is similar to GnuPG 2.1.x defaults, with no expiration or preferred keyserver
key.add_uid(uid, usage={KeyFlags.Sign, KeyFlags.EncryptCommunications, KeyFlags.EncryptStorage},
            hashes=[HashAlgorithm.SHA256, HashAlgorithm.SHA384, HashAlgorithm.SHA512, HashAlgorithm.SHA224],
            ciphers=[SymmetricKeyAlgorithm.AES256, SymmetricKeyAlgorithm.AES192, SymmetricKeyAlgorithm.AES128],
            compression=[CompressionAlgorithm.ZLIB, CompressionAlgorithm.BZ2, CompressionAlgorithm.ZIP, CompressionAlgorithm.Uncompressed])

Specifying key expiration can be done using the key_expiration keyword when adding the user id. Expiration can be specified using a datetime.datetime or a datetime.timedelta object:

from datetime import timedelta

# in this example, we'll use fewer preferences for the sake of brevity, and set the key to expire in 1 year
key = pgpy.PGPKey.new(PubKeyAlgorithm.RSAEncryptOrSign, 4096)
uid = pgpy.PGPUID.new('Nikola Tesla')  # comment and email are optional

# the key_expires keyword accepts a :py:obj:`datetime.datetime`
key.add_uid(uid, usage={KeyFlags.Sign}, hashes=[HashAlgorithm.SHA512, HashAlgorithm.SHA256],
            ciphers=[SymmetricKeyAlgorithm.AES256, SymmetricKeyAlgorithm.Camellia256],
            compression=[CompressionAlgorithm.BZ2, CompressionAlgorithm.Uncompressed],

Generating Sub Keys

Generating a subkey is similar to the process above, except that it requires an existing primary key:

# assuming we already have a primary key, we can generate a new key and add it as a subkey thusly:
subkey = pgpy.PGPKey.new(PubKeyAlgorithm.RSAEncryptOrSign, 4096)

# preferences that are specific to the subkey can be chosen here
# any preference(s) needed for actions by this subkey that not specified here
# will seamlessly "inherit" from those specified on the selected User ID
key.add_subkey(subkey, usage={KeyFlags.Authentication})

Loading Keys

There are two ways to load keys: individually, or in a keyring.

Loading Keys Individually

Keys can be loaded individually into PGPKey objects:

# A new, empty PGPkey object can be instantiated, but this is not very useful
# by itself.
# ASCII or binary data can be parsed into an empty PGPKey object with the .parse()
# method
empty_key = pgpy.PGPKey()

# A key can be loaded from a file, like so:
key, _ = pgpy.PGPKey.from_file('path/to/key.asc')

# or from a text or binary string/bytes/bytearray that has already been read in:
key, _ = pgpy.PGPKey.from_blob(keyblob)

Loading Keys Into a Keyring

If you intend to maintain multiple keys in memory for extended periods, using a PGPKeyring may be more appropriate:

# These two methods are mostly equivalent
kr = pgpy.PGPKeyring(glob.glob(os.path.expanduser('~/.gnupg/*ring.gpg')))

# the only advantage to doing it this way, is the .load method returns a set containing
#  the fingerprints of all keys and subkeys that were loaded this time
kr = pgpy.PGPKeyring()
loaded = kr.load(glob.glob(os.path.expanduser('~/.gnupg/*ring.gpg')))

Key Operations

Once you have one or more keys generated or loaded, there are some things you may need or want to do before they can be used.

Passphrase Protecting Secret Keys

It is usually recommended to passphrase-protect private keys. Adding a passphrase to a key is simple:

# key.is_public is False
# key.is_protected is False
key.protect("C0rrectPassphr@se", SymmetricKeyAlgorithm.AES256, HashAlgorithm.SHA256)
# key.is_protected is now True

Unlocking Protected Secret Keys

If you have a key that is protected with a passphrase, you will need to unlock it first. PGPy handles this using a context manager block, which also removes the unprotected key material from the object once execution exits that block.

Key unlocking is quite simple:

# enc_key.is_public is False
# enc_key.is_protected is True
# enc_key.is_unlocked is False
# Note that this context manager yields self, so while you can supply `as cvar`, it isn't strictly required
# If the passphrase given is incorrect, this will raise PGPDecryptionError
with enc_key.unlock("C0rrectPassphr@se"):
    # enc_key.is_unlocked is now True

# This form works equivalently, but may be more semantically clear in some cases:
with enc_key.unlock("C0rrectPassphr@se") as ukey:
    # ukey is just a reference to enc_key in this case

Exporting Keys

Keys can be exported in OpenPGP compliant binary or ASCII-armored formats.

In Python 3:

# binary
keybytes = bytes(key)

# ASCII armored private key
keystr = str(key)

# ASCII armored public key
keystr = str(key.pubkey)

in Python 2:

# binary
keybytes = key.__bytes__()

# ASCII armored
keystr = str(key)


Other than plaintext, you may want to be able to form PGP Messages. These can be signed and then encrypted to one or more recipients.

Creating New Messages

New messages can be created quite easily:

# this creates a standard message from text
# it will also be compressed, by default with ZIP DEFLATE, unless otherwise specified
text_message = pgpy.PGPMessage.new("This is a brand spankin' new message!")

# if you'd like to pack a file into a message instead, you can do so
# PGPMessage will store the basename of the file and the time it was last modified.
file_message = pgpy.PGPMessage.new("path/to/a/file", file=True)

# or, if you want to create a *cleartext* message, which is what you may know as a
# canonicalized text document with an inline signature block, that is done by setting
# cleartext=True. You can load the contents of a file as above, as well.
ct_message = pgpy.PGPMessage.new("This is a shiny new cleartext document. Hooray!",

Loading Existing Messages

Existing messages can also be loaded very simply. This is nearly identical to loading keys, except that it only returns the new message object, instead of a tuple:

# PGPMessage will automatically determine if this is a cleartext message or not
message_from_file = pgpy.PGPMessage.from_file("path/to/a/message")
message_from_blob = pgpy.PGPMessage.from_blob(msg_blob)

Exporting Messages

Messages can be exported in OpenPGP compliant binary or ASCII-armored formats.

In Python 3:

# binary
msgbytes = bytes(message)

# ASCII armored
# if message is cleartext, this will also properly canonicalize and dash-escape
# the message text
msgstr = str(message)

in Python 2:

# binary
msgbytes = message.__bytes__()

# ASCII armored
# if message is cleartext, this will also properly canonicalize and dash-escape
# the message text
msgstr = str(message)


Signing Things

One of the things you may want to do with PGPKeys is to sign things. This is split into several categories in order to keep the method signatures relatively simple. Remember that signing requires a private key.


Text and messages can be signed using the .sign method:

# sign some text
sig = sec.sign("I have just signed this text!")

# sign a message
# the bitwise OR operator '|' is used to add a signature to a PGPMessage.
message |= sec.sign(message)

# timestamp signatures can also be generated, like so.
# Note that GnuPG seems to have no idea what to do with this
timesig = sec.sign(None)

# if optional parameters are supplied, then a standalone signature is created
# instead of a timestamp signature. Effectively, they are equivalent, except
# that the standalone signature has more information in it.
lone_sig = sec.sign(None, notation={"cheese status": "standing alone"})

Keys/User IDs

Keys and User IDs can be signed using the .certify method:

# Sign a key - this creates a Signature Directly On A Key.
# GnuPG only partially supports this type of signature.
someones_pubkey |= mykey.certify(someones_pubkey)

# Sign the primary User ID - this creates the usual certification signature
# that is best supported by other popular OpenPGP implementations.
# As above, the bitwise OR operator '|' is used to add a signature to a PGPUID.
cert = mykey.certify(someones_pubkey.userids[0], level=SignatureType.Persona_Cert)
someones_pubkey.userids[0] |= cert

# If you want to sign all of their User IDs, that can be done easily in a loop.
# This is equivalent to GnuPG's default behavior when signing someone's public key.
# As above, the bitwise OR operator '|' is used to add a signature to a PGPKey.
for uid in someones_pubkey.userids:
    uid |= mykey.certify(uid)

Verifying Things

Although signing things uses multiple methods, there is only one method to remember for verifying signatures:

# verify a detached signature
pub.verify("I have just signed this text!", sig)

# verify signatures in a message

# verify signatures on a userid
for uid in someones_pubkey.userids:

# or, better yet, verify all applicable signatures on a key in one go


Another thing you may want to do is encrypt or decrypt messages.

Encrypting/Decrypting Messages With a Public Key

Encryption using keys requires a public key, while decryption requires a private key. PGPy currently only supports asymmetric encryption/decryption using RSA or ECDH:

# this returns a new PGPMessage that contains an encrypted form of the
# unencrypted message
encrypted_message = pubkey.encrypt(message)
Encrypting Messages to Multiple Recipients


Care must be taken when doing this to delete the session key as soon as possible after encrypting the message.

Messages can also be encrypted to multiple recipients by pre-generating the session key:

# The symmetric cipher should be specified, in case the first preferred cipher is not
#  the same for all recipients' public keys
cipher = pgpy.constants.SymmetricKeyAlgorithm.AES256
sessionkey = cipher.gen_key()

# encrypt the message to multiple recipients
# A decryption passphrase can be added at any point as well, as long as cipher
#  and sessionkey are also provided to enc_msg.encrypt
enc_msg = pubkey1.encrypt(message, cipher=cipher, sessionkey=sessionkey)
enc_msg = pubkey2.encrypt(enc_msg, cipher=cipher, sessionkey=sessionkey)

# do at least this as soon as possible after encrypting to the final recipient
del sessionkey

Encrypting/Decrypting Messages With a Passphrase

There are some situations where encrypting a message with a passphrase may be more desirable than doing so with someone else’s public key. That can be done like so:

# the .encrypt method returns a new PGPMessage object which contains the encrypted
# contents of the old message
enc_message = message.encrypt("S00per_Sekr3t")

# message.is_encrypted is False
# enc_message.is_encrypted is True
# a message that was encrypted using a passphrase can also be decrypted using
# that same passphrase
dec_message = enc_message.decrypt("S00per_Sekr3t")

Exporting PGP* Objects

PGPKey, PGPMessage, and PGPSignature objects can all be exported to OpenPGP-compatible binary and ASCII-armored formats.

To export in ASCII-armored format:

# This works in both Python 2.x and 3.x
# ASCII-armored format
# cleartext PGPMessages will also have properly canonicalized and dash-escaped
# message text
pgpstr = str(pgpobj)

To export to binary format in Python 3:

# binary format
pgpbytes = bytes(pgpobj)

To export to binary format in Python 2:

# binary format
pgpbytes = pgpobj.__bytes__()