PyCryptodome
PyCryptodome is a self-contained Python package providing low-level cryptographic primitives. It offers a comprehensive suite of algorithms for encryption, decryption, hashing, and digital signatures, acting as a maintained fork and drop-in replacement for the outdated PyCrypto library. It supports Python 2.7, Python 3.7+, and PyPy, with a consistent release cadence.
Warnings
- breaking PyCryptodome uses the `Crypto` top-level package name. Installing `pycryptodome` in an environment that also has the unmaintained `PyCrypto` library will cause import conflicts and unexpected behavior. Always use virtual environments and ensure only one is installed.
- breaking Python 3.6 support was removed in version 3.22.0. Users on Python 3.6 will need to pin to an older version of PyCryptodome (e.g., <3.22.0).
- breaking ECB mode is no longer the default for symmetric ciphers. Calling `AES.new(key)` will now fail. ECB is not semantically secure and should generally be avoided.
- breaking Several methods like `sign()`, `verify()`, `encrypt()`, `decrypt()`, `blind()`, `unblind()` were removed from public key objects (RSA, DSA, ElGamal) due to security concerns or maintenance difficulties.
- gotcha A side-channel leakage vulnerability (Manger attack) in OAEP decryption was fixed.
- gotcha An infinite loop bug affecting RC4 ciphers when processing data larger than 4GB was resolved.
- gotcha For HashEdDSA and Ed448, the `sign()` and `verify()` methods incorrectly modified the state of the XOF (eXtendable Output Function).
Install
-
pip install pycryptodome
Imports
- AES
from Crypto.Cipher import AES
- get_random_bytes
from Crypto.Random import get_random_bytes
- PBKDF2
from Crypto.Protocol.KDF import PBKDF2
- RSA
from Crypto.PublicKey import RSA
Quickstart
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from Crypto.Protocol.KDF import PBKDF2
import os
# Simulate a password for key derivation
password = os.environ.get('CRYPTO_PASSWORD', 'mysecretpassword').encode('utf-8')
# Generate a random salt
salt = get_random_bytes(16)
# Derive a strong key from the password and salt
# Use default iterations (or a high number like 1000000)
key = PBKDF2(password, salt, dkLen=32) # 32 bytes for AES-256
# The data to encrypt
data = b"This is a super secret message."
# Encrypt with AES GCM
# A nonce is automatically generated by AES.new() in GCM mode
cipher = AES.new(key, AES.MODE_GCM)
ciphertext, tag = cipher.encrypt_and_digest(data)
nonce = cipher.nonce
print(f"Original: {data}")
print(f"Salt: {salt.hex()}")
print(f"Nonce: {nonce.hex()}")
print(f"Ciphertext: {ciphertext.hex()}")
print(f"Tag: {tag.hex()}")
# --- Decryption ---
# Re-derive the key using the same password and salt
decryption_key = PBKDF2(password, salt, dkLen=32)
# Create a new cipher object for decryption using the received key and nonce
decrypt_cipher = AES.new(decryption_key, AES.MODE_GCM, nonce=nonce)
# Decrypt and verify
try:
plaintext = decrypt_cipher.decrypt_and_verify(ciphertext, tag)
print(f"Decrypted: {plaintext}")
except ValueError:
print("Decryption failed or message was tampered with!")