Examples

Keys

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],
            key_expiration=timedelta(days=365))

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()
empty_key.parse(keyblob)

# 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)

Messages

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!",
                                 cleartext=True)

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)

Actions

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/Messages/Other

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
pub.verify(message)

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

# or, better yet, verify all applicable signatures on a key in one go
pub.verify(someones_pubkey)

Encryption

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:

# Assume the sender has retrieved the public key and saved it to a file.
# reload the public key
pubkey, _ = PGPKey.from_file("PATH TO PUBLIC KEY FILE")

# As usual, construct a PGPMessage from a string:
message = PGPMessage.new("42 is quite a pleasant number")

# Transform it into a new PGPMessage that contains an encrypted form of the
# unencrypted message
encrypted_message = pubkey.encrypt(message)

# Recipient loads the private key
key, _ = PGPKey.from_file("PATH TO _PRIVATE_ KEY FILE")

# after the sender sends the encrypted message, the recipient decrypts:
plaintext = key.decrypt(encrypted_message).message
Encrypting Messages to Multiple Recipients

Warning

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")

Ignoring Usage Flags

Warning

Don’t do this unless you’re really sure you need to!

Sometimes a key is created without the correct usage flags and an error is raised when you try to use the key:

>>> from pgpy import PGPKey, PGPMessage
>>> key, _ = PGPKey.from_file('path/to/key_without_usage_flags.asc')
>>> message = PGPMessage.new('secret message')
>>> encrypted_phrase = key.encrypt(message)
PGPError: Key 0123456789ABCDEF does not have the required usage flag EncryptStorage, EncryptCommunications

To disable this check, set _require_usage_flags to False on the key before calling the problem function:

>>> from pgpy import PGPKey, PGPMessage
>>> key, _ = PGPKey.from_file('path/to/key_without_usage_flags.asc')
>>> key._require_usage_flags = False
>>> message = PGPMessage.new('secret message')
>>> encrypted_phrase = key.encrypt(message)

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__()