Skip to main content

Python binding for rpgp, a Rust implementation of OpenPGP according to RFC 9580

Project description

rpgp-py

Python bindings for rpgp, exposed as the openpgp package.

  • support for RFC 9580
  • a typed Python surface (.pyi stubs ship with the package),
  • wheels for Python 3.10+,
  • high-level helpers for common signing/encryption workflows,
  • detailed inspection APIs for packets, signatures, key bindings, and generated key material.

Why use rpgp-py instead of PGPy or PGPy13?

Broadly:

  • RFC 9580 coverage: rpgp-py follows the Rust pgp crate, which targets newer OpenPGP work such as RFC 9580-compatible v6 key material and modern curves/packet handling. PGPy and PGPy13 are still RFC 4880.
  • Rust core: the cryptographic core is implemented in Rust and exposed through a Python-first API.
  • Typed builders and inspectors: the package exposes typed builders for key generation plus rich metadata for self-signatures, key flags, features, user bindings, S2K settings, and public-key parameters.
  • Python 3.13 story: PGPy still imports imghdr, which was removed from the standard library in Python 3.13. PGPy13 exists as a compatibility fork; rpgp-py targets current Python directly.

Installation

pip install rpgp-py

Reference documentation

When you need the underlying Rust semantics or want to compare behaviour against upstream docs, these are the most useful references:

Main use cases

1. Parse and inspect transferable keys

from openpgp import PublicKey, SecretKey

public_key, _ = PublicKey.from_armor(public_key_armor)
public_key.verify_bindings()

secret_key, _ = SecretKey.from_armor(secret_key_armor)
assert secret_key.to_public_key().fingerprint == public_key.fingerprint
assert public_key.public_subkey_count >= 0
assert secret_key.secret_subkey_count >= 0

This is the core entry point when you want to inspect fingerprints, key IDs, OpenPGP key versions, user IDs, subkeys, self-signatures, or packet-level metadata.

2. Sign and verify messages and detached signatures

from openpgp import DetachedSignature, Message, sign_message

signed = sign_message(b"hello world", secret_key)
message, _ = Message.from_armor(signed)
message.verify(public_key)
assert message.payload_text() == "hello world"

signature = DetachedSignature.sign_binary(b"hello world", secret_key)
signature.verify(public_key, b"hello world")
info = signature.signature_info()
assert info.signature_type == "binary"
assert info.hash_algorithm == "SHA256"

For inline or detached signatures, SignatureInfo exposes the signature packet metadata that is often needed for debugging or auditing.

3. Work with cleartext signatures

from openpgp import CleartextSignedMessage, sign_cleartext_message

armored = sign_cleartext_message("hello\n-world\n", secret_key)
message, _ = CleartextSignedMessage.from_armor(armored)

assert message.signed_text() == "hello\r\n-world\r\n"
assert message.signature_count() == 1
message.verify(public_key)

4. Encrypt and decrypt OpenPGP messages

Recipient encryption:

from openpgp import Message, encrypt_message_to_recipient

recipient_encrypted = encrypt_message_to_recipient(b"secret", public_key)
recipient_message, _ = Message.from_armor(recipient_encrypted)
recipient_decrypted = recipient_message.decrypt(secret_key)
assert recipient_decrypted.payload_bytes() == b"secret"

Password encryption:

from openpgp import Message, encrypt_message_with_password

password_encrypted = encrypt_message_with_password(b"secret", "hunter2")
password_message, _ = Message.from_armor(password_encrypted)
password_decrypted = password_message.decrypt_with_password("hunter2")
assert password_decrypted.payload_text() == "secret"

5. Generate modern RFC 9580-compatible key material

from openpgp import (
    EncryptionCaps,
    KeyType,
    Message,
    PacketHeaderVersion,
    SecretKeyParamsBuilder,
    SubkeyParamsBuilder,
    UserAttribute,
    encrypt_message_to_recipient,
    sign_message,
)

secret_key = (
    SecretKeyParamsBuilder()
    .version(6)
    .created_at(1_700_000_000)
    .key_type(KeyType.ed25519())
    .can_certify(True)
    .can_sign(True)
    .packet_version(PacketHeaderVersion.new())
    .feature_seipd_v2(True)
    .primary_user_id("Me <me@example.com>")
    .preferred_symmetric_algorithms(["aes256", "aes192", "aes128"])
    .preferred_hash_algorithms(["sha256", "sha384", "sha512", "sha224"])
    .preferred_compression_algorithms(["zlib", "zip"])
    .user_attribute(UserAttribute.image_jpeg(bytes.fromhex("ffd8ffe000104a464946000101")))
    .subkey(
        SubkeyParamsBuilder()
        .version(6)
        .created_at(1_700_000_123)
        .key_type(KeyType.x25519())
        .packet_version(PacketHeaderVersion.new())
        .can_encrypt(EncryptionCaps.all())
        .build()
    )
    .build()
    .generate()
)

public_key = secret_key.to_public_key()
secret_key.verify_bindings()
public_key.verify_bindings()

assert secret_key.version == 6
assert public_key.public_key_algorithm == "ed25519"
assert public_key.public_params.kind == "ed25519"
assert public_key.public_params.curve == "ed25519"
assert public_key.packet_version == PacketHeaderVersion.new()

signed = sign_message(b"generated payload", secret_key)
message, _ = Message.from_armor(signed)
message.verify(public_key)
assert message.payload_bytes() == b"generated payload"

encrypted = encrypt_message_to_recipient(b"secret", public_key)
encrypted_message, _ = Message.from_armor(encrypted)
assert encrypted_message.decrypt(secret_key).payload_bytes() == b"secret"

Use PacketHeaderVersion.old() when you need legacy packet-header framing for round-tripping older transferable key material.

6. Customize secret-key S2K protection for generated keys

from openpgp import (
    EncryptionCaps,
    KeyType,
    S2kParams,
    SecretKeyParamsBuilder,
    StringToKey,
    SubkeyParamsBuilder,
)

secret_key = (
    SecretKeyParamsBuilder()
    .version(6)
    .key_type(KeyType.ed25519())
    .can_certify(True)
    .can_sign(True)
    .primary_user_id("Me <me@example.com>")
    .passphrase("hunter2")
    .s2k(
        S2kParams.aead(
            "aes256",
            "ocb",
            StringToKey.argon2(3, 4, 16),
        )
    )
    .subkey(
        SubkeyParamsBuilder()
        .version(6)
        .key_type(KeyType.x25519())
        .can_encrypt(EncryptionCaps.all())
        .passphrase("hunter2")
        .s2k(
            S2kParams.cfb(
                "aes128",
                StringToKey.iterated("sha256", 96),
            )
        )
        .build()
    )
    .build()
    .generate()
)

primary_s2k = secret_key.primary_secret_s2k()
assert primary_s2k.usage == "aead"
assert primary_s2k.aead_algorithm == "ocb"
assert primary_s2k.string_to_key is not None
assert primary_s2k.string_to_key.kind == "argon2"

Feature overview

The current binding surface covers these areas:

  • parse ASCII-armored or binary transferable public keys,
  • parse ASCII-armored or binary transferable secret keys,
  • inspect fingerprints, key IDs, versions, creation times, algorithms, subkeys, user IDs, and S2K settings,
  • inspect direct-key signatures, user-ID signatures, subkey bindings, embedded primary-key-binding signatures, key flags, features, and preferred algorithm lists,
  • serialize keys back to binary packets or ASCII armor,
  • generate new secret/public keys with typed builder APIs,
  • parse OpenPGP messages into reusable Message objects,
  • inspect message metadata and read signed, literal, or compressed payloads,
  • decrypt encrypted messages with a secret key or password,
  • create password-encrypted or recipient-encrypted messages,
  • parse, serialize, create, and verify detached signatures,
  • parse, create, serialize, and verify cleartext signed messages,
  • inspect and selectively verify multi-signed inline messages,
  • verify key self-signatures and bindings,
  • convert a parsed secret key to its public-key view.

Benchmarks

Median runtime graph (1 KiB payload, lower is better)

Grouped benchmark chart for the shared workflows

rpgp-py is substantially faster: roughly 9x–71x faster for key parsing and 25x–48x faster for the sign/verify and recipient-encryption loops.

Password-encryption benchmark

Grouped benchmark chart for password encryption and decryption

This result is shown separately: rpgp-py defaults to modern SEIPDv2 + AEAD (OCB) password-protected messages, while PGPy/PGPy13 remain RFC 4880-era implementations.

Table of results

Operation rpgp-py PGPy13 PGPy
Parse armored public key 0.011 ms 0.786 ms 0.776 ms
Parse armored secret key 0.156 ms 1.473 ms 1.455 ms
Detached sign + verify 2.453 ms 61.329 ms 61.420 ms
Encrypt + decrypt to recipient 2.537 ms 122.726 ms 120.701 ms
Encrypt + decrypt with password 62.369 ms 50.346 ms 50.289 ms

Reproduction

To make that comparison reproducible, the repository now ships:

  • scripts/benchmark.py – an isolated benchmark runner,
  • docs/benchmarks/results.json – the committed raw results used below.
. "$HOME/.cargo/env"
uv run --python 3.12 python scripts/benchmark.py

Versioning

rpgp-py's version will reflect the major and minor version of the underlying pgp crate. The patch version will be incremented for both Python-facing API changes and for any internal changes that require a new build of the Rust core, such as dependency updates or bug fixes.

Development

See the list of useful commands by running:

just

Acknowledgements

Many thanks to the rpgp contributors and maintainers for building and documenting the Rust OpenPGP implementation that powers this package.

License

This repository is distributed under the MIT License. The upstream Rust crates it wraps keep their own licenses; check their repositories and published metadata when you need to audit the full dependency chain.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

rpgp_py-0.19.2.tar.gz (98.0 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

rpgp_py-0.19.2-cp310-abi3-win_amd64.whl (3.0 MB view details)

Uploaded CPython 3.10+Windows x86-64

rpgp_py-0.19.2-cp310-abi3-win32.whl (2.8 MB view details)

Uploaded CPython 3.10+Windows x86

rpgp_py-0.19.2-cp310-abi3-musllinux_1_2_x86_64.whl (3.7 MB view details)

Uploaded CPython 3.10+musllinux: musl 1.2+ x86-64

rpgp_py-0.19.2-cp310-abi3-musllinux_1_2_aarch64.whl (3.5 MB view details)

Uploaded CPython 3.10+musllinux: musl 1.2+ ARM64

rpgp_py-0.19.2-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.4 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.17+ x86-64

rpgp_py-0.19.2-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (3.3 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.17+ ARM64

rpgp_py-0.19.2-cp310-abi3-macosx_11_0_arm64.whl (3.0 MB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

rpgp_py-0.19.2-cp310-abi3-macosx_10_12_x86_64.whl (3.2 MB view details)

Uploaded CPython 3.10+macOS 10.12+ x86-64

File details

Details for the file rpgp_py-0.19.2.tar.gz.

File metadata

  • Download URL: rpgp_py-0.19.2.tar.gz
  • Upload date:
  • Size: 98.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for rpgp_py-0.19.2.tar.gz
Algorithm Hash digest
SHA256 c1ae567b490e5d518676e750d02d4eff791b48949a420127a46d7ccfd43c589b
MD5 9d5571472713c5e380ba0bf09bb3919f
BLAKE2b-256 26cab6b196bb68943497104b9f474ea8cf5819d49721a0b464d616043b112cdd

See more details on using hashes here.

File details

Details for the file rpgp_py-0.19.2-cp310-abi3-win_amd64.whl.

File metadata

  • Download URL: rpgp_py-0.19.2-cp310-abi3-win_amd64.whl
  • Upload date:
  • Size: 3.0 MB
  • Tags: CPython 3.10+, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for rpgp_py-0.19.2-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 c3160090eb28c24758c20c2ecaf21a57bddf032f2a495b5b71746d64a578b78e
MD5 0861c915f6623239a77fbd1e62d2d633
BLAKE2b-256 5ebac46a8151b475d292027d1b735ecfc83806258b2ad1ba91a40b346297a00f

See more details on using hashes here.

File details

Details for the file rpgp_py-0.19.2-cp310-abi3-win32.whl.

File metadata

  • Download URL: rpgp_py-0.19.2-cp310-abi3-win32.whl
  • Upload date:
  • Size: 2.8 MB
  • Tags: CPython 3.10+, Windows x86
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for rpgp_py-0.19.2-cp310-abi3-win32.whl
Algorithm Hash digest
SHA256 9e6d1e27cc6b1deb8e76b0e8f039a0a114092aeadcc5a7bb61b1e332cab9ec79
MD5 25f7a5596ef31044300b65557582f05c
BLAKE2b-256 c2644237f8ef802e478af4da416751e73b1816b1af4d85623ef9e7be724954e7

See more details on using hashes here.

File details

Details for the file rpgp_py-0.19.2-cp310-abi3-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for rpgp_py-0.19.2-cp310-abi3-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 1178dfaecd41be163d393cd627ed327ab5630a53dc4c0569f81a807ff8888fcf
MD5 22185b79dff4b025fde7899509dfbffb
BLAKE2b-256 189ba7ffb42db4d32d34d43522dc8abffface6446802b5e2f4d387c2beed8719

See more details on using hashes here.

File details

Details for the file rpgp_py-0.19.2-cp310-abi3-musllinux_1_2_aarch64.whl.

File metadata

File hashes

Hashes for rpgp_py-0.19.2-cp310-abi3-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 28ca64a01192ac785303d51f7cd868afd68c024bdbf40aac52a0f1acde095c71
MD5 e8b2068bf6bce5be17833e3fa98aceae
BLAKE2b-256 db72dfeee8bb7be23fe6c96105721dc83c38aae38f2cf1674faab34026a1de4f

See more details on using hashes here.

File details

Details for the file rpgp_py-0.19.2-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for rpgp_py-0.19.2-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 f62a31c8e8e889ade48f8c7ca6ef08b8780c897f3f62535c4cf6fd96a778ba21
MD5 4256b500b0db1377312b60ab07067e30
BLAKE2b-256 175e11ff5758b26b0ca4af2743884444b7d6139da4a76988f77fd3601b6c93d4

See more details on using hashes here.

File details

Details for the file rpgp_py-0.19.2-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for rpgp_py-0.19.2-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 a50b7082c5e374fa7ae1607c1d4d0316cf4c7a7dd6d1aef82ef1e5753f57e00d
MD5 eda57f4ce49afb2c455267290858d772
BLAKE2b-256 70d904e922288f92eb760ff86501f75a6e4ae0018dd9c3b0e8e3fda4332efd73

See more details on using hashes here.

File details

Details for the file rpgp_py-0.19.2-cp310-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for rpgp_py-0.19.2-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 2ebd0fdbebeaeb536445bb0f45571eb6afe0725704fbd684c290c7b6533fa6e3
MD5 da752772ee1b41f1b2fd086324f165c3
BLAKE2b-256 cefa433caccca788d91c85627d60e762e87dd031cefeb4b003b10b9238977b10

See more details on using hashes here.

File details

Details for the file rpgp_py-0.19.2-cp310-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for rpgp_py-0.19.2-cp310-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 94364781893b4f1e017219763ac889208d12f8171a791c32b059d04dbf1b8397
MD5 0d9726266ea71d2594a8465983aa4236
BLAKE2b-256 326a93be2be1a34255ebf85decb6c6176527ca34673ca00148b13c8cac56dad9

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page