mrkeyoor.com_
Sun 20 Sept 11:44 UTC
PyPISecurityupdated 20 Sept 2026

pycryptodome review

PyCryptodome 3.23.0 is a low-level cryptography toolkit that imports as `Crypto`. It implements block and stream ciphers, authenticated modes, hashes, MACs, password KDFs, RSA, elliptic-curve operations, signatures, secret sharing, and format helpers without wrapping OpenSSL. Performance-sensitive sections ship as compiled extensions. The 3.23 release adds RFC 3394 AES key wrap and RFC 5649 padded key wrap, publishes Windows ARM wheels, and fixes HashEdDSA and Ed448 signing and verification changing the underlying XOF state. Our Python 3.12 check imported `Crypto` in 0.01 seconds with no other Python packages pulled in.

Verdict

PyCryptodome 3.23.0 installed in 0.3 seconds, used 7 MB, imported in 0.01 seconds, and produced zero pip-audit findings in our sandbox. It is a sound fit when a named protocol requires its primitives; product teams designing their own encryption should choose a narrower construction with fewer unsafe decisions.

We installed it

Lab card: what happened when we installed pycryptodomeScreenshot of pycryptodome documentation
Install✓ · 0.3s1 package on disk · 7 MB
Importimport Crypto in 0.01s · compiled extensions · py.typed · requires Python >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*
Known vulns0(pip-audit)

Answers from our run

Does pycryptodome install cleanly?

Yes. In a fresh container with an empty cache, pip install pycryptodome finished in 0.3s, leaving 1 package and 7 MB on disk. pip-audit reported no known vulnerabilities.

What does pycryptodome need to run?

Python >=2.7, !=3.0., !=3.1., !=3.2., !=3.3., !=3.4., !=3.5., !=3.6.*, and a platform wheel with compiled extensions. In our run import Crypto succeeded in 0.01s, and the package ships py.typed for type checkers.

pycryptodome or cryptography: which should you use?

cryptography: Choose it for higher-level recipes, X.509 work, and primitives backed by its OpenSSL/Rust implementation. PyCryptodome 3.23.0 installed in 0.3 seconds, used 7 MB, imported in 0.01 seconds, and produced zero pip-audit findings in our sandbox.

When should you not use pycryptodome?

You are inventing an encrypted record format for a new product. A higher-level recipe narrows choices around nonces, tags, associated data, and key derivation.

API stability4/5PyCryptodome 3.x keeps algorithm modules under `Crypto` and uses consistent object operations such as `new`, `update`, `encrypt_and_digest`, `decrypt_and_verify`, `sign`, and `verify`. Release 3.23 adds `MODE_KW` and `MODE_KWP` without displacing existing modes. Stability at the method level does not make serialized data interchangeable, so applications still need fixed algorithms, parameter records, and compatibility tests.
Docs4/5The official documentation returned HTTP 200 and provides separate references for ciphers, authenticated modes, hashes, MACs, public-key algorithms, signatures, KDFs, key formats, and protocol helpers. Its GCM page states the 12-byte interoperability recommendation and examples retain nonces and tags. The same index documents legacy algorithms, so syntax is easier to find than a safe architecture for a new application.
Maintenance4/5GitHub showed an unarchived repository, a push on 2026-07-18, 3,260 stars, and 92 open issues and pull requests. Version 3.23.0 shipped in May 2025 with two standardized AES key-wrap modes, Windows ARM wheels, and an XOF-state fix for HashEdDSA and Ed448. The in-progress 3.24 changelog already records a BLAKE2 OID correction, evidence of continued maintenance despite a slower release rhythm.
Ecosystem4/5The supplied usage figure is about 22.1 million downloads per week. Wheels cover CPython, PyPy, macOS, Windows, manylinux, and musllinux targets in the 3.23.0 release files, while `py.typed` exposes typing metadata. The `Crypto` namespace eases PyCrypto replacement but creates a collision risk; the separately published `pycryptodomex` package exists specifically to install the same API under `Cryptodome`.

Use it if

  • A protocol specifies AES-GCM, AES-KWP, RSA-OAEP, EdDSA, HPKE, or another primitive that PyCryptodome implements directly.
  • A maintained application still imports the `Crypto` namespace left by PyCrypto and can isolate that namespace in its own environment.
  • You need serialized keys, signatures, hashes, and KDFs in one self-contained distribution instead of depending on the system OpenSSL build.
  • The engineers choosing parameters already understand nonce uniqueness, authenticated decryption, key separation, and format versioning.
Skip it if

Setup reality

We installed pycryptodome 3.23.0 in a clean Python 3.12 Bookworm container. The install finished in 0.3 seconds and left one package using 7 MB. It declared zero direct dependencies, and pip-audit reported zero known vulnerabilities. import Crypto completed in 0.01 seconds. The distribution includes py.typed and compiled .so modules. Its metadata lists BSD and Public Domain licensing and accepts Python 2.7 while excluding Python 3.0 through 3.6.

Choose the namespace before writing imports. pip install pycryptodome provides Crypto; pip install pycryptodomex provides Cryptodome. The README says PyCrypto and PyCryptodome interfere when installed together, so give the replacement its own virtual environment or use the x distribution. Common platforms receive wheels, including Windows ARM in 3.23.0. An unsupported interpreter or platform falls back to building the C extensions and may also use system GMP for faster public-key operations.

Encryption calls do not define your storage format. For AES-GCM, keep a version, key identifier, nonce, ciphertext, authentication tag, and any associated-data convention. The documentation recommends a 12-byte GCM nonce for interoperability; that nonce must stay unique for a given key. Do not expose decrypted bytes before decrypt_and_verify accepts the tag. Password-based keys also need the salt and KDF cost parameters stored beside the record.

Compatibility algorithms remain available because existing protocols need them. Their presence is not approval for ECB, unauthenticated CBC, raw RSA, or new SHA-1 signatures. RSA-OAEP handles a bounded payload, so use it to wrap a random data key and encrypt bulk bytes with an authenticated symmetric mode. Keep long-lived keys outside application config, rotate with explicit key IDs, and run python -m Crypto.SelfTest on unusual wheel or source-build targets.

Patterns

Run the bundled self-test verify-installation

python -m Crypto.SelfTest

Run the suite after a source build or on an unusual platform. A `pycryptodomex` install uses `python -m Cryptodome.SelfTest` instead.

Encrypt a record with AES-GCM encrypt-aes-gcm

from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes

key = get_random_bytes(32)
nonce = get_random_bytes(12)
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
ciphertext, tag = cipher.encrypt_and_digest(b'invoice 42')

Store the 12-byte nonce and tag with the ciphertext. Reusing that nonce with the same key breaks GCM security.

Authenticate an encrypted record decrypt-aes-gcm

cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
try:
    plaintext = cipher.decrypt_and_verify(ciphertext, tag)
except ValueError:
    raise InvalidRecord

`decrypt_and_verify` rejects a changed nonce, tag, ciphertext, or wrong key. Do not process plaintext before this call succeeds.

Authenticate metadata without encrypting it bind-associated-data

cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
cipher.update(b'tenant=acme;format=2')
ciphertext, tag = cipher.encrypt_and_digest(payload)

The decryptor must pass the exact same associated bytes to `update()` before it verifies the tag.

Turn a password into a key derive-key-with-scrypt

from Crypto.Protocol.KDF import scrypt
from Crypto.Random import get_random_bytes

salt = get_random_bytes(16)
key = scrypt(password.encode('utf-8'), salt, 32, N=2**15, r=8, p=1)

Persist the 16-byte salt and all cost parameters. Benchmark the work factor on your own login or batch workload before fixing it in a format.

Generate and protect an RSA key export-rsa-private-key

from Crypto.PublicKey import RSA

private_key = RSA.generate(3072)
private_pem = private_key.export_key(
    passphrase=passphrase,
    pkcs=8,
    protection='scryptAndAES128-CBC',
)
public_pem = private_key.public_key().export_key()

The passphrase protects the serialized file. It does not replace access controls or secure handling while the private key is loaded.

Wrap a small key with RSA-OAEP encrypt-key-with-rsa-oaep

from Crypto.Cipher import PKCS1_OAEP
from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA

public_key = RSA.import_key(public_pem)
wrapped_key = PKCS1_OAEP.new(public_key, hashAlgo=SHA256).encrypt(data_key)

OAEP accepts only a short payload determined by the RSA modulus and hash. Wrap a random symmetric key instead of a document.

Sign and verify with RSA-PSS sign-and-verify-pss

from Crypto.Hash import SHA256
from Crypto.Signature import pss

digest = SHA256.new(message)
signature = pss.new(private_key).sign(digest)
pss.new(private_key.public_key()).verify(SHA256.new(message), signature)

`verify` returns `None` on success and raises `ValueError` or `TypeError` on failure. Keep the hash and PSS parameters consistent across systems.

Create an Ed25519 signature sign-with-eddsa

from Crypto.PublicKey import ECC
from Crypto.Signature import eddsa

private_key = ECC.generate(curve='Ed25519')
signer = eddsa.new(private_key, mode='rfc8032')
signature = signer.sign(message)
eddsa.new(private_key.public_key(), mode='rfc8032').verify(message, signature)

EdDSA signs the message bytes directly in this API. A failed verification raises `ValueError`.

Wrap arbitrary-length key material wrap-key-with-kwp

from Crypto.Cipher import AES

wrapped = AES.new(kek, AES.MODE_KWP).seal(data_key)
restored = AES.new(kek, AES.MODE_KWP).unseal(wrapped)

`MODE_KWP` was added in 3.23.0 and accepts input that is not a multiple of 8 bytes. `unseal` verifies the wrapping integrity check.

Compute and verify an HMAC authenticate-with-hmac

from Crypto.Hash import HMAC, SHA256

tag = HMAC.new(mac_key, message, SHA256).digest()

verifier = HMAC.new(mac_key, message, SHA256)
verifier.verify(tag)

Use `verify()` instead of comparing tags with `==`; it raises `ValueError` when authentication fails.

Split and restore a secret split-secret

from Crypto.Protocol.SecretSharing import Shamir

shares = Shamir.split(3, 5, secret_16_bytes)
restored = Shamir.combine(shares[:3])

This API splits exactly 16 bytes. Shares need authenticated transport and storage because Shamir splitting alone does not detect tampering.

Alternatives

PackageRegistryPick it when
cryptographyPyPIChoose it for higher-level recipes, X.509 work, and primitives backed by its OpenSSL/Rust implementation.
PyNaClPyPIChoose its libsodium bindings when boxes, secret boxes, and signatures cover the protocol with fewer parameter choices.
pycryptodomexPyPIChoose the same PyCryptodome code under `Cryptodome` when another package already owns the `Crypto` namespace.

More security guides

cryptography · pyjwt · jose · requests-oauthlib · oauthlib · dompurify · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.