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"

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"

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"

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

Uploaded CPython 3.10+Windows x86-64

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

Uploaded CPython 3.10+Windows x86

rpgp_py-0.19.3-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.3-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.3-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.3-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.3-cp310-abi3-macosx_11_0_arm64.whl (3.1 MB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

rpgp_py-0.19.3-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.3.tar.gz.

File metadata

  • Download URL: rpgp_py-0.19.3.tar.gz
  • Upload date:
  • Size: 102.7 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.3.tar.gz
Algorithm Hash digest
SHA256 34fc3e0c52494b68e4815f17ab0854eaba2693c8e243f28591e302320c8fdf33
MD5 9895527905333040bfd812a0febece8a
BLAKE2b-256 9e210db9055ee219e1feeaebccb25e094803ce82fc8b010f21ff3d8d37f6f223

See more details on using hashes here.

File details

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

File metadata

  • Download URL: rpgp_py-0.19.3-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.3-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 a411a7bcaaaf5a17f682067e3bf6f05795355d1541e7b3197df2d75d3af0488a
MD5 793b3ab45c0d7c96772d997e0c25ec8b
BLAKE2b-256 bed7007e926b0e6aa1dae869b577b9278fb1a213340778cec12ec31cada80cec

See more details on using hashes here.

File details

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

File metadata

  • Download URL: rpgp_py-0.19.3-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.3-cp310-abi3-win32.whl
Algorithm Hash digest
SHA256 6c2bf2c4318918949e181d08c91ab2c55a4bb7b9e8cb499b0244cc3a71ac1746
MD5 0d0f0d3c33c8ee228d5be76762b886d6
BLAKE2b-256 c7e36b14df1c2b4728f64472c9ec14e95506565ae93b231459d55317d8e048a4

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for rpgp_py-0.19.3-cp310-abi3-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 8f898dbd3477180fc1a122caa6f39c3b2ca5cbc75fc85bd6e068e90ca7804b0c
MD5 3992fb3c682fde8a0452096e3e6056ae
BLAKE2b-256 08d80bf8a3db1631f7d0d98290526b1b922f6a44a40debd1ff6fd3807f9bf3fb

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for rpgp_py-0.19.3-cp310-abi3-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 566b3815e8aeed3c5c24918d6258ad2d331bb3919e1b0b4819a1cdc7a9c0712b
MD5 389f669f6e01d744c2663abf286c350c
BLAKE2b-256 8a1d8d74acd63146ee9656dc8dbb1f8887f4be877ec191bc588a617af65c299d

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for rpgp_py-0.19.3-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 ca6ae6fd8db2daa8ea2f6cba11ba3242ad352d019ff9e72ed9a65ed7c26b8aa7
MD5 c7e7ee06d28e0589ba54b23f10b8133e
BLAKE2b-256 803a3fed70dba5d09ef31cb8cd9f4ecd0473909ac874988cddabaee5f927064d

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for rpgp_py-0.19.3-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 ca05e555373f98b8b36245db72e5dd358930da449a089780ef9055279f45b4af
MD5 5c3bfedfc374940467eb1f72484a2a4f
BLAKE2b-256 342121e826de50cf292364c7b3d3631de725b37010a16488011edfe3db134b52

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for rpgp_py-0.19.3-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 773048ee588da94144439113aaad4e4ff53952b8db99e9b8a37dc30db61c0329
MD5 daedec900bbec28a5e17d3df7348a5ea
BLAKE2b-256 801ccb8e0ff943d47bce91356914c71139e08bac84f5d050b069d1eb2d996e8f

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for rpgp_py-0.19.3-cp310-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 aa51879a5b255c2cecc3d33bf9501225c288aac9f4205fe83fefdc77a79a04a1
MD5 737d601d85e25ce89c69ecc80d7bbca2
BLAKE2b-256 c5432868375df3c95b7a199dd6d8728a747a6b0d4c35dcc03fcf1ab5c63958b5

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