Skip to main content

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

Project description

rpgp-py

Supported versions PyPI Downloads GitHub stars pyrefly License: MIT

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 support: 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 useful references:

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"

Binary output, packet access, and caller-supplied session keys:

from openpgp import (
    Message,
    encrypt_message_to_recipient_bytes,
    encrypt_session_key_to_recipient,
)

session_key = bytes(range(16))
message_bytes = encrypt_message_to_recipient_bytes(
    b"secret",
    public_key,
    version="seipd-v2",
    symmetric_algorithm="aes128",
    session_key=session_key,
)

message = Message.from_bytes(message_bytes)
pkesk = message.public_key_encrypted_session_key_packets()[0]
edata = message.encrypted_data_packet()

assert pkesk.recipient_is_anonymous is False
assert edata.kind == "seipd-v2"
assert message.decrypt_with_session_key(session_key).payload_bytes() == b"secret"

raw_pkesk = encrypt_session_key_to_recipient(
    session_key,
    public_key,
    version="seipd-v2",
    symmetric_algorithm="aes128",
).to_bytes()
assert raw_pkesk

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"

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"

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.
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.

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.4.tar.gz (104.5 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.4-cp310-abi3-win_amd64.whl (3.1 MB view details)

Uploaded CPython 3.10+Windows x86-64

rpgp_py-0.19.4-cp310-abi3-win32.whl (2.9 MB view details)

Uploaded CPython 3.10+Windows x86

rpgp_py-0.19.4-cp310-abi3-musllinux_1_2_x86_64.whl (3.8 MB view details)

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

rpgp_py-0.19.4-cp310-abi3-musllinux_1_2_aarch64.whl (3.6 MB view details)

Uploaded CPython 3.10+musllinux: musl 1.2+ ARM64

rpgp_py-0.19.4-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.5 MB view details)

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

rpgp_py-0.19.4-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (3.4 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.17+ ARM64

rpgp_py-0.19.4-cp310-abi3-macosx_11_0_arm64.whl (3.1 MB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

rpgp_py-0.19.4-cp310-abi3-macosx_10_12_x86_64.whl (3.3 MB view details)

Uploaded CPython 3.10+macOS 10.12+ x86-64

File details

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

File metadata

  • Download URL: rpgp_py-0.19.4.tar.gz
  • Upload date:
  • Size: 104.5 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.4.tar.gz
Algorithm Hash digest
SHA256 ca080d035fd4d7456d3096d2decacd9aaa6cbc6aced8b75893e526c3c81792e2
MD5 b1805f0012f598a111b061b894a72293
BLAKE2b-256 178c5a13cfed128065f2681fabc5d6c7ccc1f9aad691b3b9c197d2fd949c121a

See more details on using hashes here.

File details

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

File metadata

  • Download URL: rpgp_py-0.19.4-cp310-abi3-win_amd64.whl
  • Upload date:
  • Size: 3.1 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.4-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 89993d52b90cebc9ce15282e2e2ae2fec775293816dfc59ebdd2ab0a172164c5
MD5 05397555cb8f974f90018d73668c161f
BLAKE2b-256 68e7e5823dcbaea0a8801d74d4f5d862849ae35fe698adf889d33cc6b7627879

See more details on using hashes here.

File details

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

File metadata

  • Download URL: rpgp_py-0.19.4-cp310-abi3-win32.whl
  • Upload date:
  • Size: 2.9 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.4-cp310-abi3-win32.whl
Algorithm Hash digest
SHA256 d0fb380a6ab16209e3ed4cb8b782e025c053b99c5bb33b14b2edd1fd261a5158
MD5 5952c564000dd706ff7c2faee806a14c
BLAKE2b-256 1fe33a0fb1b7ec435c9f9be7d2fa6690995dc20fdf09438014c602e79ea704e1

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for rpgp_py-0.19.4-cp310-abi3-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 1d754a9c7c854806193f5c6f54dd27455405877aac27009fa67c40dda72e7cff
MD5 6f9f2aee86a11a5f69c878f2301e0cde
BLAKE2b-256 c54a3c78d5a584cf4bb061b712dddf4151e3b3f743425f03179b52067b827361

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for rpgp_py-0.19.4-cp310-abi3-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 756fbb4a7826334252e615b7ab1e73aeaf62496fcb864a64459493a632c2af92
MD5 128d41fa538e3adf3848cf090b95eb80
BLAKE2b-256 f112f9b5987ad2a582e4415ea48953d71f3fc4d3a87459881d77729d0c3e7e84

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for rpgp_py-0.19.4-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 a6b7b5b10820edc6d3bb652468c7aa26efc0393cb9d654b90b4ba90210a36e81
MD5 dbf096fa51399185a1dcadfb06f8742d
BLAKE2b-256 f16b60d169ba8a017642008f46cfb5aac7f31c599b4f561603d140fd52189e5b

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for rpgp_py-0.19.4-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 2181b281a8c65b8c60cafe3257ffa2b78276d0c0e124173f799c374907ec2423
MD5 6a356f5e5d5d43120b706169eb8dd760
BLAKE2b-256 7e46ab30a2d79bf176f9e60a63bbb735aeef8750117f07a24b7eefa7ca79fa56

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for rpgp_py-0.19.4-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 2dde564f4816f0f0eb25170d4eb004a51e1cf7d4ffe41854a80a0fb244dee90e
MD5 db656f74340ed6f2f7b650562794d448
BLAKE2b-256 700c7a17944780aff609a72e27c037eaea23f24d01907f52f08952b8ee04b9c1

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for rpgp_py-0.19.4-cp310-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 a0291f474e5eb1282925ab0a1f6413ffcb146eda9f44696aaeaf1449775ffd03
MD5 00642d95cfb82515248562e38864f9a5
BLAKE2b-256 447cd12000541830cf10b06e2a69eade72071f609cb8e4d67f3a9dfd6e73050c

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