python-gnupg
The `python-gnupg` library provides a Python wrapper for the GNU Privacy Guard (GnuPG or GPG), enabling Python programs to encrypt, decrypt, sign data, verify signatures, and manage GPG keys. It is actively maintained by Vinay Sajip with frequent bug-fix and enhancement releases. The current version is 0.5.6.
Warnings
- gotcha The `gpg` executable must be installed on the system and accessible via the system's PATH. If not found, `python-gnupg` operations will fail with `FileNotFoundError` or similar.
- gotcha GnuPG uses a 'home directory' (`gnupghome`) for keyrings and trust databases. If you don't specify `gnupghome` when initializing `gnupg.GPG`, it will use the GnuPG default (typically `~/.gnupg`). Permissions issues on this directory are common, especially in server environments, leading to `secret key not available` or other access errors.
- deprecated The `always_trust=True` parameter in `encrypt` and `verify` methods (which maps to GnuPG's `--always-trust`) was effectively deprecated by GnuPG itself. For GnuPG versions >= 0.5.1, `python-gnupg` now internally uses `--trust-model always` when `always_trust=True` is passed. While the parameter still works, be aware of the underlying change.
- gotcha For GnuPG versions >= 2.1, passing passphrases directly via streams (e.g., to `decrypt`) might not work by default. GnuPG 2.1+ often requires `allow-loopback-pinentry` to be added to `gpg-agent.conf` in the GnuPG home directory for programmatic passphrase input. Without this, operations requiring a passphrase may hang or fail.
- gotcha Always check the `ok` attribute and `stderr` of the result object returned by `gnupg` methods (e.g., `gpg.encrypt().ok`, `gpg.decrypt().stderr`). Operations might not raise Python exceptions on GnuPG failures, but instead report errors in the result object's attributes.
- gotcha Encoding issues can arise, especially when working with non-ASCII data. `python-gnupg` defaults to `latin-1` for communication with the `gpg` executable. If your data or GnuPG's output uses a different encoding, you might encounter `UnicodeDecodeError` or corrupted output.
Install
-
pip install python-gnupg
Imports
- GPG
import gnupg gpg = gnupg.GPG()
Quickstart
import gnupg
import os
import tempfile
import shutil
import subprocess
def check_gpg_installed():
try:
subprocess.run(['gpg', '--version'], check=True, capture_output=True)
return True
except (subprocess.CalledProcessError, FileNotFoundError):
return False
if not check_gpg_installed():
print("GnuPG (gpg) executable not found. Please install GnuPG first.")
exit(1)
# Create a temporary directory for GnuPG home to avoid interfering with user's keyring
try:
temp_gnupghome = tempfile.mkdtemp()
print(f"Using temporary GnuPG home: {temp_gnupghome}")
gpg = gnupg.GPG(gnupghome=temp_gnupghome)
# For GnuPG >= 2.1, passphrases might require 'allow-loopback-pinentry' in gpg-agent.conf
# For a quickstart, we generate a key without protection for simplicity if supported by GnuPG version.
# However, 'passphrase' is generally required for security.
# Generate a key
# Note: Real key generation might require sufficient entropy on your system
input_data = gpg.gen_key_input(
key_type='RSA',
key_length=2048,
name_real='Test User',
name_email='test@example.com',
passphrase='mysecretpassword',
no_protection=False # Keep True for keys without passphrase, False for password-protected
)
key = gpg.gen_key(input_data)
if not key.ok:
print(f"Key generation failed: {key.status} - {key.stderr}")
if "no_protection" in key.stderr and "passphrase" in key.stderr and gpg.version[0] >= 2:
print("Hint: For GnuPG >= 2.1, generating keys with an empty passphrase (no_protection=True) or without `allow-loopback-pinentry` might fail. Try providing a passphrase.")
exit(1)
print(f"Generated key: {key.fingerprint}")
# List keys to confirm
public_keys = gpg.list_keys()
print("Public keys:")
for k in public_keys:
print(f" {k['keyid']} - {k['uids']}")
# Encrypt a message
unencrypted_string = "This is a secret message."
encrypted_data = gpg.encrypt(
unencrypted_string,
recipients=[key.fingerprint],
always_trust=True # Use --trust-model always in newer gpg, but always_trust is for this library.
)
if not encrypted_data.ok:
print(f"Encryption failed: {encrypted_data.status} - {encrypted_data.stderr}")
exit(1)
print(f"Encrypted message: {encrypted_data.data.decode('utf-8')[:50]}...")
# Decrypt the message
decrypted_data = gpg.decrypt(
encrypted_data.data,
passphrase='mysecretpassword'
)
if not decrypted_data.ok:
print(f"Decryption failed: {decrypted_data.status} - {decrypted_data.stderr}")
exit(1)
print(f"Decrypted message: {decrypted_data.data.decode('utf-8')}")
assert decrypted_data.data.decode('utf-8') == unencrypted_string
print("Encryption and decryption successful!")
finally:
# Clean up the temporary directory
if 'temp_gnupghome' in locals() and os.path.exists(temp_gnupghome):
print(f"Cleaning up temporary GnuPG home: {temp_gnupghome}")
shutil.rmtree(temp_gnupghome)