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.
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
| Install | ✓ · 0.3s | 1 package on disk · 7 MB |
| Import | ✓ | import 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 vulns | 0 | (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.
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.
- 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.
- Old PyCrypto must remain in the same interpreter environment. The project warns that both distributions write the `Crypto` namespace; install `pycryptodomex` under `Cryptodome` instead.
- A compliance requirement calls for a FIPS-validated module. The project documentation does not claim a FIPS validation for this implementation.
- You need TLS sessions or certificate-chain verification. PyCryptodome supplies cryptographic building blocks rather than a network security stack.
- The deployment target cannot load compiled extensions and has no supported wheel. Our install included `.so` files, so a source build can require a C toolchain and platform headers.
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.SelfTestRun 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
| Package | Registry | Pick it when |
|---|---|---|
| cryptography | PyPI | Choose it for higher-level recipes, X.509 work, and primitives backed by its OpenSSL/Rust implementation. |
| PyNaCl | PyPI | Choose its libsodium bindings when boxes, secret boxes, and signatures cover the protocol with fewer parameter choices. |
| pycryptodomex | PyPI | Choose 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.

