PYCRYPTODOME(1)
🌸 NAME 🌸
pycryptodome — the PyCryptodome documentation, bestie. That's the whole name line. (Also, fair warning: the raw page has a stray .SH PYCRYPTODOME wedged in there right after, which is just the next section header leaking through — don't panic, it's not a command, it's not a flag, it's just roff being roff. 💅)
✨ PYCRYPTODOME ✨
Okay so picture this: PyCryptodome is a self-contained Python package of low-level cryptographic primitives. Self-contained. Like, it doesn't need to hold hands with some C library to function. Iconic behavior honestly. 🔥
It supports Python 2.7, Python 3.7 and newer, and PyPy. Yes, PyPy gets first-class treatment, we love to see it.
The installation procedure depends on which package you want the library to be in. PyCryptodome can be used as:
- An almost drop-in replacement for the old PyCrypto library. You install it with:
pip install pycryptodome
In this case, all modules are installed under the Crypto package.
One must avoid having both PyCrypto and PyCryptodome installed at the same time, as they will interfere with each other. 😭 So this option is therefore recommended only when you are sure that the whole application is deployed in a virtualenv. Like, sure sure. Not "I think it's fine" sure.
- A library independent of the old PyCrypto. You install it with:
pip install pycryptodomex
In this case, all modules are installed under the Cryptodome package. PyCrypto and PyCryptodome can coexist. Peaceful coexistence era. 🫶
For faster public key operations in Unix, you should install GMP in your system. Little performance slay, no cap.
🦋 the fork tea 🦋
So PyCryptodome is a fork of PyCrypto. It brings the following enhancements with respect to the last official version of PyCrypto (2.6.1). Brace yourself, this list is long and it ate:
- Authenticated encryption modes (GCM, CCM, EAX, SIV, OCB, KW, KWP)
- Hybrid Public Key Encryption (HPKE)
- Accelerated AES on Intel platforms via AES-NI
- First class support for PyPy
- Elliptic curves cryptography (NIST P-curves; Ed25519, Ed448, Curve25519, Curve448)
- Better and more compact API (nonce and iv attributes for ciphers, automatic generation of random nonces and IVs, simplified CTR cipher mode, and more)
- SHA-3 hash algorithms (FIPS 202) and derived functions (NIST SP-800 185):
- SHAKE128 and SHA256 XOFs
- cSHAKE128 and cSHAKE256 XOFs
- KMAC128 and KMAC256
- TupleHash128 and TupleHash256
- KangarooTwelve, TurboSHAKE128, and TurboSHAKE256 XOFs
- Truncated hash algorithms SHA-512/224 and SHA-512/256 (FIPS 180-4)
- BLAKE2b and BLAKE2s hash algorithms
- Salsa20 and ChaCha20/XChaCha20 stream ciphers
- Poly1305 MAC
- ChaCha20-Poly1305 and XChaCha20-Poly1305 authenticated ciphers
- scrypt, bcrypt, HKDF, and NIST SP 800 108r1 Counter Mode key derivation functions
- Deterministic (EC)DSA and EdDSA
- Password-protected PKCS#8 key containers
- Shamir's Secret Sharing scheme
- Random numbers get sourced directly from the OS (and not from a CSPRNG in userspace)
- Simplified install process, including better support for Windows
- Cleaner RSA and DSA key generation (largely based on FIPS 186-4)
- Major clean ups and simplification of the code base
PyCryptodome is not a wrapper to a separate C library like OpenSSL. To the largest possible extent, algorithms are implemented in pure Python. Only the pieces that are extremely critical to performance (e.g. block ciphers) are implemented as C extensions. So it's giving "Python-first, C only where it counts." 💅
For more information, see the homepage.
For security issues, please send an email to security@pycryptodome.org. (Fr fr, that's the one address you actually want to use for that.)
All the code can be downloaded from GitHub.
🎀 FEATURES 🎀
This page lists the low-level primitives that PyCryptodome provides.
You are expected to have a solid understanding of cryptography and security engineering to successfully use them. Like, this is not the "copy a snippet from Stack Overflow and vibe" library, iykyk.
You must also be able to recognize that some primitives are obsolete (e.g. TDES) or even unsecure (RC4). They are provided only to enable backward compatibility where required by the applications. Translate: they're here because legacy stuff needs them, not because you should be picking them for your new project. 🙈
A list of useful resources in that area can be found on Matthew Green's blog.
- Symmetric ciphers:
- AES
- Single and Triple DES (legacy)
- CAST-128 (legacy)
- RC2 (legacy)
- Traditional modes of operations for symmetric ciphers:
- ECB
- CBC
- CFB
- OFB
- CTR
- OpenPGP (a variant of CFB, RFC4880)
- Authenticated Encryption:
- CCM (AES only)
- EAX
- GCM (AES only)
- SIV (AES only)
- OCB (AES only)
- ChaCha20-Poly1305
- Stream ciphers:
- Salsa20
- ChaCha20 and XChaCha20
- RC4 (legacy)
- Cryptographic hashes:
- SHA-1
- SHA-2 hashes (224, 256, 384, 512, 512/224, 512/256)
- SHA-3 hashes (224, 256, 384, 512) and XOFs (SHAKE128, SHAKE256)
- Functions derived from SHA-3 (cSHAKE128, cSHAKE256, TupleHash128, TupleHash256)
- KangarooTwelve, TurboSHAKE128, TurboSHAKE256 (XOF)
- Keccak (original submission to SHA-3)
- BLAKE2b and BLAKE2s
- RIPE-MD160 (legacy)
- MD5 (legacy)
- Message Authentication Codes (MAC):
- HMAC
- CMAC
- KMAC128 and KMAC256
- Poly1305
- Asymmetric key generation:
- RSA
- ECC (NIST P-curves; Ed25519, Ed448, Curve25519, Curve448)
- DSA
- ElGamal (legacy)
- Export and import format for asymmetric keys:
- PEM (clear and encrypted)
- PKCS#8 (clear and encrypted)
- ASN.1 DER
- Asymmetric ciphers:
- PKCS#1 (RSA)
- RSAES-PKCS1-v1_5
- RSAES-OAEP
- PKCS#1 (RSA)
- Asymmetric digital signatures:
- PKCS#1 (RSA)
- RSASSA-PKCS1-v1_5
- RSASSA-PSS
- (EC)DSA
- Nonce-based (FIPS 186-3)
- Deterministic (RFC6979)
- EdDSA
- PKCS#1 (RSA)
- Key derivation:
- PBKDF2
- scrypt
- HKDF
- PBKDF1 (legacy)
- Other cryptographic protocols:
- HPKE
- Shamir Secret Sharing
- Padding
- PKCS#7
- ISO-7816
- X.923
🌸 INSTALLATION 🌸
okay bestie, so how you install this thing depends on which package you want the library to live in. PyCryptodome can be used as:
- An almost drop-in replacement for the old PyCrypto library. You install it with:
pip install pycryptodome
In this case, all modules are installed under the Crypto package. You can test everything is right with:
pip install pycryptodome-test-vectors
python -m Crypto.SelfTest
⚠️ One must avoid having both PyCrypto and PyCryptodome installed at the same time, as they will interfere with each other. This option is therefore recommended only when you are sure that the whole application is deployed in a virtualenv. 💅
- A library independent of the old PyCrypto. You install it with:
pip install pycryptodomex
You can test everything is right with:
pip install pycryptodome-test-vectors
python -m Cryptodome.SelfTest
In this case, all modules are installed under the Cryptodome package. The old PyCrypto and PyCryptodome can coexist. 🫶
🎀 NOTE: If you intend to run PyCryptodome with Python 2.7 under Windows, you must first install the Microsoft Visual C++ 2015 Redistributable. That is not necessary if you use Python 3.
The procedures below go a bit more in detail, by explaining how to setup the environment for compiling the C extensions for each OS, and how to install the GMP library. 🧚
🐧 Compiling in Linux Ubuntu
💜 NOTE: If you want to install under theCryptopackage, replace belowpycryptodomexwithpycryptodome.
For Python 2.x:
$ sudo apt-get install build-essential python-dev
$ pip install pycryptodomex
$ pip install pycryptodome-test-vectors
$ python -m Cryptodome.SelfTest
For Python 3.x:
$ sudo apt-get install build-essential python3-dev
$ pip install pycryptodomex
$ pip install pycryptodome-test-vectors
$ python3 -m Cryptodome.SelfTest
For PyPy:
$ sudo apt-get install build-essential pypy-dev
$ pip install pycryptodomex
$ pip install pycryptodome-test-vectors
$ pypy -m Cryptodome.SelfTest
🎩 Compiling in Linux Fedora
💜 NOTE: If you want to install under theCryptopackage, replace belowpycryptodomexwithpycryptodome.
For Python 2.x:
$ sudo yum install gcc gmp python-devel
$ pip install pycryptodomex
$ pip install pycryptodome-test-vectors
$ python -m Cryptodome.SelfTest
For Python 3.x:
$ sudo yum install gcc gmp python3-devel
$ pip install pycryptodomex
$ pip install pycryptodome-test-vectors
$ python3 -m Cryptodome.SelfTest
For PyPy:
$ sudo yum install gcc gmp pypy-devel
$ pip install pycryptodomex
$ pip install pycryptodome-test-vectors
$ pypy -m Cryptodome.SelfTest
🪟 Windows (from sources)
💜 NOTE: If you want to install under theCryptopackage, replace belowpycryptodomexwithpycryptodome. That being the case and if you want to run the test, instead ofCryptodome.SelfTest, runCrypto.SelfTest.
Windows does not come with a C compiler like most Unix systems 😭. The simplest way to compile the PyCryptodome extensions from source code is to install the minimum set of Visual Studio components freely made available by Microsoft.
- [Once only] Download Build Tools for Visual Studio 2019. In the installer, select the C++ build tools, the Windows 10 SDK, and the latest version of MSVC v142 x64/x86 build tools.
- Compile and install PyCryptodome:
> pip install pycryptodomex --no-binary :all:
- To make sure everything work fine, run the test suite:
> pip install pycryptodome-test-vectors
> python -m Cryptodome.SelfTest
📚 Documentation
Project documentation is written in reStructuredText and it is stored under Doc/src. To publish it as HTML files, you need to install sphinx and use:
> make -C Doc/ html
It will then be available under Doc/_build/html/. ✨
🔐 PGP verification
All source packages and wheels on PyPI are cryptographically signed. They can be verified with the following PGP key:
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBFTXjPgBEADc3j7vnma9MXRshBPPXXenVpthQD6lrF/3XaBT2RptSf/viOD+
tz85du5XVp+r0SYYGeMNJCQ9NsztxblN/lnKgkfWRmSrB+V6QGS+e3bR5d9OIxzN
7haPxBnyRj//hCT/kKis6fa7N9wtwKBBjbaSX+9vpt7Rrt203sKfcChA4iR3EG89
TNQoc/kGGmwk/gyjfU38726v0NOhMKJp2154iQQVZ76hTDk6GkOYHTcPxdkAj4jS
Dd74M9sOtoOlyDLHOLcWNnlWGgZjtz0z0qSyFXRSuOfggTxrepWQgKWXXzgVB4Jo
0bhmXPAV8vkX5BoG6zGkYb47NGGvknax6jCvFYTCp1sOmVtf5UTVKPplFm077tQg
0KZNAvEQrdWRIiQ1cCGCoF2Alex3VmVdefHOhNmyY7xAlzpP0c8z1DsgZgMnytNn
GPusWeqQVijRxenl+lyhbkb9ZLDq7mOkCRXSze9J2+5aLTJbJu3+Wx6BEyNIHP/f
K3E77nXvC0oKaYTbTwEQSBAggAXP+7oQaA0ea2SLO176xJdNfC5lkQEtMMSZI4gN
iSqjUxXW2N5qEHHex1atmTtk4W9tQEw030a0UCxzDJMhD0aWFKq7wOxoCQ1q821R
vxBH4cfGWdL/1FUcuCMSUlc6fhTM9pvMXgjdEXcoiLSTdaHuVLuqmF/E0wARAQAB
tB9MZWdyYW5kaW4gPGhlbGRlcmlqc0BnbWFpbC5jb20+iQI4BBMBAgAiBQJU14z4
AhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRDabO+N4RaZEn7IEACpApha
vRwPB+Dv87aEyVmjZ96Nb3mxHdeP2uSmUxAODzoB5oJJ1QL6HRxEVlU8idjdf73H
DX39ZC7izD+oYIve9sNwTbKqJCZaTxlTDdgSF1N57eJOlELAy+SqpHtaMJPk7SfJ
l/iYoUYxByPLZU1wDwZEDNzt9RCGy3bd/vF/AxWjdUJJPh3E4j5hswvIGSf8/Tp3
MDROU1BaNBOd0CLvBHok8/xavwO6Dk/fE4hJhd5uZcEPtd1GJcPq51z2yr7PGUcb
oERsKZyG8cgfd7j8qoTd6jMIW6fBVHdxiMxW6/Z45X/vVciQSzzEl/yjPUW42kyr
Ib6M16YmnDzp8bl4NNFvvR9uWvOdUkep2Bi8s8kBMJ7G9rHHJcdVy/tP1ECS9Bse
hN4v5oJJ4v5mM/MiWRGKykZULWklonpiq6CewYkmXQDMRnjGXhjCWrB6LuSIkIXd
gKvDNpJ8yEhAfmpvA4I3laMoof/tSZ7ZuyLSZGLKl6hoNIB13HCn4dnjNBeaXCWX
pThgeOWxV6u1fhz4CeC1Hc8WOYr8S7G8P10Ji6owOcj/a1QuCW8XDB2omCTXlhFj
zpC9dX8HgmUVnbPNiMjphihbKXoOcunRx4ZvqIa8mnTbI4tHtR0K0tI4MmbpcVOZ
8IFJ0nZJXuZiL57ijLREisPYmHfBHAgmh1j/W7kCDQRU14z4ARAA3QATRgvOSYFh
nJOnIz6PO3G9kXWjJ8wvp3yE1/PwwTc3NbVUSNCW14xgM2Ryhn9NVh8iEGtPGmUP
4vu7rvuLC2rBs1joBTyqf0mDghlZrb5ZjXv5LcG9SA6FdAXRU6T+b1G2ychKkhEh
d/ulLw/TKLds9zHhE+hkAagLQ5jqjcQN0iX5EYaOukiPUGmnd9fOEGi9YMYtRdrH
+3bZxUpsRStLBWJ6auY7Bla8NJOhaWpr5p/ls+mnDWoqf+tXCCps1Da/pfHKYDFc
2VVdyM/VfNny9eaczYpnj5hvIAACWChgGDBwxPh2DGdUfiQi/QqrK96+F7ulqz6V
2exX4CL0cPv5fUpQqSU/0R5WApM9bl2+wljFhoCXlydU9HNn+0GatGzEoo3yrV/m
PXv7d6NdZxyOqgxu/ai/z++F2pWUXSBxZN3Gv28boFKQhmtthTcFudNUtQOchhn8
Pf/ipVISqrsZorTx9Qx4fPScEWjwbh84Uz20bx0sQs1oYcek2YG5RhEdzqJ6W78R
S/dbzlNYMXGdkxB6C63m8oiGvw0hdN/iGVqpNAoldFmjnFqSgKpyPwfLmmdstJ6f
xFZdGPnKexCpHbKr9fg50jZRenIGai79qPIiEtCZHIdpeemSrc7TKRPV3H2aMNfG
L5HTqcyaM2+QrMtHPMoOFzcjkigLimMAEQEAAYkCHwQYAQIACQUCVNeM+AIbDAAK
CRDabO+N4RaZEo7lD/45J6z2wbL8aIudGEL0aY3hfmW3qrUyoHgaw35KsOY9vZwb
cZuJe0RlYptOreH/NrbR5SXODfhd2sxYyyvXBOuZh9i7OOBsrAd5UE01GCvToPwh
7IpMV3GSSAB4P8XyJh20tZqiZOYKhmbf29gUDzqAI6GzUa0U8xidUKpW2zqYGZjp
wk3RI1fS7tyi/0N8B9tIZF48kbvpFDAjF8w7NSCrgRquAL7zJZIG5o5zXJM/ffF3
67Dnz278MbifdM/HJ+Tj0R0Uvvki9Z61nT653SoUgvILQyC72XI+x0+3GQwsE38a
5aJNZ1NBD3/v+gERQxRfhM5iLFLXK0Xe4K2XFM1g0yN4L4bQPbhSCq88g9Dhmygk
XPbBsrK0NKPVnyGyUXM0VpgRbot11hxx02jC3HxS1nlLF+oQdkKFzJAMOU7UbpX/
oO+286J1FmpG+fihIbvp1Quq48immtnzTeLZbYCsG4mrM+ySYd0Er0G8TBdAOTiN
3zMbGX0QOO2fOsJ1d980cVjHn5CbAo8C0A/4/R2cXAfpacbvTiNq5BVk9NKa2dNb
kmnTStP2qILWmm5ASXlWhOjWNmptvsUcK+8T+uQboLioEv19Ob4j5Irs/OpOuP0K
v4woCi9+03HMS42qGSe/igClFO3+gUMZg9PJnTJhuaTbytXhUBgBRUPsS+lQAQ==
=DpoI
-----END PGP PUBLIC KEY BLOCK-----
💀 COMPATIBILITY WITH PYCRYPTO 💀
okay so here's the tea ☕ — PyCryptodome exposes almost the same API as the old PyCrypto so that most applications will run unmodified. However, a very few breaks in compatibility had to be introduced for those parts of the API that represented a security hazard or that were too hard to maintain.
Specifically, for public key cryptography:
- The following methods from public key objects (RSA, DSA, ElGamal) have been removed:
sign()verify()encrypt()decrypt()blind()unblind()
Applications should be updated to use instead:
Crypto.Cipher.PKCS1_OAEPfor encrypting using RSA.Crypto.Signature.pkcs1_15orCrypto.Signature.pssfor signing using RSA.Crypto.Signature.DSSfor signing using DSA.
- Method:
generate()for public key modules does not accept theprogress_funcparameter anymore. - Ambiguous method
sizefrom RSA, DSA and ElGamal key objects have been removed. Instead, use methodssize_in_bytes()andsize_in_bits()and check the documentation. 👑 - The 3 public key object types (RSA, DSA, ElGamal) are now unpickable. You must use the
export_key()method of each key object and select a good output format: for private keys that means a good password-based encryption scheme. 🔐 - Removed attribute
Crypto.PublicKey.RSA.algorithmIdentifier. - Removed
Crypto.PublicKey.RSA.RSAImplementation(which should have been private in the first place). Same forCrypto.PublicKey.DSA.DSAImplementation.
For symmetric key cryptography:
- Symmetric ciphers do not have ECB as default mode anymore. ECB is not semantically secure and it exposes correlation across blocks. An expression like
AES.new(key)will now fail. If ECB is the desired mode, one has to explicitly useAES.new(key, AES.MODE_ECB). Crypto.Cipher.DES3does not allow keys that degenerate to Single DES.- Parameter
segment_sizecannot be0for the CFB mode. - Parameters
disabled_shortcutandoverflowcannot be passed anymore toCrypto.Util.Counter.new. Parameterallow_wraparoundis ignored (counter block wraparound will always be checked). - The
counterparameter of a CTR mode cipher must be generated viaCrypto.Util.Counter. It cannot be a generic callable anymore. - Keys for
Crypto.Cipher.ARC2,Crypto.Cipher.ARC4andCrypto.Cipher.Blowfishmust be at least 40 bits long (still very weak 😬).
The following packages, modules and functions have been removed:
Crypto.Random.OSRNG,Crypto.Util.winrandomandCrypto.Random.randpool. You should useCrypto.Randomonly.Crypto.Cipher.XOR. If you just want to XOR data, useCrypto.Util.strxor.Crypto.Hash.new. UseCrypto.Hash.<algorithm>.new()instead.Crypto.Protocol.AllOrNothingCrypto.Protocol.ChaffingCrypto.Util.number.getRandomNumberCrypto.pct_warnings
Others:
- Support for any Python version older than 2.6 is dropped.
✨ API DOCUMENTATION ✨
🔐 Crypto.Cipher package
🌈 Introduction
The Crypto.Cipher package contains algorithms for protecting the confidentiality of data. 💖
There are three types of encryption algorithms:
- Symmetric ciphers — literally every party uses the same key, for both decrypting and encrypting data. The vibe: symmetric ciphers are typically very fast and can process very large amounts of data. 🚀
- Asymmetric ciphers — senders and receivers use different keys. Senders encrypt with public keys (non-secret) whereas receivers decrypt with private keys (secret). The trade-off is real though: asymmetric ciphers are typically very slow and can process only very small payloads. Example: PKCS#1 OAEP (RSA). 💅
- Hybrid ciphers — the two types above get combined in a construction that inherits the benefits of both. An asymmetric cipher is used to protect a short-lived symmetric key, and a symmetric cipher (under that key) encrypts the actual message. Bestie behaviour, honestly. 🫶
✨ API principles ✨
[image] Generic state diagram for a cipher object.
The base API of a cipher is fairly simple, ngl:
- You instantiate a cipher object by calling the
new()function from the relevant cipher module (e.g.Crypto.Cipher.AES.new()). The first parameter is always the cryptographic key; its length depends on the particular cipher. You can (and sometimes must) pass additional cipher- or mode-specific parameters tonew()(such as anonceor a mode of operation). 🔑 - For encrypting data, you call the
encrypt()method of the cipher object with the plaintext. The method returns the piece of ciphertext. Alternatively, with theoutputparameter you can specify a pre-allocated buffer for the result.
For most algorithms, you may call encrypt() multiple times (i.e. once for each piece of plaintext).
- For decrypting data, you call the
decrypt()method of the cipher object with the ciphertext. The method returns the piece of plaintext. Theoutputparameter can be passed here too.
For most algorithms, you may call decrypt() multiple times (i.e. once for each piece of ciphertext).
NOTE: 🚨 Plaintexts and ciphertexts (input/output) can only bebytes,bytearrayormemoryview. In Python 3, you cannot pass strings. In Python 2, you cannot pass Unicode strings. That's just how it is, bestie.
Often, the sender has to deliver to the receiver other data in addition to ciphertext alone (e.g. initialization vectors or nonces, MAC tags, etc). Don't forget to ship those along, or your receiver is gonna be stuck staring at gibberish. 😭
This is a basic example:
>>> from Crypto.Cipher import Salsa20
>>>
>>> key = b'0123456789012345'
>>> cipher = Salsa20.new(key)
>>> ciphertext = cipher.encrypt(b'The secret I want to send.')
>>> ciphertext += cipher.encrypt(b'The second part of the secret.')
>>> print cipher.nonce # A byte string you must send to the receiver too
🌸 Symmetric ciphers 🌸
There are two types of symmetric ciphers:
- Stream ciphers — the most natural kind of ciphers: they encrypt data one byte at a time. See
ChaCha20andXChaCha20andSalsa20. 🌊 - Block ciphers — ciphers that can only operate on a fixed amount of data. The most important block cipher is
AES, which has a block size of128 bits(16 bytes). 🧱
In general, a block cipher is mostly useful only together with a mode of operation, which allows one to encrypt a variable amount of data. Some modes (like CTR) effectively turn a block cipher into a stream cipher.
The widespread consensus is that ciphers that provide only confidentiality, without any form of authentication, are undesirable. Like, honestly? Don't. Instead, primitives have been defined to integrate symmetric encryption and authentication (MAC). For instance: 👇
- Modern modes of operation for block ciphers (like
GCM). - Stream ciphers paired with a MAC function, like
ChaCha20-Poly1305andXChaCha20-Poly1305.
🎀 Classic modes of operation for symmetric block ciphers 🎀
A block cipher uses a symmetric key to encrypt data of fixed and very short length (the block size), such as 16 bytes for AES. In order to cope with data of arbitrary length, the cipher must be combined with a mode of operation. Slay, that's the whole point.
You create a cipher object with the new() function in the relevant module under Crypto.Cipher:
- the first parameter is always the cryptographic key (a byte string)
- the second parameter is always the constant that selects the desired mode of operation
Constants for each mode of operation are defined at the module level for each algorithm. Their name starts with MODE_, for instance Crypto.Cipher.AES.MODE_CBC. Note that not all ciphers support all modes. 👀
For instance:
>>> from Crypto.Cipher import AES
>>> from Crypto.Random import get_random_bytes
>>>
>>> key = get_random_bytes(16)
>>> cipher = AES.new(key, AES.MODE_CBC)
>>>
>>> # You can now use use cipher to encrypt or decrypt...
The state machine for a cipher configured with a classic mode is:
[image] Generic state diagram for a cipher object.
What follows is a list of classic modes of operation: they all provide confidentiality but not data integrity (unlike modern AEAD modes, which are described in another section). Keep that in mind, okay? 😌
💜 ECB mode 💜
Electronic CodeBook. The most basic but also the weakest mode of operation. Each block of plaintext is encrypted independently of any other block. No thoughts, no chaining, no nada.
WARNING: The ECB mode should not be used because it is semantically insecure. For one, it exposes correlation between blocks. Like, it's giving "i put the same outfit on every block" and everyone can tell. 💀
The new() function at the module level under Crypto.Cipher instantiates a new ECB cipher object for the relevant base algorithm. In the following definition, <algorithm> could be AES:
Crypto.Cipher.<algorithm>.new(key, mode)
Create a new ECB object, using <algorithm> as the base block cipher.
Parameters
key(bytes) — the cryptographic keymode— the constantCrypto.Cipher.<algorithm>.MODE_ECB
Returns
an ECB cipher object
The method encrypt() (and likewise decrypt()) of an ECB cipher object expects data to have length multiple of the block size (e.g. 16 bytes for AES). You might need to use Crypto.Util.Padding to align the plaintext to the right boundary. Do not skip this step, it's not optional, it's a whole thing. 🫠
🦋 CBC mode 🦋
Ciphertext Block Chaining, defined in NIST SP 800-38A, section 6.2. It is a mode of operation where each plaintext block gets XOR-ed with the previous ciphertext block prior to encryption. Chain chain chain, that's the vibe. ⛓️
The new() function at the module level under Crypto.Cipher instantiates a new CBC cipher object for the relevant base algorithm. In the following definition, <algorithm> could be AES:
Crypto.Cipher.<algorithm>.new(key, mode, *, iv=None)
Create a new CBC object, using <algorithm> as the base block cipher.
Parameters
key(bytes) — the cryptographic keymode— the constantCrypto.Cipher.<algorithm>.MODE_CBCiv(bytes) — the Initialization Vector. A piece of data unpredictable to adversaries. It is as long as the block size (e.g.16 bytesfor AES). If not present, the library creates a random IV value.
Returns
a CBC cipher object
The method encrypt() (and likewise decrypt()) of a CBC cipher object expects data to have length multiple of the block size (e.g. 16 bytes for AES). You might need to use Crypto.Util.Padding to align the plaintext to the right boundary. Same story as ECB, bestie. 💅
A CBC cipher object has a read-only attribute iv, holding the Initialization Vector (bytes).
Example (encryption):
>>> import json
>>> from base64 import b64encode
>>> from Crypto.Cipher import AES
>>> from Crypto.Util.Padding import pad
>>> from Crypto.Random import get_random_bytes
>>>
>>> data = b"secret"
>>> key = get_random_bytes(16)
>>> cipher = AES.new(key, AES.MODE_CBC)
>>> ct_bytes = cipher.encrypt(pad(data, AES.block_size))
>>> iv = b64encode(cipher.iv).decode('utf-8')
>>> ct = b64encode(ct_bytes).decode('utf-8')
>>> result = json.dumps({'iv':iv, 'ciphertext':ct})
>>> print(result)
'{"iv": "bWRHdzkzVDFJbWNBY0EwSmQ1UXFuQT09", "ciphertext": "VDdxQVo3TFFCbXIzcGpYa1lJbFFZQT09"}'
Example (decryption):
>>> import json
>>> from base64 import b64decode
>>> from Crypto.Cipher import AES
>>> from Crypto.Util.Padding import unpad
>>>
>>> # We assume that the key was securely shared beforehand
>>> try:
>>> b64 = json.loads(json_input)
>>> iv = b64decode(b64['iv'])
>>> ct = b64decode(b64['ciphertext'])
>>> cipher = AES.new(key, AES.MODE_CBC, iv)
>>> pt = unpad(cipher.decrypt(ct), AES.block_size)
>>> print("The message was: ", pt)
>>> except (ValueError, KeyError):
>>> print("Incorrect decryption")
🔥 CTR mode 🔥
CounTeR mode, defined in NIST SP 800-38A, section 6.5 and Appendix B. This mode turns the block cipher into a stream cipher. Each byte of plaintext is XOR-ed with a byte taken from a keystream: the result is the ciphertext. The keystream is generated by encrypting a sequence of counter blocks with ECB. Obsessed with how clever that is, honestly. ✨
[image]
A counter block is exactly as long as the cipher block size (e.g. 16 bytes for AES). It consists of the concatenation of two pieces:
- a fixed
nonce, set at initialization. - a variable counter, which gets increased by
1for any subsequent counter block. The counter is big endian encoded.
The new() function at the module level under Crypto.Cipher instantiates a new CTR cipher object for the relevant base algorithm. In the following definition, <algorithm> could be AES:
Crypto.Cipher.<algorithm>.new(key, mode, *, nonce=None,
initial_value=None, counter=None)
Create a new CTR object, using <algorithm> as the base block cipher.
Parameters
key(bytes) — the cryptographic keymode— the constantCrypto.Cipher.<algorithm>.MODE_CTRnonce(bytes) — the value of the fixed nonce. It must be unique for the combination message/key. Its length varies from0to the block size minus1. If not present, the library creates a random nonce of length equal to block size/2.initial_value(integer or bytes) — the value of the counter for the first counter block. It can be either an integer or bytes (which is the same integer, just big endian encoded). If not specified, the counter starts at0.counter— a custom counter object created withCrypto.Util.Counter.new(). This allows the definition of a more complex counter block.
Returns
a CTR cipher object
okay so, CTR, one last little detail before we move on 💅
The methods encrypt() and decrypt() of a CTR cipher object accept data of any length (i.e. padding is not needed, bestie — not even a little). Both raise an OverflowError exception as soon as the counter wraps around to repeat the original value. That’s the “nope, absolutely not” moment 😭.
The CTR cipher object has a read-only attribute nonce (bytes).
Example (encryption):
>>> import json
>>> from base64 import b64encode
>>> from Crypto.Cipher import AES
>>> from Crypto.Random import get_random_bytes
>>>
>>> data = b"secret"
>>> key = get_random_bytes(16)
>>> cipher = AES.new(key, AES.MODE_CTR)
>>> ct_bytes = cipher.encrypt(data)
>>> nonce = b64encode(cipher.nonce).decode('utf-8')
>>> ct = b64encode(ct_bytes).decode('utf-8')
>>> result = json.dumps({'nonce':nonce, 'ciphertext':ct})
>>> print(result)
{"nonce": "XqP8WbylRt0=", "ciphertext": "Mie5lqje"}
Example (decryption):
>>> import json
>>> from base64 import b64decode
>>> from Crypto.Cipher import AES
>>>
>>> # We assume that the key was securely shared beforehand
>>> try:
>>> b64 = json.loads(json_input)
>>> nonce = b64decode(b64['nonce'])
>>> ct = b64decode(b64['ciphertext'])
>>> cipher = AES.new(key, AES.MODE_CTR, nonce=nonce)
>>> pt = cipher.decrypt(ct)
>>> print("The message was: ", pt)
>>> except (ValueError, KeyError):
>>> print("Incorrect decryption")
CFB mode 🌀
Cipher FeedBack, defined in NIST SP 800-38A, section 6.3. It’s a mode of operation that turns the block cipher into a stream cipher. Each byte of plaintext gets XOR-ed with a byte taken from a keystream: the result is the ciphertext. Literally the glow-up from blocky to streamy ✨.
The keystream is obtained on a per-segment basis: the plaintext is broken up in segments (from 1 byte up to the size of a block). Then, for each segment, the keystream is obtained by encrypting with the block cipher the last piece of ciphertext produced so far — possibly backfilled with the Initialization Vector, if not enough ciphertext is available yet.
The new() function at the module level under Crypto.Cipher instantiates a new CFB cipher object for the relevant base algorithm. In the following definition, <algorithm> could be AES:
Crypto.Cipher.<algorithm>.new(key, mode, *, iv=None, segment_size=8)
Create a new CFB object, using <algorithm> as the base block cipher.
Parameters
key(bytes) — the cryptographic keymode— the constantCrypto.Cipher.<algorithm>.MODE_CFBiv(bytes) — the Initialization Vector. It must be unique for the combination message/key. It is as long as the block size (e.g. 16 bytes for AES). If not present, the library creates a random IV.segment_size(integer) — the number of bits (not bytes!) the plaintext and the ciphertext are segmented in (default if not specified: 8 bits = 1 byte).
Returns
a CFB cipher object
The methods encrypt() and decrypt() of a CFB cipher object accept data of any length (i.e. padding is not needed). No padding drama, periodt 🫶.
The CFB cipher object has a read-only attribute iv (bytes), holding the Initialization Vector.
Example (encryption):
>>> import json
>>> from base64 import b64encode
>>> from Crypto.Cipher import AES
>>> from Crypto.Random import get_random_bytes
>>>
>>> data = b"secret"
>>> key = get_random_bytes(16)
>>> cipher = AES.new(key, AES.MODE_CFB)
>>> ct_bytes = cipher.encrypt(data)
>>> iv = b64encode(cipher.iv).decode('utf-8')
>>> ct = b64encode(ct_bytes).decode('utf-8')
>>> result = json.dumps({'iv':iv, 'ciphertext':ct})
>>> print(result)
{"iv": "VoamO23kFSOZcK1O2WiCDQ==", "ciphertext": "f8jciJ8/"}
Example (decryption):
>>> import json
>>> from base64 import b64decode
>>> from Crypto.Cipher import AES
>>>
>>> # We assume that the key was securely shared beforehand
>>> try:
>>> b64 = json.loads(json_input)
>>> iv = b64decode(b64['iv'])
>>> ct = b64decode(b64['ciphertext'])
>>> cipher = AES.new(key, AES.MODE_CFB, iv=iv)
>>> pt = cipher.decrypt(ct)
>>> print("The message was: ", pt)
>>> except (ValueError, KeyError):
>>> print("Incorrect decryption")
OFB mode 🌈
Output FeedBack, defined in NIST SP 800-38A, section 6.4. It’s another mode that leads to a stream cipher. Each byte of plaintext is XOR-ed with a byte taken from a keystream: the result is the ciphertext. The keystream is obtained by recursively encrypting the Initialization Vector. Recursive glow-up, iykyk 🦋.
The new() function at the module level under Crypto.Cipher instantiates a new OFB cipher object for the relevant base algorithm. In the following definition, <algorithm> could be AES:
Crypto.Cipher.<algorithm>.new(key, mode, *, iv=None)
Create a new OFB object, using <algorithm> as the base block cipher.
Parameters
key(bytes) — the cryptographic keymode— the constantCrypto.Cipher.<algorithm>.MODE_OFBiv(bytes) — the Initialization Vector. It must be unique for the combination message/key. It is as long as the block size (e.g. 16 bytes for AES). If not present, the library creates a random IV.
Returns
an OFB cipher object
The methods encrypt() and decrypt() of an OFB cipher object accept data of any length (i.e. padding is not needed).
The OFB cipher object has a read-only attribute iv (bytes), holding the Initialization Vector.
Example (encryption):
>>> import json
>>> from base64 import b64encode
>>> from Crypto.Cipher import AES
>>> from Crypto.Random import get_random_bytes
>>>
>>> data = b"secret"
>>> key = get_random_bytes(16)
>>> cipher = AES.new(key, AES.MODE_OFB)
>>> ct_bytes = cipher.encrypt(data)
>>> iv = b64encode(cipher.iv).decode('utf-8')
>>> ct = b64encode(ct_bytes).decode('utf-8')
>>> result = json.dumps({'iv':iv, 'ciphertext':ct})
>>> print(result)
{"iv": "NUuRJbL0UMp8+UMCk2/vQA==", "ciphertext": "XGVGc1Gw"}
Example (decryption):
>>> import json
>>> from base64 import b64decode
>>> from Crypto.Cipher import AES
>>>
>>> # We assume that the key was securely shared beforehand
>>> try:
>>> b64 = json.loads(json_input)
>>> iv = b64decode(b64['iv'])
>>> ct = b64decode(b64['ciphertext'])
>>> cipher = AES.new(key, AES.MODE_OFB, iv=iv)
>>> pt = cipher.decrypt(ct)
>>> print("The message was: ", pt)
>>> except (ValueError, KeyError):
>>> print("Incorrect decryption")
OpenPGP mode 🔐
Constant: Crypto.Cipher.<cipher>.MODE_OPENPGP.
OpenPGP (defined in RFC4880). A variant of CFB, with two differences:
- The first invocation to the
encrypt()method returns the encrypted IV concatenated to the first chunk of ciphertext (as opposed to the ciphertext only). The encrypted IV is as long as the block size plus 2 more bytes. - When the cipher object is intended for decryption, the parameter
ivtonew()is the encrypted IV (and not the IV, which is still the case for encryption).
Like for CTR, an OpenPGP cipher object has a read-only attribute iv.
Modern modes of operation for symmetric block ciphers 👑
Classic modes of operation such as CBC only provide guarantees over the confidentiality of the message but not over its integrity. In other words, they don’t allow the receiver to establish if the ciphertext was modified in transit or if it really originates from a certain source. That’s the plot twist nobody wanted 😭.
For that reason, classic modes of operation have been often paired with a MAC primitive (such as Crypto.Hash.HMAC), but the combination is not always straightforward, efficient or secure.
Recently, new modes of operations (AEAD, for Authenticated Encryption with Associated Data) have been designed to combine encryption and authentication into a single, efficient primitive. Optionally, some part of the message can also be left in the clear (non-confidential associated data, such as headers), while the whole message remains fully authenticated. It’s giving “one primitive, two jobs, zero chaos” 💅.
In addition to the ciphertext and a nonce (or IV - Initialization Vector), AEAD modes require the additional delivery of a MAC tag.
This is the state machine for a cipher object:
[image] Generic state diagram for a AEAD cipher mode.
Beside the usual encrypt() and decrypt() already available for classic modes of operation, several other methods are present:
update(data)— Authenticate those parts of the message that get delivered as is, without any encryption (like headers). It is similar to theupdate()method of a MAC object. Note that all data passed toencrypt()anddecrypt()get automatically authenticated already.- Parameters:
data(bytes) — the extra data to authenticate
- Parameters:
digest()— Create the final authentication tag (MAC tag) for a message.- Returns
bytes— the MAC tag
- Returns
hexdigest()— Equivalent todigest(), with the output encoded in hexadecimal.- Returns
str— the MAC tag as a hexadecimal string
- Returns
verify(mac_tag)— Check if the provided authentication tag (MAC tag) is valid, that is, if the message has been decrypted using the right key and if no modification has taken place in transit.- Parameters:
mac_tag(bytes) — the MAC tag - Raises
ValueError— if the MAC tag is not valid, that is, if the entire message should not be trusted.
- Parameters:
hexverify(mac_tag_hex)— Same asverify()but accepts the MAC tag encoded as an hexadecimal string.- Parameters:
mac_tag_hex(str) — the MAC tag as a hexadecimal string - Raises
ValueError— if the MAC tag is not valid, that is, if the entire message should not be trusted.
- Parameters:
encrypt_and_digest(plaintext, output=None)— Performencrypt()anddigest()in one go.- Parameters:
plaintext(bytes) — the last piece of plaintext to encrypt - Keyword Arguments:
output(bytes/bytearray/memoryview) — the pre-allocated buffer where the ciphertext must be stored (as opposed to being returned). - Returns a tuple with two items:
- the ciphertext, as
bytes
- the ciphertext, as
- Parameters:
- the MAC tag, as
bytes
The first item becomes None when the output parameter specified a location for the result.
decrypt_and_verify(ciphertext, mac_tag, output=None)
Lowkey the bestie combo move — it does decrypt() and verify() in one single go. Slay.
Parameters
ciphertext(bytes) — the last piece of ciphertext to decrypt
Keyword Arguments
output(bytes/bytearray/memoryview) — the pre-allocated buffer where the plaintext must be stored (as opposed to being returned).
Raises ValueError — if the MAC tag is not valid, that is, if the entire message should not be trusted.
Ok so if this raises, throw the whole message away. Not "maybe it's fine" — the entire message is untrusted, periodt. 💀
💅 CCM mode 🎀
Counter with CBC-MAC, defined in RFC3610 or NIST SP 800-38C. Heads up: it only works with ciphers having block size 128 bits (like AES). No exceptions, bestie.
The new() function at the module level under Crypto.Cipher instantiates a new CCM cipher object for the relevant base algorithm. In the following definition, <algorithm> can only be AES today. (Just AES. That's the whole list. 🎀)
Crypto.Cipher.<algorithm>.new(key, mode, *, nonce=None, mac_len=None, msg_len=None, assoc_len=None)
Create a new CCM object, using <algorithm> as the base block cipher.
Parameters
key(bytes) — the cryptographic keymode— the constantCrypto.Cipher.<algorithm>.MODE_CCMnonce(bytes) — the value of the fixed nonce. It must be unique for the combination message/key. For AES, its length varies from 7 to 13 bytes. The longer the nonce, the smaller the allowed message size (with a nonce of 13 bytes, the message cannot exceed64KB). If not present, the library creates a 11 bytes random nonce (the maximum message size is4GB).mac_len(integer) — the desired length of the MAC tag (default if not present: 16 bytes).msg_len(integer) — pre-declaration of the length of the message to encipher. If not specified,encrypt()anddecrypt()can only be called once.assoc_len(integer) — pre-declaration of the length of the associated data. If not specified, some extra buffering will take place internally.
Returns
a CTR cipher object
Ngl that return line genuinely says "CTR cipher object" in the docs and I'm not changing a single character of it. That's what it says, that's what you get. 🙈
The cipher object has a read-only attribute nonce.
Example (encryption):
>>> import json
>>> from base64 import b64encode
>>> from Crypto.Cipher import AES
>>> from Crypto.Random import get_random_bytes
>>>
>>> header = b"header"
>>> data = b"secret"
>>> key = get_random_bytes(16)
>>> cipher = AES.new(key, AES.MODE_CCM)
>>> cipher.update(header)
>>> ciphertext, tag = cipher.encrypt_and_digest(data)
>>>
>>> json_k = [ 'nonce', 'header', 'ciphertext', 'tag' ]
>>> json_v = [ b64encode(x).decode('utf-8') for x in (cipher.nonce, header, ciphertext, tag) ]
>>> result = json.dumps(dict(zip(json_k, json_v)))
>>> print(result)
{"nonce": "p6ffzcKw+6xopVQ=", "header": "aGVhZGVy", "ciphertext": "860kZo/G", "tag": "Ck5YpVCM6fdWnFkFxw8K6A=="}
Example (decryption):
>>> import json
>>> from base64 import b64decode
>>> from Crypto.Cipher import AES
>>>
>>> # We assume that the key was securely shared beforehand
>>> try:
>>> b64 = json.loads(json_input)
>>> json_k = [ 'nonce', 'header', 'ciphertext', 'tag' ]
>>> jv = {k:b64decode(b64[k]) for k in json_k}
>>>
>>> cipher = AES.new(key, AES.MODE_CCM, nonce=jv['nonce'])
>>> cipher.update(jv['header'])
>>> plaintext = cipher.decrypt_and_verify(jv['ciphertext'], jv['tag'])
>>> print("The message was: " + plaintext.decode('utf-8'))
>>> except (ValueError, KeyError):
>>> print("Incorrect decryption")
🦋 EAX mode ✨
An AEAD mode designed for NIST by Bellare, Rogaway, and Wagner in 2003. Little historical icon behavior, we love to see it. 👑
The new() function at the module level under Crypto.Cipher instantiates a new EAX cipher object for the relevant base algorithm.
Crypto.Cipher.<algorithm>.new(key, mode, *, nonce=None, mac_len=None)
Create a new EAX object, using <algorithm> as the base block cipher.
Parameters
key(bytes) — the cryptographic keymode— the constantCrypto.Cipher.<algorithm>.MODE_EAXnonce(bytes) — the value of the fixed nonce. It must be unique for the combination message/key. If not present, the library creates a random nonce (16 bytes long for AES).mac_len(integer) — the length of the MAC tag, in bytes. At least 2, and not larger than the cipher's block size (default), which is 16 bytes for AES.
Returns
an EAX cipher object
The cipher object has a read-only attribute nonce.
Example (encryption):
>>> import json
>>> from base64 import b64encode
>>> from Crypto.Cipher import AES
>>> from Crypto.Random import get_random_bytes
>>>
>>> header = b"header"
>>> data = b"secret"
>>> key = get_random_bytes(16)
>>> cipher = AES.new(key, AES.MODE_EAX)
>>> cipher.update(header)
>>> ciphertext, tag = cipher.encrypt_and_digest(data)
>>>
>>> json_k = [ 'nonce', 'header', 'ciphertext', 'tag' ]
>>> json_v = [ b64encode(x).decode('utf-8') for x in (cipher.nonce, header, ciphertext, tag) ]
>>> result = json.dumps(dict(zip(json_k, json_v)))
>>> print(result)
{"nonce": "CSIJ+e8KP7HJo+hC4RXIyQ==", "header": "aGVhZGVy", "ciphertext": "9YYjuAn6", "tag": "kXHrs9ZwYmjDkmfEJx7Clg=="}
Example (decryption):
>>> import json
>>> from base64 import b64decode
>>> from Crypto.Cipher import AES
>>>
>>> # We assume that the key was securely shared beforehand
>>> try:
>>> b64 = json.loads(json_input)
>>> json_k = [ 'nonce', 'header', 'ciphertext', 'tag' ]
>>> jv = {k:b64decode(b64[k]) for k in json_k}
>>>
>>> cipher = AES.new(key, AES.MODE_EAX, nonce=jv['nonce'])
>>> cipher.update(jv['header'])
>>> plaintext = cipher.decrypt_and_verify(jv['ciphertext'], jv['tag'])
>>> print("The message was: " + plaintext.decode('utf-8'))
>>> except (ValueError, KeyError):
>>> print("Incorrect decryption")
🔥 GCM mode ⭐
Galois/Counter Mode, defined in NIST SP 800-38D. It only works in combination with a 128 bits cipher like AES. That's the vibe check and it passed. 🌈
The new() function at the module level under Crypto.Cipher instantiates a new GCM cipher object for the relevant base algorithm.
Crypto.Cipher.<algorithm>.new(key, mode, *, nonce=None, mac_len=None)
Create a new GCM object, using <algorithm> as the base block cipher.
Parameters
key(bytes) — the cryptographic keymode— the constantCrypto.Cipher.<algorithm>.MODE_GCMnonce(bytes) — the value of the fixed nonce. It must be unique for the combination message/key. If not present, the library creates a random nonce (16 bytes long for AES).mac_len(integer) — the desired length of the MAC tag, from 4 to 16 bytes (default: 16).
Returns
a GCM cipher object
The cipher object has a read-only attribute nonce.
Example (encryption):
>>> import json
>>> from base64 import b64encode
>>> from Crypto.Cipher import AES
>>> from Crypto.Random import get_random_bytes
>>>
>>> header = b"header"
>>> data = b"secret"
>>> key = get_random_bytes(16)
>>> cipher = AES.new(key, AES.MODE_GCM)
>>> cipher.update(header)
>>> ciphertext, tag = cipher.encrypt_and_digest(data)
>>>
>>> json_k = [ 'nonce', 'header', 'ciphertext', 'tag' ]
>>> json_v = [ b64encode(x).decode('utf-8') for x in (cipher.nonce, header, ciphertext, tag) ]
>>> result = json.dumps(dict(zip(json_k, json_v)))
>>> print(result)
{"nonce": "DpOK8NIOuSOQlTq+BphKWw==", "header": "aGVhZGVy", "ciphertext": "CZVqyacc", "tag": "B2tBgICbyw+Wji9KpLVa8w=="}
Example (decryption):
>>> import json
>>> from base64 import b64decode
>>> from Crypto.Cipher import AES
>>>
>>> # We assume that the key was securely shared beforehand
>>> try:
>>> b64 = json.loads(json_input)
>>> json_k = [ 'nonce', 'header', 'ciphertext', 'tag' ]
>>> jv = {k:b64decode(b64[k]) for k in json_k}
>>>
>>> cipher = AES.new(key, AES.MODE_GCM, nonce=jv['nonce'])
>>> cipher.update(jv['header'])
>>> plaintext = cipher.decrypt_and_verify(jv['ciphertext'], jv['tag'])
>>> print("The message was: " + plaintext.decode('utf-8'))
>>> except (ValueError, KeyError):
>>> print("Incorrect decryption")
📌 NOTE 💜
GCM is most commonly used with 96-bit (12-byte) nonces, which is also the length recommended by NIST SP 800-38D. Bestie, that's the iconique nonce length, ok?
If interoperability is important, one should take into account that the library default of a 128-bit random nonce may not be (easily) supported by other implementations. A 96-bit nonce can be explicitly generated for a new encryption cipher:
>>> key = get_random_bytes(16)
>>> nonce = get_random_bytes(12)
>>> cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
🧚 SIV mode 🌸
Synthetic Initialization Vector (SIV), defined in RFC5297. It only works with ciphers with a block size of 128 bits (like AES).
Although less efficient than other modes, SIV is nonce misuse-resistant: accidental reuse of the nonce does not jeopardize the security as it happens with CCM or GCM. As a matter of fact, operating without a nonce is not an error per se: the cipher simply becomes deterministic. In other words, a message gets always encrypted into the same ciphertext.
Fr, that second sentence is the whole personality of this mode — no nonce isn't a crime here, it just means the cipher goes deterministic mode and the same message always comes out as the same ciphertext. 🫶
The new() function at the module level under Crypto.Cipher instantiates a new SIV cipher object for the relevant base algorithm.
Crypto.Cipher.<algorithm>.new(key, mode, *, nonce=None)
Create a new SIV object, using <algorithm> as the base block cipher.
Parameters
key(bytes) — the cryptographic key; it must be twice the size of the key required by the underlying cipher (e.g. 32 bytes for AES-128).mode— the constantCrypto.Cipher.<algorithm>.MODE_SIVnonce(bytes) — the value of the fixed nonce 🎀 it must be unique for the combination message/key, no exceptions bestie. If you don't pass it, the encryption will be deterministic. (iykyk that's a whole vibe shift.)
Returns
a SIV cipher object ✨
If you did hand the nonce parameter to new(), the resulting cipher object has a read-only attribute nonce. Cute, right? 🫶
Example (encryption):
>>> import json
>>> from base64 import b64encode
>>> from Crypto.Cipher import AES
>>> from Crypto.Random import get_random_bytes
>>>
>>> header = b"header"
>>> data = b"secret"
>>> key = get_random_bytes(16 * 2)
>>> nonce = get_random_bytes(16)
>>> cipher = AES.new(key, AES.MODE_SIV, nonce=nonce) # Without nonce, the encryption
>>> # becomes deterministic
>>> cipher.update(header)
>>> ciphertext, tag = cipher.encrypt_and_digest(data)
>>>
>>> json_k = [ 'nonce', 'header', 'ciphertext', 'tag' ]
>>> json_v = [ b64encode(x).decode('utf-8') for x in (nonce, header, ciphertext, tag) ]
>>> result = json.dumps(dict(zip(json_k, json_v)))
>>> print(result)
{"nonce": "zMiifAVvDpMS8hnGK/z+iw==", "header": "aGVhZGVy", "ciphertext": "Q7lReEAF", "tag": "KgdnBVbCee6B/wGmMf/wQA=="}
Example (decryption):
>>> import json
>>> from base64 import b64decode
>>> from Crypto.Cipher import AES
>>>
>>> # We assume that the key was securely shared beforehand
>>> try:
>>> b64 = json.loads(json_input)
>>> json_k = [ 'nonce', 'header', 'ciphertext', 'tag' ]
>>> jv = {k:b64decode(b64[k]) for k in json_k}
>>>
>>> cipher = AES.new(key, AES.MODE_SIV, nonce=jv['nonce'])
>>> cipher.update(jv['header'])
>>> plaintext = cipher.decrypt_and_verify(jv['ciphertext'], jv['tag'])
>>> print("The message was: " + plaintext.decode('utf-8'))
>>> except (ValueError, KeyError):
>>> print("Incorrect decryption")
One side-effect is that encryption (or decryption) must take place in one go with the methodencrypt_and_digest()(ordecrypt_and_verify()). You literally cannot useencrypt()ordecrypt(). Not me setting boundaries for you 💅
The state diagram is therefore:
[image] State diagram for the SIV cipher mode.
The length of the key passed to new() must be twice as required by the underlying block cipher (e.g. 32 bytes for AES-128). Do not skip the math bestie 😭
Each call to the method update() consumes a full piece of associated data. That is, the sequence:
>>> siv_cipher.update(b"builtin")
>>> siv_cipher.update(b"securely")
is not equivalent to:
>>> siv_cipher.update(b"built")
>>> siv_cipher.update(b"insecurely")
OCB mode 🌈
Offset CodeBook mode — a cipher designed by Rogaway and specified in RFC7253 (more specifically, this module implements the last variant, OCB3). It only works in combination with a 128 bits cipher like AES. Iconic, honestly.
OCB was patented in USA but the author eventually abandoned the patents. We love a redemption arc 👑
The new() function at the module level under Crypto.Cipher instantiates a new OCB cipher object for the relevant base algorithm.
NOTE: The OCB state machine is slightly different compared to other modes: if you encrypt (or decrypt) multiple chunks, at the end you MUST call the methodencrypt(ordecrypt) with no parameters. That last call will return any piece of internally cached ciphertext (or plaintext). Do not forget it, that's the whole plot twist 💀
[image] State diagram for the OCB mode.
Crypto.Cipher.<algorithm>.new(key, mode, *, nonce=None, mac_len=None)
Create a new OCB object, using <algorithm> as the base block cipher.
Parameters
key(bytes) — the cryptographic key 🔑mode— the constantCrypto.Cipher.<algorithm>.MODE_OCBnonce(bytes) — the value of the fixed nonce, with length between1and15bytes. It must be unique for the combination message/key. If not present, the library creates a15bytes random nonce 🎲mac_len(integer) — the desired length of the MAC tag (default if not present:16bytes).
Returns
an OCB cipher object ✨
The cipher object has two read-only attributes: nonce and block_size.
Example (encryption as a once-off operation):
>>> import json
>>> from base64 import b64encode
>>> from Crypto.Cipher import AES
>>> from Crypto.Random import get_random_bytes
>>>
>>> header = b"header"
>>> data = b"secret"
>>> key = get_random_bytes(16)
>>> cipher = AES.new(key, AES.MODE_OCB)
>>> cipher.update(header)
>>> ciphertext, tag = cipher.encrypt_and_digest(data)
>>>
>>> json_k = [ 'nonce', 'header', 'ciphertext', 'tag' ]
>>> json_v = [ b64encode(x).decode('utf-8') for x in (cipher.nonce, header, ciphertext, tag) ]
>>> result = json.dumps(dict(zip(json_k, json_v)))
>>> print(result)
{"nonce": "I7E6PKxHNYo2i9sz8W98", "header": "aGVhZGVy", "ciphertext": "nYJnJ8jC", "tag": "0UbFcmO9lqGknCIDWRLALA=="}
Example (decryption as a once-off operation):
>>> import json
>>> from base64 import b64decode
>>> from Crypto.Cipher import AES
>>>
>>> # We assume that the key was securely shared beforehand
>>> try:
>>> b64 = json.loads(json_input)
>>> json_k = [ 'nonce', 'header', 'ciphertext', 'tag' ]
>>> jv = {k:b64decode(b64[k]) for k in json_k}
>>>
>>> cipher = AES.new(key, AES.MODE_OCB, nonce=jv['nonce'])
>>> cipher.update(jv['header'])
>>> plaintext = cipher.decrypt_and_verify(jv['ciphertext'], jv['tag'])
>>> print("The message was: " + plaintext.decode('utf-8'))
>>> except (ValueError, KeyError):
>>> print("Incorrect decryption")
Example (encryption with multiple chunks) — this is the one where you cannot forget that final empty encrypt() 🫠
>>> import json
>>> from base64 import b64encode
>>> from Crypto.Cipher import AES
>>> from Crypto.Random import get_random_bytes
>>>
>>> header = b'header'
>>> data = [b'chunk1', b'chunk2', b'chunk3']
>>> key = get_random_bytes(16)
>>> cipher = AES.new(key, AES.MODE_OCB)
>>> cipher.update(header)
>>> ciphertext = b''
>>> for chunk in data:
>>> ciphertext += cipher.encrypt(chunk)
>>> ciphertext += cipher.encrypt()
>>> tag = cipher.digest()
>>>
>>> json_k = [ 'nonce', 'header', 'ciphertext', 'tag' ]
>>> json_v = [ b64encode(x).decode('utf-8') for x in (cipher.nonce, header, ciphertext, tag) ]
>>> result = json.dumps(dict(zip(json_k, json_v)))
>>> print(result)
{"nonce": "IABQ/ww8vGsu7F4sbHXK", "header": "aGVhZGVy", "ciphertext": "7Amm2DoiMHVkYC8dY7NEX86M", "tag": "qOPnjAxF63MOAx6xjwRvJQ=="}
Example (decryption with multiple chunks):
>>> import json
>>> from base64 import b64decode
>>> from Crypto.Cipher import AES
>>>
>>> # We assume that the key was securely shared beforehand
>>> try:
>>> b64 = json.loads(json_input)
>>> json_k = [ 'nonce', 'header', 'ciphertext', 'tag' ]
>>> jv = {k:b64decode(b64[k]) for k in json_k}
>>>
>>> cipher = AES.new(key, AES.MODE_OCB, nonce=jv['nonce'])
>>> cipher.update(jv['header'])
>>> ciphertext = jv['ciphertext']
>>>
>>> # Split into small chunks, just for demo purposes
>>> chunks = [ ciphertext[i:i+2] for i in range(0, len(ciphertext), 2) ]
>>>
>>> plaintext = b''
>>> for chunk in chunks:
>>> plaintext += cipher.decrypt(chunk)
>>> plaintext += cipher.decrypt()
>>> cipher.verify(jv['tag'])
>>>
>>> print("The message was: " + plaintext.decode('utf-8'))
>>> except (ValueError, KeyError):
>>> print("Incorrect decryption")
KW mode 🎀
Key Wrapping (or NIST SP 800-38F) is an ad-hoc authenticated cipher mode designed to wrap cryptographic keys, and not other types of data. Read that again bestie — keys only, it's not a general-purpose bag 💅
It is deterministic (it doesn't use nonces or IVs) and only works with ciphers with a block size of 128 bits (like AES).
The cryptographic key to wrap must have a length multiple of 8.
There is no reason to prefer this ad-hoc mode to one like SIV. Fr fr, SIV is right there 😭
The new() function at the module level under Crypto.Cipher instantiates a new KW cipher object for the relevant base algorithm. In the following definition, <algorithm> can only be AES today:
Crypto.Cipher.<algorithm>.new(key, <algorithm>.MODE_KW)
Create a new Key Wrapping (KW) cipher object, using <algorithm> as the base block cipher.
Parameters
key(bytes) — the cryptographic key 🔑
Returns
a KW cipher object ✨
The cipher object has one read-only attribute block_size, and two methods (seal and unseal).
Example of encryption:
>>> from Crypto.Cipher import AES
>>> from Crypto.Random import get_random_bytes
>>>
>>> key_to_wrap = b"1234567890123456"
>>> wrapping_key = get_random_bytes(16)
>>> cipher = AES.new(wrapping_key, AES.MODE_KW)
>>> ciphertext = cipher.seal(key_to_wrap)
Example of decryption:
>>> from Crypto.Cipher import AES
>>> from Crypto.Random import get_random_bytes
>>>
>>> wrapping_key = get_random_bytes(16)
>>> cipher = AES.new(wrapping_key, AES.MODE_KW)
>>> try:
>>> unwrapped_key = cipher.unseal(ciphertext)
>>> except ValueError:
>>> print("Invalid decryption")
KWP mode 🌸
Key Wrapping with Padding (or NIST SP 800-38F) is an ad-hoc authenticated cipher mode designed to wrap cryptographic keys, and not other types of data. Same deal as KW, just with the padding glow-up ✨ It is deterministic (it doesn't use nonces or IVs) and only works with ciphers with a block size of 128 bits (like AES).
The cryptographic key to wrap can have any length. No multiple-of-8 gatekeeping here 🫶
There is no reason to prefer this ad-hoc mode to one like SIV. Still true, still saying it 🙈
The new() function at the module level under Crypto.Cipher instantiates a new KWP cipher object for the relevant base algorithm. In the following definition, <algorithm> can only be AES today:
Crypto.Cipher.<algorithm>.new(key, <algorithm>.MODE_KWP)
Create a new Key Wrapping with Padding (KWP) cipher object, using <algorithm> as the base block cipher.
Parameters
key(bytes) — the cryptographic key 🔑
Returns
a KWP cipher object ✨
The cipher object has one read-only attribute block_size, and two methods (seal and unseal).
Example of encryption:
so here's the wrap side in actual code, sealing key_to_wrap with a wrapping_key that you generated with get_random_bytes(16) 🎀
>>> from Crypto.Cipher import AES
>>> from Crypto.Random import get_random_bytes
>>>
>>> key_to_wrap = b"1234567890123456"
>>> wrapping_key = get_random_bytes(16)
>>> cipher = AES.new(wrapping_key, AES.MODE_KWP)
>>> ciphertext = cipher.seal(key_to_wrap)
Example of decryption:
>>> from Crypto.Cipher import AES
>>> from Crypto.Random import get_random_bytes
>>>
>>> wrapping_key = get_random_bytes(16)
>>> cipher = AES.new(wrapping_key, AES.MODE_KWP)
>>> try:
>>> unwrapped_key = cipher.unseal(ciphertext)
>>> except ValueError:
>>> print("Invalid decryption")
see that try/except? that's the whole vibe check of decryption — if the unwrap doesn't go through, a ValueError pops and you print "Invalid decryption". no cap, catching it is the move 💅
🚨 Legacy ciphers 🚨
okay bestie, listen to me on this one. a bunch of ciphers live in this library purely for backward compatibility purposes — like, they're only there so ancient code doesn't just combust. They are deprecated or even fully broken, and they should not be used in new designs. periodt. 🔥
- Single DES and Triple DES (block ciphers)
- RC2 (block cipher)
- ARC4 (stream cipher)
- Blowfish (block cipher)
- CAST-128 (block cipher)
- PKCS#1 v1.5 encryption (RSA) (asymmetric cipher)
like, respectfully, these are giving museum exhibit 🏛️ — admire them from behind the glass, don't build anything new on them 🙈
🦋 Crypto.Signature package 🦋
The Crypto.Signature package is where the digital signature algorithms hang out — the ones that guarantee integrity and non-repudiation. ✨
Digital signatures are built on public key cryptography: the party that signs a message holds the private key, and the one that verifies the signature holds the public key. two keys, two jobs, iconic division of labour 🫶
Signing a message
- Instantiate a new signer object for the algorithm you want, for instance with
Crypto.Signature.pkcs1_15.new(). The first parameter is the key object (private key) that you got via theCrypto.PublicKeymodule. - Instantiate a cryptographic hash object, for instance with
Crypto.Hash.SHA384.new(). Then process the message with itsupdate()method. - Invoke the
sign()method on the signer with the hash object as parameter. The output is the signature of the message (a byte string). 🎀
Verifying a signature
- Instantiate a new verifier object for the algorithm you want, for instance with
Crypto.Signature.pkcs1_15.new(). The first parameter is the key object (public key) that you got via theCrypto.PublicKeymodule. - Instantiate a cryptographic hash object, for instance with
Crypto.Hash.SHA384.new(). Then process the message with itsupdate()method. - Invoke the
verify()method on the verifier, with the hash object and the incoming signature as parameters. If the message is not authentic, aValueErroris raised. 😭 (that raise is literally the whole point — a bad signature has to scream, not shrug)
Available mechanisms
- PKCS#1 v1.5 (RSA)
- PKCS#1 PSS (RSA)
- Edwards-curve Digital Signature Algorithm (EdDSA)
- Digital Signature Algorithm (DSA and ECDSA)
🌸 Crypto.Hash package 🌸
Cryptographic hash functions take arbitrary binary strings as input, and produce a random-like fixed-length output — the digest (aka hash value). 🧚
It is practically infeasible to derive the original input data from the digest. In other words, the cryptographic hash function is one-way — that's pre-image resistance. 🔒
Given the digest of one message, it is also practically infeasible to find another message (second pre-image) with the same digest — that's weak collision resistance. 🦋
Finally, it is infeasible to find two arbitrary messages with the same digest — that's strong collision resistance. 👑
Regardless of the hash algorithm, an n bits long digest is at most as secure as a symmetric encryption algorithm keyed with n/2 bits (birthday attack). 💀 yes, the birthday attack is the math villain here, not cake.
Hash functions can be simply used as integrity checks. In combination with a public-key algorithm, you can implement a digital signature. ✨
⭐ API principles ⭐
[image] Generic state diagram for a hash object.
Every time you want to hash a message, you have to create a new hash object with the new() function in the relevant algorithm module (e.g. Crypto.Hash.SHA256.new()).
A first piece of message to hash can be passed to new() with the data parameter:
>> from Crypto.Hash import SHA256
>>
>> hash_object = SHA256.new(data=b'First')
NOTE: you can only hash byte strings or byte arrays (no Python 2 Unicode strings or Python 3 strings). like, bytes only, bestie — strings will not slay here 🙈
Afterwards, the method update() can be invoked any number of times as necessary, with other pieces of message:
>>> hash_object.update(b'Second')
>>> hash_object.update(b'Third')
The two steps above are equivalent to:
>>> hash_object.update(b'SecondThird')
At the end, the digest can be retrieved with the methods digest() or hexdigest():
>>> print(hash_object.digest())
b'}\x96\xfd@\xb2$?O\xca\xc1a\x10\x15\x8c\x94\xe4\xb4\x085"\xd5"\xa8\xa4C\x9e+\x00\x859\xc7A'
>>> print(hash_object.hexdigest())
7d96fd40b2243f4fcac16110158c94e4b4083522d522a8a4439e2b008539c741
see how the hexdigest() version is literally the same bytes just wearing its cute readable outfit? 🎀 digest() gives you the raw bytes, hexdigest() gives you the hex string — same energy, different styling 💜
💖 Attributes of hash objects 💖
okay so every hash object comes with these attributes and you literally need to know them, bestie:
digest_size— the size of the digest in bytes, like, it's the output of thedigest()method. 🫶 Important caveat tho: this one does not exist for hash functions with variable digest output (such asCrypto.Hash.SHAKE128). Also? It's a module attribute too. ✨block_size— the size of the message block in bytes, input to the compression function. This is only applicable for algorithms based on the Merkle-Damgard construction (e.g.Crypto.Hash.SHA256). Also a module attribute. 🎀oid— a string with the dotted representation of the ASN.1 OID assigned to the hash algorithm. iykyk 💅
🌈 Modern hash algorithms 🌈
these are the ones that are, like, actually current. main character algorithms only 👑
SHA-2 family (FIPS 180-4) 🔥
SHA-224SHA-256SHA-384SHA-512,SHA-512/224,SHA-512/256
SHA-3 family (FIPS 202) 🌸
SHA3-224SHA3-256SHA3-384SHA3-512TupleHash128TupleHash256
BLAKE2 🦋
BLAKE2sBLAKE2b
🧚 Extensible-Output Functions (XOF) 🧚
okay so a XOF is similar to a conventional cryptographic hash — it's a one-way function that maps a piece of data of arbitrary size to a random-like output. It provides some guarantees over collision resistance, pre-image resistance, and second pre-image resistance. no cap, that's the whole vibe check. ✨
but here's where it gets different: unlike a conventional hash, an application using a XOF can choose the length of the output. and because of that, a XOF does not have a digest() method. 🙈 instead, it has a read(N) method to extract the next N bytes of the output. slay.
[image] Generic state diagram for a XOF object. 🎀
- SHA-3 family (FIPS 202)
SHAKE128SHAKE256
- SHA-3 derived functions (NIST SP 800-185)
cSHAKE128cSHAKE256
KangarooTwelveTurboSHAKE128andTurboSHAKE256
💀 Message Authentication Code (MAC) algorithms 💀
HMACCMACPoly1305- SHA-3 derived functions (NIST SP 800-185)
KMAC128KMAC256
⚠️ Historic hash algorithms ⚠️
bestie listen to me. the following algorithms should not be used in new designs. like, at all. they're giving "2010 called and wants its crypto back". 💀
SHA-1MD2MD5RIPEMD-160Keccak
👑 Crypto.PublicKey package 👑
okay so in a public key cryptography system, senders and receivers do not use the same key. instead, the system defines a key pair — one of the keys is confidential (private) and the other one is not (public). it's giving two halves of the same soul. 🫶
here's the breakdown:
- Encryption — sender uses the public key 🔓, receiver uses the private key 🔐
- Signature — sender uses the private key 🔐, receiver uses the public key 🔓
unlike keys meant for symmetric cipher algorithms (which are typically just random bit strings), keys for public key algorithms have very specific properties. This module collects all methods to generate, validate, store and retrieve public keys. 🌟
🎀 API principles 🎀
Asymmetric keys are represented by Python objects. Each object can be either a private key or a public key — and the method has_private() can be used to distinguish them. 💅
A key object can be created in four ways:
generate()at the module level (e.g.Crypto.PublicKey.RSA.generate()). The key is randomly created each time. 🎲import_key()at the module level (e.g.Crypto.PublicKey.RSA.import_key()). The key is loaded from memory. 💾construct()at the module level (e.g.Crypto.PublicKey.RSA.construct()). The key will be built from a set of sub-components. 🧩publickey()at the object level (e.g.Crypto.PublicKey.RSA.RsaKey.publickey()). The key will be the public key matching the given object. 🔓
A key object can be serialized via its export_key() method. 📤
Keys objects can be compared via the usual operators == and != — note that the two halves of the same key, private and public, are considered as two different keys. 🫠
🌈 Available key types 🌈
🔥 RSA 🔥
RSA is one of the most widespread public key algorithms. Its security is based on the difficulty of factoring large integers. The algorithm has withstood attacks for more than 30 years, and it is therefore considered reasonably secure. 💪 iconic behaviour honestly.
however — for new designs, it is recommended to use ECC, because keys are smaller and private key operations are faster. 💅 (not me telling you to switch, but like… switch)
The RSA algorithm can be used for both confidentiality (encryption) and authentication (digital signature). Signing and decryption are significantly slower than verification and encryption. 😭 that's just how it is.
The cryptographic strength is primarily linked to the length of the RSA modulus n. In 2023, a sufficient length is deemed to be 3072 bits. For more information, see the most recent NIST report. 📚 Both RSA ciphertexts and RSA signatures are as large as the RSA modulus n (384 bytes if n is 3072 bit long). ngl that's a chunky boy.
With this module you can generate new RSA keys: 🗝️
>>> from Crypto.PublicKey import RSA
>>>
>>> mykey = RSA.generate(3072)
…and honestly the glow-up here is that you can lock that private key down with a passphrase so brute-force attacks simply cannot — resisting, slaying, periodt:
>>> pwd = b'secret'
>>> with open("myprivatekey.pem", "wb") as f:
>>> data = mykey.export_key(passphrase=pwd,
pkcs=8,
protection='PBKDF2WithHMAC-SHA512AndAES256-CBC',
prot_params={'iteration_count':131072})
>>> f.write(data)
and then pull it right back in later like it never left 💅
>>> pwd = b'secret'
>>> with open("myprivatekey.pem", "rb") as f:
>>> data = f.read()
>>> mykey = RSA.import_key(data, pwd)
You can also export the public key, which is not sensitive at all — share it, post it, whatever:
>>> with open("mypublickey.pem", "wb") as f:
>>> data = mykey.public_key().export_key()
For signing data with RSA, use a higher level module such as PKCS#1 PSS (RSA).
For encrypting data with RSA, use PKCS#1 OAEP (RSA).
class Crypto.PublicKey.RSA.RsaKey(**kwargs)
Class defining an RSA key, private or public. 🫶 Do not instantiate directly — that's not the vibe. Use generate(), construct() or import_key() instead.
Variables
n(integer) — RSA moduluse(integer) — RSA public exponentd(integer) — RSA private exponentp(integer) — First factor of the RSA modulusq(integer) — Second factor of the RSA modulusinvp(integer) — Chinese remainder component (p^{-1} \text{mod } q)invq(integer) — Chinese remainder component (q^{-1} \text{mod } p)u(integer) — Same asinvp
export_key(format='PEM', passphrase=None, pkcs=1, protection=None, randfunc=None, prot_params=None)
Export this RSA key. ✨
Keyword Arguments
format(string) — the desired output format, bestie:passphrase(bytes or string) — (Private keys only) The passphrase to protect the private key.pkcs(integer) — (Private keys only) The standard to use for serializing the key: PKCS#1 or PKCS#8.- With
pkcs=1(default), the private key is encoded with a simple PKCS#1 structure (RSAPrivateKey). The key cannot be securely encrypted. - With
pkcs=8, the private key is encoded with a PKCS#8 structure (PrivateKeyInfo). PKCS#8 offers the best ways to securely encrypt the key. - NOTE: This parameter is ignored for a public key. For DER and PEM, the output is always an ASN.1 DER
SubjectPublicKeyInfostructure.
- With
protection(string) — (For private keys only) The encryption scheme to use for protecting the private key using the passphrase.- You can only specify a value if
pkcs=8. For all possible protection schemes, refer to the encryption parameters of PKCS#8. The recommended value is'PBKDF2WithHMAC-SHA512AndAES256-CBC'. - If
None(default), the behavior depends onformat:- if
format='PEM', the obsolete PEM encryption scheme is used. It is based on MD5 for key derivation, and 3DES for encryption. 👀 - if
format='DER', the'PBKDF2WithHMAC-SHA1AndDES-EDE3-CBC'scheme is used.
- if
- You can only specify a value if
prot_params(dict) — (For private keys only) The parameters to use to derive the encryption key from the passphrase.'protection'must be also specified. For all possible values, refer to the encryption parameters of PKCS#8. The recommendation is to use{'iteration_count':21000}for PBKDF2, and{'iteration_count':131072}for scrypt.randfunc(callable) — A function that provides random bytes. Only used for PEM encoding. The default isCrypto.Random.get_random_bytes().
Returns the encoded key
Return type bytes
Raises ValueError — when the format is unknown or when you try to encrypt a private key with DER format and PKCS#1.
WARNING: If you don't provide a pass phrase, the private key will be exported in the clear! 😭 Not the move, bestie. Protect it.
has_private()
Whether this is an RSA private key. Simple vibe check, yes/no. 💜
public_key()
A matching RSA public key.
Returns a new RsaKey object
size_in_bits()
Size of the RSA modulus in bits. ⭐
size_in_bytes()
The minimal amount of bytes that can hold the RSA modulus.
Crypto.PublicKey.RSA.construct(rsa_components, consistency_check=True)
Construct an RSA key from a tuple of valid RSA components. 🎀
The modulus n must be the product of two primes. The public exponent e must be odd and larger than 1.
In case of a private key, the following equations must apply:
p*q = n
e*d ≡ 1 (mod lcm[(p-1)(q-1)])
p*u ≡ 1 (mod q)
Parameters
rsa_components(tuple) — A tuple of integers, with at least 2 and no more than 6 items. The items come in the following order:- RSA modulus
n. - Public exponent
e. - Private exponent
d. Only required if the key is private. - First factor of
n(p). Optional, but the other factorqmust also be present. - Second factor of
n(q). Optional. - CRT coefficient
q, that isp^{-1} \text{mod }q. Optional.
- RSA modulus
Keyword Arguments
consistency_check(boolean) — IfTrue, the library will verify that the provided components fulfil the main RSA properties.
Raises ValueError — when the key being imported fails the most basic RSA validity checks.
Returns: An RSA key object (RsaKey).
Crypto.PublicKey.RSA.generate(bits, randfunc=None, e=65537)
Create a new RSA key pair. Fresh keys, fresh start. 🔥
The algorithm closely follows NIST FIPS 186-4 in its sections B.3.1 and B.3.3. The modulus is the product of two non-strong probable primes. Each prime passes a suitable number of Miller-Rabin tests with random bases and a single Lucas test. Nerd stuff, but it's giving rigorous. 🌈
Parameters
bits(integer) — Key length, or size (in bits) of the RSA modulus. It must be at least1024, but2048is recommended. The FIPS standard only defines1024,2048and3072.
Keyword Arguments
randfunc(callable) — Function that returns random bytes. The default isCrypto.Random.get_random_bytes().e(integer) — Public RSA exponent. It must be an odd positive integer. It is typically a small number with very few ones in its binary representation. The FIPS standard requires the public exponent to be at least65537(the default).
Returns: an RSA key object (RsaKey, with private key).
Crypto.PublicKey.RSA.import_key(extern_key, passphrase=None)
Import an RSA key (public or private). Come on in, king. 👑
Parameters
extern_key(string or byte string) — The RSA key to import. The following formats are supported for an RSA public key:- X.509 certificate (binary or PEM format)
- X.509
subjectPublicKeyInfoDER SEQUENCE (binary or PEM encoding) - PKCS#1
RSAPublicKeyDER SEQUENCE (binary or PEM encoding) - An OpenSSH line (e.g. the content of
~/.ssh/id_ecdsa, ASCII)
The following formats are supported for an RSA private key:
- PKCS#1
RSAPrivateKeyDER SEQUENCE (binary or PEM encoding) - PKCS#8
PrivateKeyInfoorEncryptedPrivateKeyInfoDER SEQUENCE (binary or PEM encoding) - OpenSSH (text format, introduced in OpenSSH 6.5)
For details about the PEM encoding, see RFC1421/RFC1423.
passphrase(string or byte string) — For private keys only, the pass phrase that encrypts the key.
Returns: An RSA key object (RsaKey).
Raises ValueError/IndexError/TypeError — When the given key cannot be parsed (possibly because the pass phrase is wrong). 🫠
Crypto.PublicKey.RSA.oid = '1.2.840.113549.1.1.1'
Object ID for the RSA encryption algorithm. This OID often indicates a generic RSA key, even when such key will be actually used for digital signatures. Don't overthink it — it's the label on the box. 🎀
🌸 ECC 🌸
ECC (Elliptic Curve Cryptography) is a modern and efficient type of public key cryptography. Its security is based on the difficulty to solve discrete logarithms on the field defined by specific equations computed over a curve. Curves! Slay. 🦋
ECC can be used to create digital signatures or to perform a key exchange.
Okay so, compared to traditional algorithms like RSA, an ECC key is significantly smaller at the same security level. Fr fr — a 3072-bit RSA key takes 768 bytes, whereas the equally strong NIST P-256 private key only takes 32 bytes (that is, 256 bits). Like… the glow-up is real 💅.
With this module you can generate new ECC keys:
>>> from Crypto.PublicKey import ECC
>>>
>>> mykey = ECC.generate(curve='p256')
You can also export an ECC private key and protect it with a password, so that it is resistant to brute force attacks:
>>> pwd = b'secret'
>>> with open("myprivatekey.pem", "wt") as f:
>>> data = mykey.export_key(format='PEM',
passphrase=pwd,
protection='PBKDF2WithHMAC-SHA512AndAES256-CBC',
prot_params={'iteration_count':131072})
>>> f.write(data)
and reimport it later:
>>> pwd = b'secret'
>>> with open("myprivatekey.pem", "rt") as f:
>>> data = f.read()
>>> mykey = ECC.import_key(data, pwd)
You can also export the public key, which is not sensitive (it's giving public info, bestie, nobody's mad at it):
>>> with open("mypublickey.pem", "wt") as f:
>>> data = mykey.public_key().export_key(format='PEM')
🦋 the curve table (as a list, because tables are not the vibe here)
every curve, its canonical name, and every alias — nothing dropped, bestie 🫶
- NIST P-192 — canonical name
'NIST P-192'; aliases:'p192','P-192','prime192v1','secp192r1' - NIST P-224 — canonical name
'NIST P-224'; aliases:'p224','P-224','prime224v1','secp224r1' - NIST P-256 — canonical name
'NIST P-256'; aliases:'p256','P-256','prime256v1','secp256r1' - NIST P-384 — canonical name
'NIST P-384'; aliases:'p384','P-384','prime384v1','secp384r1' - NIST P-521 — canonical name
'NIST P-521'; aliases:'p521','P-521','prime521v1','secp521r1' - Ed25519 — canonical name
'Ed25519'; alias:'ed25519' - Ed448 — canonical name
'Ed448'; alias:'ed448' - Curve25519 — canonical name
'Curve25519'; alias:'curve25519' - Curve448 — canonical name
'Curve448'; alias:'curve448'
For more information about each NIST curve, see FIPS 186-4, Section D.1.2. 📖
Curves Ed25519 and Ed448 are defined in RFC8032.
Curves Curve25519 and Curve448 are defined in RFC7748.
The ECC keys can be used to perform or verify signatures, using the modules Crypto.Signature.DSS (ECDSA; NIST curves only) or Crypto.Signature.eddsa (EdDSA; Ed25519 and Ed448 curve only). Note the "only"-ness, okay? NIST curves for ECDSA, Ed25519/Ed448 for EdDSA — do not mix and match, that's not the move 🙈.
👑 class Crypto.PublicKey.ECC.EccKey(**kwargs)
Class defining an ECC key. Do not instantiate directly — like, ever, bestie, that's not her moment. Use generate(), construct() or import_key() instead.
Variables
curve(string) — The canonical name of the curve as defined in the ECC table. ✨pointQ(EccPointorEccXPoint) — an ECC point representing the public component.d(integer) — A scalar that represents the private component in NIST P curves. It is smaller than the order of the generator point.seed(bytes) — A seed that represents the private component inEd22519(32bytes), Curve25519 (32bytes), Curve448 (56bytes), Ed448 (57bytes).
🌸 export_key(**kwargs)
Export this ECC key. Time to yeet it into a file, periodt.
Parameters
format(string) — The output format:'DER'. The key will be encoded in ASN.1 DER format (binary). For a public key, the ASN.1subjectPublicKeyInfostructure defined in RFC5480 will be used. For a private key, the ASN.1ECPrivateKeystructure defined in RFC5915 is used instead (possibly within a PKCS#8 envelope, see theuse_pkcs8flag below).'PEM'. The key will be encoded in a PEM envelope (ASCII).'OpenSSH'. The key will be encoded in the OpenSSH format (ASCII, public keys only).'SEC1'. The public key (i.e., the EC point) will be encoded into bytes according to Section 2.3.3 of SEC1 (which is a subset of the older X9.62 ITU standard). Only for NIST P-curves.'raw'. The public key will be encoded as bytes, without any metadata.
passphrase(bytes or string) — (Private keys only) The passphrase to protect the private key.use_pkcs8(boolean) — (Private keys only) IfTrue(default and recommended), the PKCS#8 representation will be used. It must beTruefor Ed25519, Ed448, Curve25519, and Curve448. IfFalseand a passphrase is present, the obsolete PEM encryption will be used. (<- obsolete, iykyk, don't)protection(string) — When a private key is exported with password-protection and PKCS#8 (both DER and PEM formats), this parameter MUST be present. For all possible protection schemes, refer to the encryption parameters of PKCS#8. It is recommended to use'PBKDF2WithHMAC-SHA512AndAES128-CBC'.compress(boolean) — IfTrue, the method returns a more compact representation of the public key, with the X-coordinate only. IfFalse(default), the method returns the full public key. This parameter is ignored for Ed25519/Ed448/Curve25519/Curve448, as compression is mandatory. (no choice, bestie, it's mandatory 💀)prot_params(dict) — When a private key is exported with password-protection and PKCS#8 (both DER and PEM formats), this dictionary contains the parameters to use to derive the encryption key from the passphrase. For all possible values, refer to the encryption parameters of PKCS#8. The recommendation is to use{'iteration_count':21000}for PBKDF2, and{'iteration_count':131072}for scrypt.
WARNING: 🚨 If you don't provide a passphrase, the private key will be exported in the clear! Like, fully naked. No protection. Do not do this. 😭
NOTE: When exporting a private key with password-protection and PKCS#8 (both DER and PEM formats), any extra parameters toexport_key()will be passed toCrypto.IO.PKCS8.
Returns
A multi-line string (for 'PEM' and 'OpenSSH') or bytes (for 'DER', 'SEC1', and 'raw') with the encoded key. 💖
🎀 has_private()
True if this key can be used for making signatures or decrypting data. That's the whole vibe check — does she have the private half or not. 🫶
⭐ public_key()
A matching ECC public key.
Returns
a new EccKey object
💀 exception Crypto.PublicKey.ECC.UnsupportedEccFeature
Yeah, that's an exception class. The source gives her no extra description, so we're not inventing one — she just exists, iconic and unexplained.
🌈 Crypto.PublicKey.ECC.construct(**kwargs)
Build a new ECC key (private or public) starting from some base components.
In most cases, you will already have an existing key which you can read in with import_key() instead of this function. (lowkey, import_key() is usually the main character here 👑)
Parameters
curve(string) — Mandatory. The name of the elliptic curve, as defined in the ECC table.d(integer) — Mandatory for a private key and a NIST P-curve (e.g., P-256). It must be an integer in the range[1..order-1].seed(bytes) — mandatory for a private key, and only on these curves, bestie: Ed25519 (32 bytes), Curve25519 (32 bytes), Curve448 (56 bytes) and Ed448 (57 bytes). Count the bytes, it matters 💅point_x(integer) — the X coordinate (affine) of the ECC point. Mandatory for a public key, no cap.point_y(integer) — the Y coordinate (affine) of the ECC point. Mandatory for a public key too, except for Curve25519 and Curve448 — those two get a pass 🦋
💖 Returns
a new ECC key object
💖 Return type
EccKey
Crypto.PublicKey.ECC.generate(**kwargs)
Generate a shiny new private key on the curve you pick. ✨
Parameters
curve(string) — mandatory, fr. It has to be a curve name actually defined in the ECC table.randfunc(callable) — optional. This is the RNG it reads randomness from. If it'sNone, thenCrypto.Random.get_random_bytes()is used instead. 🎲
Crypto.PublicKey.ECC.import_key(encoded, passphrase=None, curve_name=None)
Import an ECC key — public or private, it'll figure it out. Detective mode activated 🔍
Parameters
encoded(bytes or multi-line string) —
the ECC key you want to import. The function will try to automatically detect the right format.
Supported formats for an ECC public key:
- X.509 certificate: binary (DER) or ASCII (PEM).
- X.509 subjectPublicKeyInfo: binary (DER) or ASCII (PEM).
- SEC1 (or X9.62), as bytes. NIST P curves only. You must also provide the
curve_name(with a value from the ECC table). - OpenSSH line, defined in RFC5656 and RFC8709 (ASCII). This is normally the content of files like
~/.ssh/id_ecdsa.pub.
Supported formats for an ECC private key:
- A binary ECPrivateKey structure, as defined in RFC5915 (DER). NIST P curves only.
- A PKCS#8 structure (or the more recent Asymmetric Key Package, RFC5958): binary (DER) or ASCII (PEM).
- OpenSSH 6.5 and newer versions (ASCII).
Private keys can be in the clear or password-protected. 🔓🔒
For details about the PEM encoding, see RFC1421/RFC1423.
passphrase(byte string) — the passphrase used for decrypting a private key. Encryption may be applied protected at the PEM level (not recommended) or at the PKCS#8 level (recommended). This parameter is ignored if the key in input is not encrypted. 🙈curve_name(string) — for a SEC1 encoding only. This is the name of the curve, as defined in the ECC table.
NOTE 🫶 — to import EdDSA private and public keys, when encoded as raw bytes, use:
Crypto.Signature.eddsa.import_public_key(), orCrypto.Signature.eddsa.import_private_key().
NOTE 🫶 — to import X25519/X448 private and public keys, when encoded as raw bytes, use:
Crypto.Protocol.DH.import_x25519_public_key()Crypto.Protocol.DH.import_x25519_private_key()Crypto.Protocol.DH.import_x448_public_key()Crypto.Protocol.DH.import_x448_private_key()
Returns a new ECC key object
Return type EccKey
Raises ValueError — when the given key cannot be parsed (possibly because the pass phrase is wrong). 😭
🗝️ DSA
DSA is a widespread public key signature algorithm. Its security is based on the discrete logarithm problem (DLP). Given a cyclic group, a generator g, and an element h, it is hard to find an integer x such that g^x = h. The problem is believed to be difficult, and it has been proved such (and therefore secure) for more than 30 years. The math is literally older than your parents and still holding up 👑
The group is actually a sub-group over the integers modulo p, with p prime. The sub-group order is q, which is prime too; it always holds that (p-1) is a multiple of q. The cryptographic strength is linked to the magnitude of p and q. The signer holds a value x (0<x<q-1) as private key, and its public key (y where y=g^x \text{ mod } p) is distributed.
In 2017, a sufficient size is deemed to be 2048 bits for p and 256 bits for q. For more information, see the most recent ECRYPT report. 📚
The algorithm can only be used for authentication (digital signature). DSA cannot be used for confidentiality (encryption). Say it with me: signatures only, no secrets. 🚫🤫
The values (p,q,g) are called domain parameters; they are not sensitive but must be shared by both parties (the signer and the verifier). Different signers can share the same domain parameters with no security concerns. Sharing is caring, iykyk.
The DSA signature is twice as big as the size of q (64 bytes if q is 256 bit long).
This module provides facilities for generating new DSA keys and for constructing them from known components.
As an example, this is how you generate a new DSA key pair, save the public key in a file called public_key.pem, sign a message (with Crypto.Signature.DSS), and verify it:
>>> from Crypto.PublicKey import DSA
>>> from Crypto.Signature import DSS
>>> from Crypto.Hash import SHA256
>>>
>>> # Create a new DSA key
>>> key = DSA.generate(2048)
>>> f = open("public_key.pem", "w")
>>> f.write(key.publickey().export_key())
>>> f.close()
>>>
>>> # Sign a message
>>> message = b"Hello"
>>> hash_obj = SHA256.new(message)
>>> signer = DSS.new(key, 'fips-186-3')
>>> signature = signer.sign(hash_obj)
>>>
>>> # Load the public key
>>> f = open("public_key.pem", "r")
>>> hash_obj = SHA256.new(message)
>>> pub_key = DSA.import_key(f.read())
>>> verifier = DSS.new(pub_key, 'fips-186-3')
>>>
>>> # Verify the authenticity of the message
>>> try:
>>> verifier.verify(hash_obj, signature)
>>> print "The message is authentic."
>>> except ValueError:
>>> print "The message is not authentic."
class Crypto.PublicKey.DSA.DsaKey(key_dict)
Class defining an actual DSA key. Do not instantiate directly, bestie — that's not the vibe. Use generate(), construct() or import_key() instead. 💅
Variables
p(integer) — DSA modulusq(integer) — Order of the subgroupg(integer) — Generatory(integer) — Public keyx(integer) — Private key
Undocumented exportKey, publickey
domain()
The DSA domain parameters. The whole (p,q,g) trio, served fresh 🍓
Returns tuple : (p,q,g)
Crypto.PublicKey.DSA.DsaKey.exportKey(format='PEM', pkcs8=None, passphrase=None, protection=None, randfunc=None)
Export this DSA key. Time to yeet it out into the world ✨
Parameters
format(string) —
the encoding for the output:
'PEM'(default). ASCII as per RFC1421/ RFC1423.'DER'. Binary ASN.1 encoding.'OpenSSH'. ASCII one-liner as per RFC4253. Only suitable for public keys, not for private keys.
passphrase(string) — private keys only. The pass phrase to protect the output.pkcs8(boolean) — private keys only. IfTrue(default), the key is encoded with PKCS#8. IfFalse, it is encoded in the custom OpenSSL/OpenSSH container.protection(string) —
only in combination with a pass phrase. The encryption scheme to use to protect the output.
If pkcs8 takes value True, this is the PKCS#8 algorithm to use for deriving the secret and encrypting the private DSA key. For a complete list of algorithms, see Crypto.IO.PKCS8. The default is PBKDF2WithHMAC-SHA1AndDES-EDE3-CBC.
If pkcs8 is False, the obsolete PEM encryption scheme is used. It is based on MD5 for key derivation, and Triple DES for encryption. Parameter protection is then ignored. 💀
The combination format='DER' and pkcs8=False is not allowed if a passphrase is present.
randfunc(callable) — a function that returns random bytes. By default it isCrypto.Random.get_random_bytes().
Returns the encoded key
Return type byte string
Raises ValueError — when the format is unknown or when you try to encrypt a private key with DER format and OpenSSL/OpenSSH.
WARNING: if you don't provide a pass phrase, the private key will be exported in the clear! 😭 Like, actually naked. Do not do this.
Crypto.PublicKey.DSA.DsaKey.export_key(format='PEM', pkcs8=None, passphrase=None, protection=None, randfunc=None)
Export this DSA key. (Yes, same energy, different spelling — both live here 🫶)
Parameters
format(string) —
the encoding for the output:
'PEM'(default). ASCII as per RFC1421/ RFC1423.'DER'. Binary ASN.1 encoding.'OpenSSH'. ASCII one-liner as per RFC4253. Only suitable for public keys, not for private keys.
passphrase(string) — private keys only. The pass phrase to protect the output.pkcs8(boolean) — private keys only. IfTrue(default), the key is encoded with PKCS#8. IfFalse, it is encoded in the custom OpenSSL/OpenSSH container.protection(string) —
only in combination with a pass phrase. The encryption scheme to use to protect the output.
If pkcs8 takes value True, this is the PKCS#8 algorithm to use for deriving the secret and encrypting the private DSA key. For a full lineup of algorithms, peep Crypto.IO.PKCS8. The default is PBKDF2WithHMAC-SHA1AndDES-EDE3-CBC.
If pkcs8 is False, then the obsolete PEM encryption scheme gets used instead. It's built on MD5 for key derivation and Triple DES for encryption, bestie. Parameter protection is just ignored in that case — yeah, it's giving legacy 💀.
Heads up: the combo offormat='DER'andpkcs8=Falseis not allowed if a passphrase is present. No cap, it just won't fly.
randfunc(callable) — a function that hands back random bytes. By default it'sCrypto.Random.get_random_bytes().
Returns: the encoded key 💖
Return type: byte string
Raises ValueError — when the format is unknown, or when you try to encrypt a private key with DER format and OpenSSL/OpenSSH.
⚠️ WARNING: if you don't provide a pass phrase, the private key gets exported in the clear! Literally naked. Do not do this, bestie 😭
has_private()
Whether this is a DSA private key.
public_key()
A matching DSA public key.
Returns: a new DsaKey object
publickey()
A matching DSA public key. (Yes, same thing as public_key() — twin vibes 🫶)
Returns: a new DsaKey object
🌸 Crypto.PublicKey.DSA.construct(tup, consistency_check=True)
Construct a DSA key from a tuple of valid DSA components. Glow-up from raw numbers ✨
Parameters:
tup(tuple) — a tuple of long integers with 4 or 5 items, in this exact order:- Public key (
y). - Sub-group generator (
g). - Modulus, finite field order (
p). - Sub-group order (
q). - Private key (
x). Optional.
- Public key (
consistency_check(boolean) — ifTrue, the library verifies that the components you handed over actually fulfil the main DSA properties. Kind of a vibe check for your key 🔍
Raises ValueError — when the key being imported fails the most basic DSA validity checks.
Returns: a DSA key object
Return type: DsaKey
🦋 Crypto.PublicKey.DSA.generate(bits, randfunc=None, domain=None)
Generate a brand new DSA key pair. Fresh out the oven, periodt 🔥
The algorithm follows Appendix A.1/A.2 and B.1 of FIPS 186-4, respectively for domain generation and key pair generation. (Yes, it cites its sources. Academic icon behavior 👑)
Parameters:
bits(integer) — key length, aka the size (in bits) of the DSA modulusp. It must be1024,2048or3072. Those are the only options, no improvising 💅randfunc(callable) — random number generation function; it takes a single integer N and returns a string of random data N bytes long. If you don't specify one,Crypto.Random.get_random_bytes()is used.domain(tuple) — the DSA domain parametersp,qandgas a list of 3 integers. Size ofpandqmust comply to FIPS 186-4. If not specified, the parameters are created anew.
Returns: a new DSA key object
Return type: DsaKey
Raises ValueError — when bits is too little, too big, or not a multiple of 64.
🎀 Crypto.PublicKey.DSA.import_key(extern_key, passphrase=None)
Import a DSA key. Come on in, key bestie 🫶
Parameters:
extern_key(string or byte string) — the DSA key to import.
These formats are supported for a DSA public key:
- X.509 certificate (binary DER or PEM)
- X.509 subjectPublicKeyInfo (binary DER or PEM)
- OpenSSH (ASCII one-liner, see RFC4253)
And these formats are supported for a DSA private key:
- PKCS#8 PrivateKeyInfo or EncryptedPrivateKeyInfo DER SEQUENCE (binary or PEM)
- OpenSSL/OpenSSH custom format (binary or PEM)
For details about the PEM encoding, see RFC1421/RFC1423. (Reading material for later, iykyk 📚)
passphrase(string) — in case of an encrypted private key, this is the pass phrase from which the decryption key is derived.
Encryption may be applied either at the PKCS#8 level or at the PEM level. Either way, it's covered 😌
Returns: a DSA key object
Return type: DsaKey
Raises ValueError — when the given key cannot be parsed (possibly because the pass phrase is wrong). Wrong password energy, we've all been there 🙈
- RSA keys
- Elliptic Curve keys
- DSA keys (obsolete)
💜 Obsolete key type El Gamal
⚠️ WARNING: even though ElGamal algorithms are, in theory, reasonably secure, in practice there are no real good reasons to prefer them to RSA instead.
Signature algorithm
The security of the ElGamal signature scheme is based (like DSA) on the discrete logarithm problem (DLP). Given a cyclic group, a generator g, and an element h, it is hard to find an integer x such that g^x = h.
The group is the largest multiplicative sub-group of the integers modulo p, with p prime. The signer holds a value x (0<x<p-1) as private key, and its public key (y where y=g^x \text{ mod } p) is distributed.
The ElGamal signature is twice as big as p. Chunky, but that's the deal 💀
Encryption algorithm
The security of the ElGamal encryption scheme is based on the computational Diffie-Hellman problem (CDH). Given a cyclic group, a generator g, and two integers a and b, it is difficult to find the element g^{ab} when only g^a and g^b are known, and not a and b.
As before, the group is the largest multiplicative sub-group of the integers modulo p, with p prime. The receiver holds a value a (0<a<p-1) as private key, and its public key (b where b=g^a) is given to the sender.
The ElGamal ciphertext is twice as big as p.
Domain parameters
For both the signature and encryption schemes, the values (p,g) are called domain parameters. They are not sensitive but must be distributed to all parties (senders and receivers). Different signers can share the same domain parameters, as can different recipients of encrypted messages. Sharing is caring, genuinely 🌈
Security
Both the DLP and CDH problems are believed to be difficult, and they have been proved such (and therefore secure) for more than 30 years. Long track record, iconic behavior ⭐
The cryptographic strength is linked to the magnitude of p. In 2017, a sufficient size for p is deemed to be 2048 bits. For more information, see the most recent ECRYPT report.
The signature is four times larger than the equivalent DSA, and the ciphertext is two times larger than the equivalent RSA. Big key, big output, that's just how it is 😭
Functionality
This module provides facilities for generating new ElGamal keys and constructing them from known components.
class Crypto.PublicKey.ElGamal.ElGamalKey(randfunc=None)
Class defining an ElGamal key. Do not instantiate directly. Use generate() or construct() instead, bestie — that's the whole point 🙏
Variables:
p— Modulusg— Generatory(integer) — Public key componentx(integer) — Private key component
has_private()
Whether this is an ElGamal private key.
publickey()
A matching ElGamal public key.
Returns: a new ElGamalKey object
🧚 Crypto.PublicKey.ElGamal.construct(tup)
Construct an ElGamal key from a tuple of valid ElGamal components.
The modulus p must be a prime. The following conditions must apply:
\begin{align}
&1 < g < p-1 \\ &g^{p-1} = 1 \text{ mod } 1 \\ &1 < x < p-1 \\
&g^x = y \text{ mod } p \end{align}
Parameters:
tup(tuple) — a tuple with either 3 or 4 integers, in this exact order:- Modulus (
p). - Generator (
g). - Public key (
y). - Private key (
x). Optional.
- Modulus (
Raises ValueError — when the key being imported fails the most basic ElGamal validity checks.
Returns: an ElGamalKey object
🔥 Crypto.PublicKey.ElGamal.generate(bits, randfunc)
Randomly generate a fresh, new ElGamal key.
The key will be safe for use for both encryption and signature (although it should be used for only one purpose). Pick a lane, key 💅
Parameters:
bits(int) — key length, or size (in bits) of the modulusp. The recommended value is2048.randfunc(callable) — random number generation function; it should accept a single integer N and return a string of random N random bytes.
Returns: an ElGamalKey object
- ElGamal keys
🍓 Crypto.Protocol package
Key Derivation Functions
This module contains a collection of standard key derivation functions.
A key derivation function derives one or more secondary secret keys from one primary secret (a master key or a pass phrase).
This is typically done to insulate the secondary keys from each other, to avoid that leakage of a secondary key compromises the security of the master key, or to thwart attacks on pass phrases (e.g. via rainbow tables). Basically: compartmentalize, bestie 🎀
🌸 PBKDF2
PBKDF2 is the most widespread algorithm for deriving keys from a password, originally defined in version 2.0 of the PKCS#5 standard or in RFC2898.
It is computationally expensive (a property that can be tuned via the count parameter) so as to thwart dictionary and rainbow tables attacks. However, it uses a very limited amount of RAM which makes it insufficiently protected against advanced and motivated adversaries that can leverage GPUs. So like… it's trying its best, but it's not the final boss 😭
New applications and protocols should use scrypt or bcrypt instead. Noted, moving on ✨
For example, if you need to derive two AES256 keys:
from Crypto.Protocol.KDF import PBKDF2
from Crypto.Hash import SHA512
from Crypto.Random import get_random_bytes
password = b'my super secret'
salt = get_random_bytes(16)
keys = PBKDF2(password, salt, 64, count=1000000, hmac_hash_module=SHA512)
key1 = keys[:32]
key2 = keys[32:]
Crypto.Protocol.KDF.PBKDF2(password, salt, dkLen=16, count=1000, prf=None, hmac_hash_module=None)
Ok so this one derives one or more keys from a password (or passphrase) 💖. It does key derivation exactly the way the PKCS#5 standard (v2.0) says to.
Parameters 🌸
password(string or byte string) — the secret password you're generating the key from. Heads up: strings get encoded as ISO 8859-1 (aka Latin-1), and that encoding literally cannot hold any characters with codepoints > 255. So no emoji passwords bestie 😭.salt(string or byte string) — some (byte) string used to give you better protection from dictionary attacks. It does not need to be kept secret, but it should be randomly chosen for every single derivation. They recommend at least 16 bytes. And same deal as above: strings get encoded as ISO 8859-1 (Latin-1), which does not allow any characters with codepoints > 255.dkLen(integer) — the cumulative length of the keys you want to produce. Here's the tea though 🫖: because of a flaw in the PBKDF2 design, you should not ask for more bytes than theprfcan output. Like,dkLenshould not exceed 20 bytes if you're rolling with HMAC-SHA1.count(integer) — how many iterations to carry out. Higher value = slower and more secure. You want to find the maximum number of iterations that still keeps the key derivation acceptable on the slowest hardware you have to support. The default is1000, but they recommend at least1000000(1 million) iterations. ngl, crank it up 🔥.prf(callable) — a pseudorandom function. It has to be a function that returns a pseudorandom byte string from two parameters: a secret and a salt. The slower the algorithm, the more secure the derivation function. If you don't specify one, HMAC-SHA1 is used.hmac_hash_module(module) — a module fromCrypto.Hashimplementing a Merkle-Damgard cryptographic hash, which PBKDF2 must use together with HMAC. This parameter is mutually exclusive withprf(so pick one, not both 💅).
Returns — a byte string of length dkLen that can be used as key material. Want multiple keys? Just break this string up into segments of whatever length you need. Easy ✨.
🎀 scrypt 🎀
scrypt is a password-based key derivation function created by Colin Percival, described in his paper "Stronger key derivation via sequential memory-hard functions" and also in RFC7914 📚.
On top of being computationally expensive, it's also memory intensive — which means it's more secure against the risk of custom ASICs. It's giving "expensive in every direction" and we love that for us 👑.
Example:
from Crypto.Protocol.KDF import scrypt
from Crypto.Random import get_random_bytes
password = b'my super secret'
salt = get_random_bytes(16)
key = scrypt(password, salt, 16, N=2**14, r=8, p=1)
Crypto.Protocol.KDF.scrypt(password, salt, key_len, N, r, p, num_keys=1)
Derive one or more keys from a passphrase 💜.
Parameters
password(string) — the secret pass phrase to generate the keys from.salt(string) — a string to use for better protection from dictionary attacks. Doesn't need to be kept secret, but it should be randomly chosen for each derivation, and they recommend it be at least 16 bytes long.key_len(integer) — the length in bytes of each derived key.N(integer) — CPU/Memory cost parameter. It must be a power of 2 and less than 2^{32}.r(integer) — block size parameter.p(integer) — parallelization parameter. It must be no greater than (2^{32}-1)/(4r).num_keys(integer) — how many keys to derive. Every key iskey_lenbytes long. By default only 1 key is generated. The maximum cumulative length of all keys is (2^{32}-1)*32 — that's 128TB, so like… good luck filling that 🫶.
Colin Percival suggested a good choice of parameters (N, r, p) in his 2009 presentation:
- ( 2¹⁴, 8, 1 ) for interactive logins (≤100ms)
- ( 2²⁰, 8, 1 ) for file encryption (≤5s)
Returns — a byte string or a tuple of byte strings.
🐚 bcrypt 🐚
bcrypt is a password hashing function designed by Niels Provos and David Mazières.
Same energy as scrypt: on top of being computationally expensive, it's also memory intensive, which makes it more secure against the risk of custom ASICs 🌈.
Heads up: this implementation only supports bcrypt hashes with prefix $2a. That's the vibe check it passes and no other 💀.
By design, bcrypt only accepts passwords up to 72 byte long. If you want to hash passwords with no restrictions on their length, it's common practice to apply a cryptographic hash and then BASE64-encode. For instance:
from base64 import b64encode
from Crypto.Hash import SHA256
from Crypto.Protocol.KDF import bcrypt
password = b"test"
b64pwd = b64encode(SHA256.new(password).digest())
bcrypt_hash = bcrypt(b64pwd, 12)
and to check them:
from base64 import b64encode
from Crypto.Hash import SHA256
from Crypto.Protocol.KDF import bcrypt
password_to_test = b"test"
try:
b64pwd = b64encode(SHA256.new(password).digest())
bcrypt_check(b64pwd, bcrypt_hash)
except ValueError:
print("Incorrect password")
Crypto.Protocol.KDF.bcrypt(password, cost, salt=None)
Hash a password into a key, using the OpenBSD bcrypt protocol 🔐.
Parameters
password(byte string or string) — the secret password or pass phrase. It must be at most 72 bytes long. It must not contain the zero byte. Unicode strings will be encoded as UTF-8.cost(integer) — the exponential factor that makes it slower to compute the hash. It must be in the range 4 to 31. A value of at least 12 is recommended.salt(byte string) — optional. A random byte string that thwarts dictionary and rainbow table attacks. It must be 16 bytes long. If you don't pass one, a random value is generated for you 💅.
Return (byte string): the bcrypt hash.
Raises ValueError — if password is longer than 72 bytes or if it contains the zero byte.
Crypto.Protocol.KDF.bcrypt_check(password, bcrypt_hash)
Verify if the provided password matches the given bcrypt hash ✅.
Parameters
password(byte string or string) — the secret password or pass phrase to test. It must be at most 72 bytes long. It must not contain the zero byte. Unicode strings will be encoded as UTF-8.bcrypt_hash(byte string, bytearray) — the reference bcrypt hash the password needs to be checked against.
Raises ValueError — if the password does not match 😭.
🦋 HKDF 🦋
The HMAC-based Extract-and-Expand key derivation function (HKDF) was designed by Hugo Krawczyk. It's standardized in RFC 5869 and in NIST SP-800 56C 📖.
Real talk: this KDF is not suitable for deriving keys from a password, and it's not for key stretching either. Different tool, different job 💖.
Example, for deriving two AES256 keys:
from Crypto.Protocol.KDF import HKDF
from Crypto.Hash import SHA512
from Crypto.Random import get_random_bytes
salt = get_random_bytes(16)
key1, key2 = HKDF(master_secret, 32, salt, SHA512, 2)
Crypto.Protocol.KDF.HKDF(master, key_len, salt, hashmod, num_keys=1, context=None)
Derive one or more keys from a master secret using the HMAC-based KDF defined in RFC5869 ✨.
Parameters
master(byte string) — the unguessable value the KDF uses to generate the other keys. It must be a high-entropy secret, though not necessarily uniform. It must not be a password 🚫.key_len(integer) — the length in bytes of every derived key.salt(byte string) — a non-secret, reusable value that strengthens the randomness extraction step. Ideally it's as long as the digest size of the chosen hash. If it's empty, a string of zeroes is used.hashmod(module) — a cryptographic hash algorithm fromCrypto.Hash.Crypto.Hash.SHA512is a good choice 👑.num_keys(integer) — the number of keys to derive. Every key iskey_lenbytes long. The maximum cumulative length of all keys is 255 times the digest size.context(byte string) — optional identifier describing what the keys are used for.
Returns — a byte string or a tuple of byte strings.
⭐ SP 800-108 Counter Mode ⭐
So a KDF can be generically constructed with a pseudorandom function (PRF). If the PRF has a fixed-length output, you can evaluate the PRF multiple times and concatenate the results until you've collected enough derived keying material 🧚.
This function implements that type of KDF, where a counter contributes to each invokation of the PRF, as defined in NIST SP 800-108 Rev 1. The NIST standard only allows the use of HMAC (recommended) and CMAC (not recommended) as PRF.
Same warning as HKDF: this KDF is not suitable for deriving keys from a password 🙈.
Example 1 (HMAC as PRF, one AES128 key to derive):
>> from Crypto.Hash import SHA256, HMAC
>>
>> def prf(s, x):
>> return HMAC.new(s, x, SHA256).digest()
>>
>> key_derived = SP800_108_Counter(secret, 16, prf, label=b'Key A')
Example 2 (HMAC as PRF, two AES128 keys to derive):
Example 2, where the PRF is HMAC-SHA256 and we pull out two 16-byte keys:
>> from Crypto.Hash import SHA256, HMAC
>>
>> def prf(s, x):
>> return HMAC.new(s, x, SHA256).digest()
>>
>> key_A, key_B = SP800_108_Counter(secret, 16, prf, num_keys=2, label=b'Key AB')
Example 3, where the PRF is CMAC and we derive two AES256 keys:
>> from Crypto.Cipher import AES
>> from Crypto.Hash import SHA256, CMAC
>>
>> def prf(s, x):
>> return CMAC.new(s, x, AES).digest()
>>
>> key_A, key_B = SP800_108_Counter(secret, 32, prf, num_keys=2, label=b'Key AB')
Crypto.Protocol.KDF.SP800_108_Counter(master, key_len, prf, num_keys=None, label=b'', context=b'') — okay so this one derives one or more keys from a master secret using a pseudorandom function in Counter Mode, exactly as specified in NIST SP 800-108r1. 💜
Parameters — buckle up bestie, here's the whole cast:
master(byte string) — the secret value the KDF uses to derive all the other keys. It must not be a password. The length of the secret has to line up with the input that theprffunction expects.key_len(integer) — the length in bytes of each derived key.prf(function) — a pseudorandom function that takes two byte strings as its parameters: the secret and an input. It hands back another byte string.num_keys(integer) — how many keys to derive. Every single key iskey_lenbytes long. By default, only 1 key gets derived.label(byte string) — optional description of what the derived keys are for. It must not contain zero bytes. 🙈context(byte string) — optional info about the protocol that uses the keys, like the identity of the participants, nonces, session IDs, etc. It must not contain zero bytes either.
Returns — one of two vibes:
- a byte string (if
num_keysis not specified), or - a tuple of byte strings (if
num_keyis specified).
🎀 PBKDF1 🎀
PBKDF1 is, like, the vintage key derivation function — defined all the way back in version 2.0 of the PKCS#5 standard (v1.5) or in RFC2898. It's giving grandma's cryptography, ngl. 👑
WARNING: Newer applications should use the more secure and versatilescryptinstead. No cap,scryptate and left no crumbs — PBKDF1 is only here for the legacy lore. 💀
Crypto.Protocol.KDF.PBKDF1(password, salt, dkLen, count=1000, hashAlgo=None) — derives one single key from a password (or passphrase). It does key derivation according to that old version of the PKCS#5 standard (v1.5) or RFC2898.
Parameters — the whole lineup:
password(string) — the secret password you're generating the key from.salt(byte string) — an 8 byte string used for better protection from dictionary attacks. This value does not need to be kept secret, but it should be randomly chosen for each derivation. ✨dkLen(integer) — the length of the desired key. The default is 16 bytes, suitable for instance forCrypto.Cipher.AES.count(integer) — the number of iterations to carry out. The recommendation is 1000 or more. 🔥hashAlgo(module) — the hash algorithm to use, as a module or an object from theCrypto.Hashpackage. The digest length must be no shorter thandkLen. The default algorithm isCrypto.Hash.SHA1.
Returns — a byte string of length dkLen that can be used as a key. Slay. 🫶
🦋 Secret Sharing Schemes 🦋
This module implements Shamir's secret sharing protocol, described in the paper "How to share a secret". Iconic paper, honestly.
The secret can be split into an arbitrary number of shares (n), such that it is sufficient to collect just k of them to reconstruct it (k < n). For instance, you might wanna grant 16 people the ability to access a system with a pass code, but only on the condition that at least 3 of them are present at the same time. As they join their shares, the pass code is revealed. In that case, n=16 and k=3. Main character energy for real. 💅
In the Shamir's secret sharing scheme, the n shares are created by first defining a polynomial of degree k-1:
q(x) = a_0 + a_1 x + a_2 x^2 + \ldots + a_{k-1} x^{k-1}
The coefficient a_0 is fixed with the secret value. The coefficients a_1 \ldots a_{k-1} are random and they are discarded as soon as the shares are created. Poof, gone, bye. 🌸
Each share is a pair (x_i, y_i), where x_i is an arbitrary but unique number assigned to the share's recipient and y_i=q(x_i).
This implementation has the following properties:
- The secret is a byte string of 16 bytes (e.g. an AES 128 key).
- Each share is a byte string of 16 bytes.
- The recipients of the shares are assigned an integer starting from 1 (share number
x_i). - The polynomial
q(x)is defined over the field GF(2^{128}) with the same irriducible polynomial as used in AES-GCM:1 + x + x^2 + x^7 + x^{128}. - It can be compatible with the popular
sssstool when used with the 128 bit security level and no dispersion: the command line arguments must include-s 128 -D. Note thatssssuses a slightly different polynomial:
r(x) = a_0 + a_1 x + a_2 x^2 + \ldots + a_{k-1} x^{k-1} + x^k
which requires you to specify ssss=True when calling split() and combine(). iykyk 🧚
Each recipient needs to hold both the share number (x_i, which is not confidential) and the secret (which needs to be protected securely). Like, protect it protect it. 🔒
As an example, the following code shows how to protect a file meant for 5 people, in such a way that any 2 of them are sufficient to reassemble it:
>>> from binascii import hexlify
>>> from Crypto.Cipher import AES
>>> from Crypto.Random import get_random_bytes
>>> from Crypto.Protocol.SecretSharing import Shamir
>>>
>>> key = get_random_bytes(16)
>>> shares = Shamir.split(2, 5, key)
>>> for idx, share in shares:
>>> print "Index #%d: %s" % (idx, hexlify(share))
>>>
>>> with open("clear.txt", "rb") as fi, open("enc.txt", "wb") as fo:
>>> cipher = AES.new(key, AES.MODE_EAX)
>>> ct, tag = cipher.encrypt(fi.read()), cipher.digest()
>>> fo.write(cipher.nonce + tag + ct)
Each person can be given one share and the encrypted file. Cute, right? 🎀
When 2 people gather together with their shares, they can decrypt the file:
>>> from binascii import unhexlify
>>> from Crypto.Cipher import AES
>>> from Crypto.Protocol.SecretSharing import Shamir
>>>
>>> shares = []
>>> for x in range(2):
>>> in_str = raw_input("Enter index and share separated by comma: ")
>>> idx, share = [ strip(s) for s in in_str.split(",") ]
>>> shares.append((idx, unhexlify(share)))
>>> key = Shamir.combine(shares)
>>>
>>> with open("enc.txt", "rb") as fi:
>>> nonce, tag = [ fi.read(16) for x in range(2) ]
>>> cipher = AES.new(key, AES.MODE_EAX, nonce)
>>> try:
>>> result = cipher.decrypt(fi.read())
>>> cipher.verify(tag)
>>> with open("clear2.txt", "wb") as fo:
>>> fo.write(result)
>>> except ValueError:
>>> print "The shares were incorrect"
ATTENTION: Reconstruction may succeed but still produce the incorrect secret if any of the presented shares is incorrect (due to data corruption or to a malicious participant). 😭 It is extremely important to also use an authentication mechanism (such as the EAX cipher mode in the example). Do not skip that part bestie, I'm begging.
👑 class Crypto.Protocol.SecretSharing.Shamir 👑
Shamir's secret sharing scheme.
A secret is split into n shares, and it is sufficient to collect k of them to reconstruct the secret.
static combine(shares, ssss=False) — recombines a secret, if enough shares are presented.
Parameters:
shares(tuples) — the k tuples, each containing the index (an integer) and the share (a byte string, 16 bytes long) that were assigned to a participant.
NOTE: Pass exactly as many share as they are required, and no more. Extra shares are not the vibe. 🚫
ssss(bool) — ifTrue, the shares were produced by thessssutility (without using the "diffusion layer"). Default:False.
Returns: the original secret, as a byte string (16 bytes long). 💖
static split(k, n, secret, ssss=False) — splits a secret into n shares.
The secret can be reconstructed later using just k shares out of the original n. Each share must be kept confidential to the person it was assigned to. 🤫
Each share is associated to an index (starting from 1).
Parameters:
k(integer) — the number of shares needed to reconstruct the secret.n(integer) — the number of shares to create (at least k).secret(byte string) — a byte string of 16 bytes (e.g. an AES 128 key).ssss(bool) — ifTrue, the shares can be used with thessssutility (without using the "diffusion layer"). Default:False.
Return (tuples): n tuples, one per participant. A tuple contains two items:
- the unique index (an integer)
- the share (16 bytes)
🌈 Diffie-Hellman Key Agreement 🌈
Diffie-Hellman (DH) is a key agreement scheme based on asymmetric cryptography. Highkey one of the most iconic collabs in all of computing. 🤝
It enables two parties (below referred to as U and V) to jointly compute a shared secret across an untrusted communication channel, without explicitly transmitting the secret itself or parts of it. Literally not a single crumb of the secret leaves the group chat. 💅
In other words, an adversary can monitor the channel, see all exchanged messages, and still learn nothing about the shared secret. Not them being completely locked out of the info. 💀
The shared secret is not used directly. The two parties will derive symmetric keys from it, and use those to encrypt and authenticate subsequent messages.
(the original page has a lil diagram right about here — picture U and V vibing across the untrusted channel 🦋)
NOTE: The Diffie-Hellman protocol is fairly generic and it can be implemented in many ways.
Okay so this Python module implements Diffie-Hellman according to Chapter 6 of NIST SP 800-56A Rev. 3, and only for elliptic curve cryptography (ECC CDH). Like, ECC only, bestie. No cap. 💅
✨ Examples
First up, a minimal example where only static keys are used — literally the simplest vibe:
from Crypto.PublicKey import ECC
from Crypto.Hash import SHAKE128
from Crypto.Protocol.DH import key_agreement
# This KDF has been agreed in advance
def kdf(x):
return SHAKE128.new(x).read(32)
# In a real scenario, this key already exists
U = ECC.generate(curve='p256')
# In a real scenario, this key is received from the peer
# and it is verified as authentic
V = ECC.generate(curve='p256').public_key()
session_key = key_agreement(static_priv=U, static_pub=V, kdf=kdf)
# session_key is an AES-256 key, which will be used to encrypt
# subsequent communications
NIST SP 800-56A recommends to bind the key agreement to some information concerning the context of the operation — for instance a description of what the keys are used for. Context is everything, bestie 🎀:
from Crypto.Hash import TupleHash128
from Crypto.Protocol.DH import key_agreement
# Random value (e.g., a nonce)
session_salt = b'XYZ'
# The unique identifier of each party
id_u = 'Client 12'
id_v = 'Server 25'
def kdf(x):
h = TupleHash128.new(digest_bytes=32)
h.update(x,
session_salt,
id_u.encode(), # To UTF-8
id_v.encode(), # To UTF-8
b'Email encryption',
b'TupleHash128',
b'AES256')
return h.digest()
# In a real scenario, this key already exists
U = ECC.generate(curve='p256')
# In a real scenario, this key is received from the peer
# and it is verified as authentic
V = ECC.generate(curve='p256').public_key()
key = key_agreement(static_priv=U, static_pub=V, kdf=kdf)
The examples above all used static keys only. In order to achieve Perfect Forward Secrecy, it is recommended that at least one party (but ideally both) contributes to the key agreement with one ephemeral key — that is, one key pair generated for the session, and destroyed at the end. Iconic behavior, honestly ✨:
from Crypto.PublicKey import ECC
from Crypto.Hash import SHAKE128
from Crypto.Protocol.DH import key_agreement
# This KDF has been agreed in advance
def kdf(x):
return SHAKE128.new(x).read(32)
# In a real scenario, this long-term key already exists
U_static = ECC.generate(curve='p256')
# This ephemeral key is generated only for this session
U_ephemeral = ECC.generate(curve='p256')
# In a real scenario, this long-term key is received from the peer
# and it is verified as authentic
V_static = ECC.generate(curve='p256').public_key()
# In a real scenario, the peer generated this ephemeral key only
# for this session. It doesn't need to be authenticated if the
# static key of V already has been.
V_ephemeral = ECC.generate(curve='p256').public_key()
session_key = key_agreement(static_priv=U_static,
static_pub=V_static,
eph_priv=U_ephemeral,
eph_pub=V_ephemeral,
kdf=kdf)
# session_key is an AES-256 key, which will be used to encrypt
# subsequent communications
📜 Specification
Crypto.Protocol.DH.key_agreement(**kwargs) — Perform a Diffie-Hellman key agreement. Slay.
Keywords — here's the whole cast 👑:
kdf(callable) — A key derivation function that accepts bytes as input and returns bytes.static_priv(EccKey) — The local static private key. Optional.static_pub(EccKey) — The static public key that belongs to the peer. Optional.eph_priv(EccKey) — The local ephemeral private key, generated for this session. Optional.eph_pub(EccKey) — The ephemeral public key, received from the peer for this session. Optional.
At least two keys must be passed, of which one is a private key and one a public key. Like, minimum two, and they gotta be one priv + one pub.
Returns (bytes): The derived secret key material. 💖
Crypto.Protocol.DH.import_x25519_public_key(encoded) — Create a new X25519 public key object, starting from the key encoded as raw bytes, in the format described in RFC7748.
- Parameters:
encoded(bytes) — The x25519 public key to import. It must be 32 bytes. - Returns: a new ECC key object.
- Return type:
Crypto.PublicKey.EccKey - Raises
ValueError— when the given key cannot be parsed.
Crypto.Protocol.DH.import_x25519_private_key(encoded) — Create a new X25519 private key object, starting from the key encoded as raw bytes, in the format described in RFC7748.
- Parameters:
encoded(bytes) — The X25519 private key to import. It must be 32 bytes. - Returns: a new ECC key object.
- Return type:
Crypto.PublicKey.EccKey - Raises
ValueError— when the given key cannot be parsed.
Crypto.Protocol.DH.import_x448_public_key(encoded) — Create a new X448 public key object, starting from the key encoded as raw bytes, in the format described in RFC7748.
- Parameters:
encoded(bytes) — The x448 public key to import. It must be 56 bytes. - Returns: a new ECC key object.
- Return type:
Crypto.PublicKey.EccKey - Raises
ValueError— when the given key cannot be parsed.
Crypto.Protocol.DH.import_x448_private_key(encoded) — Create a new X448 private key object, starting from the key encoded as raw bytes, in the format described in RFC7748.
- Parameters:
encoded(bytes) — The X448 private key to import. It must be 56 bytes. - Returns: a new ECC key object.
- Return type:
Crypto.PublicKey.EccKey - Raises
ValueError— when the given key cannot be parsed.
🔑 Key Derivation Function
The kdf parameter must be a cryptographically secure key derivation function, not password-based. For instance, some reasonable choices are:
- HKDF
- SHAKE128 or SHAKE256
- SP 800-108 Counter Mode
- TupleHash128 or TupleHash256
KDFs are typically fed with multiple inputs, whereas the kdf function must take exactly one (of type bytes). Therefore, a temporary function kdf should be constructed by fixing some of the inputs of the underlying KDF. For example using functools:
from Crypto.Protocol.KDF import HKDF
from Crypto.Hash import SHA256
import functools
kdf = functoools.partial(HKDF,
key_len=32,
salt=b'nonce',
hashmod=SHA256,
num_keys=2,
context=b'Some context about the operation')
# Pass kdf to key_agreement()
Yes bestie, that really says functoools in the original — copy-paste at your own risk, or fix it in your own code, your call. 💀
In other cases, defining a local function can be sufficient:
import os
from Crypto.Hash import SHAKE128
# A non-secret value generated for the session
nonce = os.urandom(16)
def kdf(x):
return SHAKE128.new(x + nonce).read(32)
# Pass kdf to key_agreement()
💡 Recommendations
This module only provides the fundamental low-level primitive of the Diffie-Hellman key agreement. When designing the protocol, users should ensure that:
- Each party is certain that the public key of the peer is authentic, and that the risk of Man-in-the-Middle attacks is considered.
- The requirement for Perfect Forward Secrecy is understood and, if necessary, ephemeral keys are generated, used in the key agreement, and then destroyed.
- The risk of Key compromise impersonation is understood. With that attack, an adversary that compromises our static key, can impersonate any other user to us. The use of ephemeral keys addresses this risk too.
- Some form of Key Confirmation is included after the key agreement (e.g., with a MAC), to immediately detect if the shared secret has not being correctly calculated.
🦋 Hybrid Public Key Encryption (HPKE)
HPKE (RFC 9180) is a cryptographic protocol that enables secure communication using a combination of public key (ECC Diffie-Hellman) and symmetric encryption (AEAD) techniques. It's giving main character energy for real 🫶.
It allows a sender that knows the public key of the intended recipient, to encrypt one or more messages ensuring confidentiality and integrity but without requiring an online key exchange.
There are no significant limits on the size of one message or on the number of messages in the sequence. Along with each encrypted message (ciphertext), the sender can also deliver a piece of non-sensitive data (e.g., a header), which will be authenticated together with the matching message (authenticated associated data, AAD).
Before, or together with the first message, the sender must deliver the encapsulated session secret (enc) to the receiver.
[image]
In the basic scheme above, the receiver does not receive any proof of identity from the sender. Variants of the protocol exist to enable the receiver to ascertain the origin of the message.
First (Auth mode), the sender can contribute its own private key to the encryption. The receiver must have the matching public key:
[image]
Second (PSK mode), the sender and the receiver can agree on the same secret key (at least 32 random bytes), and contribute that to both encryption and decryption.
[image]
Finally, the two authentication modes can also be combined together (AuthPSK mode). We love a combo ✨.
NOTE: The PSK is only used to authenticate the messages; it is not a replacement for the asymmetric key that the receiver must provide to the sender. Do not skip the asymmetric key, bestie — it's not that kind of party. 🙈
🌸 Examples
This is how the sender can encrypt two messages:
from Crypto.Protocol import HPKE
# Let's assume that the recipient delivered its public key
# which is already loaded in a variable called their_pub_key
#
# For the sake of clarity, it could be simulated with:
#
# from Crypto.PublicKey import ECC
# their_key = ECC.generate(curve='p256')
# their_pub_key = their_key.public_key()
encryptor = HPKE.new(receiver_key=their_pub_key,
aead_id=HPKE.AEAD.AES128_GCM)
ct_1 = encryptor.seal(b'Message 1')
ct_2 = encryptor.seal(b'Message 2')
# The sender will deliver:
# - encryptor.enc as enc
# - ct_1
# - ct_2
And bestie, this is literally how the receiver side decrypts them 💖
from Crypto.Protocol import HPKE
# Let's assume that the recipient has its own private key
# which is already loaded in a variable called my_key
#
# To continue the simulation, assign:
#
# my_key = their_key
decryptor = HPKE.new(receiver_key=my_key,
aead_id=HPKE.AEAD.AES128_GCM,
enc=enc)
# Any of the calls to unseal() can raise ValueError
# if a key mismatch, modification to a message, or reordering is detected
pt1 = decryptor.unseal(ct_1)
pt2 = decryptor.unseal(ct_2)
✨ Specification ✨
class Crypto.Protocol.HPKE.AEAD(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)
Authenticated Encryption with Associated Data (AEAD) Functions 🎀 — yes, that's the whole acronym, and yes it ate.
AES128_GCM = 1AES256_GCM = 2CHACHA20_POLY1305 = 3
Crypto.Protocol.HPKE.new(*, receiver_key, aead_id, enc=None, sender_key=None, psk=None, info=None)
Create an HPKE context which can be used:
- by the sender to seal (encrypt) a message or
- by the receiver to unseal (decrypt) it.
As a minimum, the two parties agree on the receiver's asymmetric key (of which the sender will only know the public half). 🤝
Additionally, for authentication purposes, they may also agree on:
- the sender's asymmetric key (of which the receiver will only know the public half)
- a shared secret (e.g., a symmetric key derived from a password)
🌸 Parameters 🌸
receiver_key(EccKey) — The ECC key of the receiver. It must be on one of the following curves: NIST P-256, NIST P-384, NIST P-521, X25519 or X448.
If this is a public key, the HPKE context can only be used to seal (encrypt).
If this is a private key, the HPKE context can only be used to unseal (decrypt).
aead_id(AEAD) — The HPKE identifier of the symmetric cipher. The possible values are:HPKE.AEAD.AES128_GCMHPKE.AEAD.AES256_GCMHPKE.AEAD.CHACHA20_POLY1305
enc(Optional[bytes]) — The encapsulated session key (i.e., the KEM shared secret).
The receiver must always specify this parameter.
The sender must always omit this parameter.
sender_key(Optional[EccKey]) — The ECC key of the sender. It must be on the same curve as thereceiver_key. If thereceiver_keyis a public key,sender_keymust be a private key, and vice versa.psk(Optional[tuple[bytes, bytes]]) — A Pre-Shared Key (PSK) as a 2-tuple of non-empty byte strings: the identifier and the actual secret value. Sender and receiver must use the same PSK (or none).
The secret value must be at least 32 bytes long, but it must not be a low-entropy password (use a KDF like PBKDF2 or scrypt to derive a secret from a password). 🔐
info(Optional[bytes]) — A non-secret parameter that contributes to the generation of all session keys. Sender and receive must use the sameinfoparameter (or none).
Return type: HPKE_Cipher
Returns: An object that can be used for sealing (if receiver_key is a public key) or unsealing (if receiver_key is a private key). In the latter case, correctness of all the keys and parameters will only be assessed with the first call to unseal(). 🫶
class Crypto.Protocol.HPKE.HPKE_Cipher(receiver_key, enc, sender_key, psk_pair, info, aead_id, mode)
enc: bytes— The encapsulated session key.seal(plaintext, auth_data=None)— Encrypt and authenticate a message.
This method can be invoked multiple times to seal an ordered sequence of messages. ✨
Parameters
plaintext(bytes) — bytes The message to seal.auth_data(Optional[bytes]) — bytes Optional. Additional Authenticated data (AAD) that is not encrypted but that will be also covered by the authentication tag.
Returns: The ciphertext concatenated with the authentication tag. 💅
unseal(ciphertext, auth_data=None)— Decrypt a message and validate its authenticity.
This method can be invoked multiple times to unseal an ordered sequence of messages.
Parameters
cipertext— bytes The message to unseal.auth_data(Optional[bytes]) — bytes Optional. Additional Authenticated data (AAD) that was also covered by the authentication tag.
Returns: The original plaintext. 🌈
Raises: ValueError — If the ciphertext (in combination with the AAD) is not valid.
But if it is the first time you call unseal() this exception may also mean that any of the parameters or keys used to establish the session is wrong or that one is missing. 😭 (yeah, slightly rude of it, but that's the deal)
- Key Derivation Functions
- Secret Sharing Schemes
- Diffie-Hellman Key Agreement
- Hybrid Public Key Encryption (HPKE)
📦 Crypto.IO package 📦
Modules for reading and writing cryptographic data.
- PEM
- PKCS#8
🦋 PEM 🦋
Set of functions for encapsulating data according to the PEM format.
PEM (Privacy Enhanced Mail) was an IETF standard for securing emails via a Public Key Infrastructure. It is specified in RFC 1421-1424.
Even though it has been abandoned, the simple message encapsulation it defined is still widely used today for encoding binary cryptographic objects like keys and certificates into text. Iconic behaviour honestly 👑
Crypto.IO.PEM.decode(pem_data, passphrase=None)
Decode a PEM block into binary.
Parameters
pem_data(string) — The PEM block.passphrase(byte string) — If given and the PEM block is encrypted, the key will be derived from the passphrase.
Returns: A tuple with the binary data, the marker string, and a boolean to indicate if decryption was performed. 🎀
Raises ValueError — if decoding fails, if the PEM file is encrypted and no passphrase has been provided or if the passphrase is incorrect.
Crypto.IO.PEM.encode(data, marker, passphrase=None, randfunc=None)
Encode a piece of binary data into PEM format.
Parameters
data(byte string) — The piece of binary data to encode.marker(string) — The marker for the PEM block (e.g."PUBLIC KEY"). Note that there is no official master list for all allowed markers. Still, you can refer to the OpenSSL source code.passphrase(byte string) — If given, the PEM block will be encrypted. The key is derived from the passphrase.randfunc(callable) — Random number generation function; it accepts an integer N and returns a byte string of random data, N bytes long. If not given, a new one is instantiated.
Returns: The PEM block, as a string. 💖
🔑 PKCS#8 🔑
PKCS#8 is a standard for encoding asymmetric private keys, such as RSA or ECC, so that they can be stored or exchanged. The private key can either be encrypted with a passphrase or left in the clear. 🙈
Example of how to encrypt an ECC private key (even though normally you would use the export_key method of the key itself):
from Crypto.PublicKey import ECC
from Crypto.IO import PKCS8
key = ECC.generate(curve='p256')
pkey = key.export_key(format='DER'),
passphrase = b'secret santa'
encrypted_key = PKCS8.wrap(
pkey,
"1.2.840.10045.2.1", # unrestricted ECC
passphrase=passphrase,
protection='PBKDF2WithHMAC-SHA512AndAES256-CBC',
prot_params={'iteration_count': 210000}
)
💜 Encryption parameters 💜
When creating an encrypted PKCS#8 container, the two parameters protection and prot_params drive the encryption algorithm:
protection(mandatory), a string that defines how the encryption key is derived from the passphrase, and which cipher to use. The string must follow one of the two patterns:'PBKDF2WithHMAC-'+ hash +'And'+ cipher'scryptAnd'+ cipher
where hash is the name of the cryptographic hash (recommended: 'SHA512') and cipher is the name of the cipher mode to use (recommended: 'AES256-CBC').
Other values for hash are 'SHA1', 'SHA224', 'SHA256', 'SHA384', 'SHA512-224', 'SHA512-256', 'SHA3-224', 'SHA3-256', 'SHA3-384', 'SHA3-512'.
Other values for cipher are 'AES128-GCM', 'AES192-GCM', 'AES256-GCM', 'AES128-CBC', 'AES192-CBC' or 'DES-EDE3-CBC'.
prot_params(optional) — a dict that overrides the settings of the key derivation function (KDF for short, iykyk). Here's every knob you can turn:iteration_count— the KDF gets run over and over on purpose, so brute-forcing a password takes forever (this is what they call N, or the CPU/memory cost in scrypt). For PBKDF2 with SHA512 the recommended value is210 000(default is1 000). For scrypt the recommended value is131 072(default value is16 384). Bestie, the defaults are cute but the recommended ones are actually doing the work 💪salt_size— salt exists to clown on dictionary and rainbow attacks against passwords. Default value is8bytes.block_size— (scrypt only) the memory-cost, aka r. Default value is8.parallelization— (scrypt only) the CPU-cost, aka p. Default value is1.
🧓 Legacy algorithms
Okay so these protection mechanisms are only supported for importing keys. They are much weaker than the ones listed above, and they're provided for backward compatibility only. Like, only only. Do not pick these for anything new, bestie, I'm begging 🙈
pbeWithMD5AndRC2-CBCpbeWithMD5AndDES-CBCpbeWithSHA1AndRC2-CBCpbeWithSHA1AndDES-CBC
Crypto.IO.PKCS8.unwrap(p8_private_key, passphrase=None)
Unwraps a private key out of a PKCS#8 blob — and yeah, it handles both the clear ones and the encrypted ones 💅
Parameters
p8_private_key(bytes) — the private key wrapped up inside a PKCS#8 container, DER encoded.
Keyword Arguments
passphrase(byte string or string) — the passphrase you use to decrypt the blob (only if it's encrypted, obviously).
Returns
A tuple containing:
- the algorithm identifier of the wrapped key (an OID, as a dotted string)
- the private key (bytes, DER encoded)
- the associated parameters (bytes, DER encoded) or
None
Raises ValueError — if decoding fails. Sad times 😭
Crypto.IO.PKCS8.wrap(private_key, key_oid, passphrase=None, protection=None, prot_params=None, key_params=<Crypto.Util.asn1.DerNull object>, randfunc=None)
Wraps a private key into a PKCS#8 blob (clear or encrypted). The reverse glow-up of unwrap ✨
Parameters
private_key(bytes) — the private key encoded in binary form. The actual encoding is algorithm specific. In most cases, it's DER.key_oid(string) — the object identifier (OID) of the private key to wrap. It's a dotted string, like'1.2.840.113549.1.1.1'(for RSA keys) or'1.2.840.10045.2.1'(for ECC keys). Slay, numbers 🎀
Keyword Arguments
passphrase(bytes or string) — the secret passphrase the wrapping key gets derived from. Set it only if encryption is required, k?protection(string) — the identifier of the algorithm used to securely wrap the key. Refer to the encryption parameters. The default value is'PBKDF2WithHMAC-SHA1AndDES-EDE3-CBC'.prot_params(dictionary) — parameters for the key derivation function (KDF). Refer to the encryption parameters.key_params(DER object or None) — the parameters field to use in theAlgorithmIdentifierSEQUENCE. IfNone, no parameters field will be added. By default, the ASN.1 typeNULLis used.randfunc(callable) — random number generation function; it should accept a single integer N and return a string of random data, N bytes long. If not specified, a new RNG will be instantiated fromCrypto.Random. Vibes randomised 🎲
Returns — the PKCS#8-wrapped private key (possibly encrypted).
Return type — bytes
🎲 Crypto.Random package
Crypto.Random.get_random_bytes(N)
Returns a random byte string of length N. That's it. That's the whole function. Iconic minimalism 💜
🎲 Crypto.Random.random module
Crypto.Random.random.getrandbits(N)
Returns a random integer, at most N bits long.
Crypto.Random.random.randrange([start], stop[, step])
Returns a random integer in the range (start, stop, step). By default, start is 0 and step is 1.
Crypto.Random.random.randint(a, b)
Returns a random integer in the range no smaller than a and no larger than b.
Crypto.Random.random.choice(seq)
Returns a random element picked from the sequence seq.
Crypto.Random.random.shuffle(seq)
Randomly shuffles the sequence seq in-place. Heads up: in-place means your original list is changed — no take-backs 🔀
Crypto.Random.random.sample(population, k)
Randomly chooses k distinct elements from the list population.
🧰 Crypto.Util package
Useful modules that don't belong in any other package. The miscellaneous drawer of the library, and honestly? Every good codebase needs one 🫶
🧬 Crypto.Util.asn1 module
This module provides minimal support for encoding and decoding ASN.1 DER objects. Minimal is the key word here — it does what it says, no more, no cap.
class Crypto.Util.asn1.DerBitString(value=b'', implicit=None, explicit=None)
Class to model a DER BIT STRING. Bestie, it's literally a string of bits, what more do you want 💀
An example of encoding is:
>>> from Crypto.Util.asn1 import DerBitString
>>> bs_der = DerBitString(b'\xAA')
>>> bs_der.value += b'\xBB'
>>> print(bs_der.encode().hex())
which will show 030300aabb, the DER encoding for the bit string b'\xAA\xBB'.
For decoding:
>>> s = bytes.fromhex('030300aabb')
>>> try:
>>> bs_der = DerBitString()
>>> bs_der.decode(s)
>>> print(bs_der.value.hex())
>>> except ValueError:
>>> print "Not a valid DER BIT STRING"
the output will be aabb.
Variables
value(byte string) — the content of the string
decode(der_encoded, strict=False)
Decodes a complete DER BIT STRING, and re-initializes this object with it.
Parameters:
der_encoded(byte string) — a complete DER BIT STRING.strict(boolean) — whether decoding must check for strict DER compliancy.
Raises ValueError — in case of parsing errors.
encode()
Returns the DER BIT STRING, fully encoded as a byte string.
class Crypto.Util.asn1.DerBoolean(value=False, implicit=None, explicit=None)
Class to model a DER-encoded BOOLEAN. True or false, that's the whole personality 🦋
An example of encoding is:
>>> from Crypto.Util.asn1 import DerBoolean
>>> bool_der = DerBoolean(True)
>>> print(bool_der.encode().hex())
which will show 0101ff, the DER encoding of True.
And for decoding:
>>> s = bytes.fromhex('0101ff')
>>> try:
>>> bool_der = DerBoolean()
>>> bool_der.decode(s)
>>> print(bool_der.value)
>>> except ValueError:
>>> print "Not a valid DER BOOLEAN"
the output will be True.
Variables
value(boolean) — the boolean value
decode(der_encoded, strict=False)
Decodes a DER-encoded BOOLEAN, and re-initializes this object with it.
Parameters:
der_encoded(byte string) — a DER-encoded BOOLEAN.
Raises ValueError — in case of parsing errors.
encode()
Returns the DER BOOLEAN, fully encoded as a binary string.
class Crypto.Util.asn1.DerInteger(value=0, implicit=None, explicit=None)
Class to model a DER INTEGER. Numbers but make it ASN.1 🔢
An example of encoding is:
>>> from Crypto.Util.asn1 import DerInteger
>>> from binascii import hexlify, unhexlify
>>> int_der = DerInteger(9)
>>> print hexlify(int_der.encode())
which will show 020109, the DER encoding of 9.
And for decoding:
>>> s = unhexlify(b'020109')
>>> try:
>>> int_der = DerInteger()
>>> int_der.decode(s)
>>> print int_der.value
>>> except ValueError:
>>> print "Not a valid DER INTEGER"
the output will be 9.
Variables
value(integer) — the integer value
decode(der_encoded, strict=False)
Decodes a DER-encoded INTEGER, and re-initializes this object with it.
Parameters:
der_encoded(byte string) — a complete INTEGER DER element.
Raises ValueError — in case of parsing errors.
encode()
Returns the DER INTEGER, fully encoded as a binary string.
class Crypto.Util.asn1.DerNull
Class to model a DER NULL element. The main character of "nothing to see here" — and honestly, sometimes nothing is exactly what you need 💅
💅 class Crypto.Util.asn1.DerObject(asn1Id=None, payload=b'', implicit=None, constructed=False, explicit=None)
Base class for defining a single DER object. It’s giving “blueprint only,” bestie.
🚨 This class should never be directly instantiated. Like, the page literally says never. Respect the boundary.
decode(der_encoded, strict=False) — Decodes a complete DER element and re-initializes this object with it. The signature also has strict=False, but the page only spells out der_encoded under parameters here.
Parameters
der_encoded(byte string) — A complete DER element.
Raises ValueError — in case of parsing errors.
encode() — Return this DER element, fully encoded as a binary byte string.
💖 class Crypto.Util.asn1.DerObjectId(value='', implicit=None, explicit=None)
Class to model a DER OBJECT ID. OID girlies, rise 🫶
An example of encoding is:
>>> from Crypto.Util.asn1 import DerObjectId
>>> from binascii import hexlify, unhexlify
>>> oid_der = DerObjectId("1.2")
>>> oid_der.value += ".840.113549.1.1.1"
>>> print hexlify(oid_der.encode())
which will show 06092a864886f70d010101, the DER encoding for the RSA Object Identifier 1.2.840.113549.1.1.1.
For decoding:
>>> s = unhexlify(b'06092a864886f70d010101')
>>> try:
>>> oid_der = DerObjectId()
>>> oid_der.decode(s)
>>> print oid_der.value
>>> except ValueError:
>>> print "Not a valid DER OBJECT ID"
the output will be 1.2.840.113549.1.1.1.
Variables
value(string) — The Object ID (OID), a dot separated list of integers
decode(der_encoded, strict=False) — Decode a complete DER OBJECT ID, and re-initializes this object with it.
Parameters
der_encoded(byte string) — A complete DER OBJECT ID.strict(boolean) — Whether decoding must check for strict DER compliancy.
Raises ValueError — in case of parsing errors.
encode() — Return the DER OBJECT ID, fully encoded as a binary string.
🎀 class Crypto.Util.asn1.DerOctetString(value=b'', implicit=None)
Class to model a DER OCTET STRING. Byte-string behavior, honestly iconic.
An example of encoding is:
>>> from Crypto.Util.asn1 import DerOctetString
>>> from binascii import hexlify, unhexlify
>>> os_der = DerOctetString(b'\xaa')
>>> os_der.payload += b'\xbb'
>>> print hexlify(os_der.encode())
which will show 0402aabb, the DER encoding for the byte string b'\xAA\xBB'.
For decoding:
>>> s = unhexlify(b'0402aabb')
>>> try:
>>> os_der = DerOctetString()
>>> os_der.decode(s)
>>> print hexlify(os_der.payload)
>>> except ValueError:
>>> print "Not a valid DER OCTET STRING"
the output will be aabb.
Variables
payload(byte string) — The content of the string
🦋 class Crypto.Util.asn1.DerSequence(startSeq=None, implicit=None, explicit=None)
Class to model a DER SEQUENCE. This object behaves like a dynamic Python sequence — yes, it’s got main character energy.
Sub-elements that are INTEGERs behave like Python integers.
Any other sub-element is a binary string encoded as a complete DER sub-element (TLV).
An example of encoding is:
>>> from Crypto.Util.asn1 import DerSequence, DerInteger
>>> from binascii import hexlify, unhexlify
>>> obj_der = unhexlify('070102')
>>> seq_der = DerSequence([4])
>>> seq_der.append(9)
>>> seq_der.append(obj_der.encode())
>>> print hexlify(seq_der.encode())
which will show 3009020104020109070102, the DER encoding of the sequence containing 4, 9, and the object with payload 02.
For decoding:
>>> s = unhexlify(b'3009020104020109070102')
>>> try:
>>> seq_der = DerSequence()
>>> seq_der.decode(s)
>>> print len(seq_der)
>>> print seq_der[0]
>>> print seq_der[:]
>>> except ValueError:
>>> print "Not a valid DER SEQUENCE"
the output will be:
3
4
[4, 9, b'.....']
decode(der_encoded, strict=False, nr_elements=None, only_ints_expected=False) — Decode a complete DER SEQUENCE, and re-initializes this object with it.
Parameters
der_encoded(byte string) — A complete SEQUENCE DER element.nr_elements(None or integer or list of integers) — The number of members the SEQUENCE can haveonly_ints_expected(boolean) — Whether the SEQUENCE is expected to contain only integers.strict(boolean) — Whether decoding must check for strict DER compliancy.
Raises ValueError — in case of parsing errors.
DER INTEGERs are decoded into Python integers. Any other DER element is not decoded. Its validity is not checked.
encode() — Return this DER SEQUENCE, fully encoded as a binary string.
Raises ValueError — if some elements in the sequence are neither integers nor byte strings.
hasInts(only_non_negative=True) — Return the number of items in this sequence that are integers.
Parameters
only_non_negative(boolean) — IfTrue, negative integers are not counted in.
hasOnlyInts(only_non_negative=True) — Return True if all items in this sequence are integers or non-negative integers.
This function returns False if the sequence is empty, or if at least one member is not an integer.
Parameters
only_non_negative(boolean) — IfTrue, the presence of negative integers causes the method to returnFalse.
🌈 class Crypto.Util.asn1.DerSetOf(startSet=None, implicit=None)
Class to model a DER SET OF. Set-of vibes only ✨
An example of encoding is:
>>> from Crypto.Util.asn1 import DerBitString
>>> from binascii import hexlify, unhexlify
>>> so_der = DerSetOf([4,5])
>>> so_der.add(6)
>>> print hexlify(so_der.encode())
which will show 3109020104020105020106, the DER encoding of a SET OF with items 4,5, and 6.
For decoding:
>>> s = unhexlify(b'3109020104020105020106')
>>> try:
>>> so_der = DerSetOf()
>>> so_der.decode(s)
>>> print [x for x in so_der]
>>> except ValueError:
>>> print "Not a valid DER SET OF"
the output will be [4, 5, 6].
add(elem) — Add an element to the set.
Parameters
elem(byte string or integer) — An element of the same type of objects already in the set. It can be an integer or a DER encoded object.
decode(der_encoded, strict=False) — Decode a complete SET OF DER element, and re-initializes this object with it.
DER INTEGERs are decoded into Python integers. Any other DER element is left undecoded; its validity is not checked.
Parameters
der_encoded(byte string) — a complete DER BIT SET OF.strict(boolean) — Whether decoding must check for strict DER compliancy.
Raises ValueError — in case of parsing errors.
encode() — Return this SET OF DER element, fully encoded as a binary string.
🧼 Crypto.Util.Padding module
This module provides minimal support for adding and removing standard padding from data. Example:
>>> from Crypto.Util.Padding import pad, unpad
>>> from Crypto.Cipher import AES
>>> from Crypto.Random import get_random_bytes
>>>
>>> data = b'Unaligned' # 9 bytes
>>> key = get_random_bytes(32)
>>> iv = get_random_bytes(16)
>>>
>>> cipher1 = AES.new(key, AES.MODE_CBC, iv)
>>> ct = cipher1.encrypt(pad(data, 16))
>>>
>>> cipher2 = AES.new(key, AES.MODE_CBC, iv)
>>> pt = unpad(cipher2.decrypt(ct), 16)
>>> assert(data == pt)
🧴 Crypto.Util.Padding.pad(data_to_pad, block_size, style='pkcs7')
Apply standard padding. Padding bestie, doing exactly what it says 💅
Parameters
data_to_pad(byte string) — The data that needs to be padded.block_size(integer) — The block boundary to use for padding. The output length is guaranteed to be a multiple ofblock_size.style(string) — Padding algorithm. It can be'pkcs7'(default),'iso7816'or'x923'.
Returns the original data with the appropriate padding added at the end.
Return type byte string
🫧 Crypto.Util.Padding.unpad(padded_data, block_size, style='pkcs7')
Remove standard padding. Stripping it back down, no cap.
Parameters
padded_data(byte string) — A piece of data with padding that needs to be stripped.block_size(integer) — The block boundary to use for padding. The input length must be a multiple ofblock_size.style(string) — Padding algorithm. It can be'pkcs7'(default),'iso7816'or'x923'.
Returns data without padding.
Return type byte string
Raises ValueError — if the padding is incorrect.
🎀 Crypto.Util.RFC1751 module
🔑 Crypto.Util.RFC1751.english_to_key(s)
Transform a string into a corresponding key. Words to key, literally.
Example:
>>> from Crypto.Util.RFC1751 import english_to_key
>>> english_to_key('RAM LOIS GOAD CREW CARE HIT')
b'66666666'
That s is a string where the words are separated by whitespace, and the word count has to be a multiple of 6. Not 5, not 7 — six or nothing, bestie. 🎀
Returns
A byte string. That's the whole vibe.
Crypto.Util.RFC1751.key_to_english(key)
Okay so this one flips the whole thing around — you hand it any old key and it gives you back a string stuffed with English words. Going the other direction, iconic of it honestly. ✨
Example:
>>> from Crypto.Util.RFC1751 import key_to_english
>>> key_to_english(b'66666666')
'RAM LOIS GOAD CREW CARE HIT'
Parameters
key(byte string) — the key you wanna convert. Heads up: its length must be a multiple of 8. No exceptions, no wiggle room. 💅
Returns
A string of English words. Cute AND functional, we love that for her. 🌸
Crypto.Util.strxor module
Fast XOR for byte strings. Literally what it says. Zoom zoom. 🔥
Crypto.Util.strxor.strxor(term1, term2, output=None)
Take two byte strings of equal length and make a third one out of them — the byte-by-byte XOR of the two. It's giving math but make it fashion. 💜
Parameters
term1(bytes/bytearray/memoryview) — the first byte string to XOR.term2(bytes/bytearray/memoryview) — the second byte string to XOR.output(bytearray/memoryview) — the spot where the result gets written. It must have the same length asterm1andterm2. If it'sNone, the result is returned instead.
Return — if output is None, you get a fresh new byte string with the result. Otherwise you get None. (Because it already went into output, bestie, keep up 😌)
NOTE:term1andterm2must have the same length. This is not optional. Do not test her. 😭
Crypto.Util.strxor.strxor_c(term, c, output=None)
Same vibes, different shape: take a byte string and build a second one of equal length where every single byte got XOR-red with the same value. One value, all the bytes, no cap. ⭐
Parameters
term(bytes/bytearray/memoryview) — the byte string to XOR.c(int) — every byte in the string gets XOR-ed with this value. It must be between 0 and 255, both included. Stay in range, queen. 👑output(None or bytearray/memoryview) — where the result goes. It must have the same length asterm. IfNone, the result is returned.
Returns
If output is None, a new bytes string with the result. Otherwise None. 🫶
Crypto.Util.Counter module
The Crypto.Util.Counter module provides the functionality to create more complex counter blocks for the CTR cipher mode. Yes, more complex. She's an overachiever and honestly? Obsessed. 💅✨
Introduction to counter blocks
CTR (Counter) is a mode of operation for block ciphers. In CTR mode, the ciphertext is generated by dividing the plaintext into blocks and then XOR-ing each block with a unique keystream. 🦋
That keystream comes from encrypting a sequence of counter blocks, and every single one of them must be distinct — otherwise you get repetitions in the keystream and that is not the move, okay? Counter blocks themselves don't need to be kept secret. Zero secrecy required. The encryption part is done using ECB.
The most straightforward approach to creating counter blocks is to include a counter field, and increment it by one within each subsequent counter block. Simple. Clean. Ate. 🎀
Creating counter blocks without Counter
The new() function, at the module level under Crypto.Cipher, lets you create a new CTR cipher object for a specific base algorithm. It hands you parameters for defining the structure of the counter block:
- an optional, fixed prefix
- the counter field encoded in big endian mode
The combined length of these two components must match the block size of the algorithm being used. For example, in the case of AES, the block size is typically 16 bytes. Math is mathing. 🔥
Creating counter blocks with Counter
If you need a more complex structure for the counter block (and honestly, same), you can define it ahead of time with Crypto.Util.Counter.new(), then pass it to new() of the cipher using the counter parameter. The counter block can then have:
- an optional, fixed prefix
- the counter field, encoded in big endian or little endian mode
- an optional, fixed suffix
As before, the total length must match the block size.
The counter blocks with a big endian counter will look like this:
[image]
The counter blocks with a little endian counter will look like this:
[image]
This is an example of AES-CTR encryption with a custom counter:
from Crypto.Cipher import AES
from Crypto.Util import Counter
from Crypto import Random
# The counter block must be
#
# +--------+----------------+--------+
# | nonce + counter + EFGH +
# +--------+----------------+--------+
# 4 bytes 8 bytes 4 bytes
nonce = Random.get_random_bytes(4)
ctr = Counter.new(64, prefix=nonce, suffix=b'EFGH', little_endian=True, initial_value=10)
key = b'AES-128 symm key'
plaintext = b'X'*1000000
cipher = AES.new(key, AES.MODE_CTR, counter=ctr)
ciphertext = cipher.encrypt(plaintext)
Look at her go. A million X's, encrypted. Main character energy fr fr. 🌈
Crypto.Util.Counter.new(nbits, prefix=b'', suffix=b'', initial_value=1, little_endian=False, allow_wraparound=False)
Create a stateful counter block function suitable for CTR encryption modes. ⭐
Each call to the function returns the next counter block. Each counter block is made up of three parts:
┌────────┬───────────────┬─────────┐
│ prefix │ counter value │ postfix │
└────────┴───────────────┴─────────┘
The counter value is incremented by 1 at each call. Every time. Like clockwork. 💜
Parameters
nbits(integer) — length of the desired counter value, in bits. It must be a multiple of 8.prefix(byte string) — the constant prefix of the counter block. By default, no prefix is used.suffix(byte string) — the constant postfix of the counter block. By default, no suffix is used.initial_value(integer) — the initial value of the counter. Default value is1. Its length in bits must not exceed the argumentnbits. (Don't go over, bestie 🙈)little_endian(boolean) — ifTrue, the counter number gets encoded in little endian format. IfFalse(the default), in big endian format.allow_wraparound(boolean) — okay so this parameter is ignored. Like, fully. AnOverflowErrorexception is always raised when the counter wraps around to zero. Non-negotiable.
Returns
An object that you can pass with the counter parameter to a CTR mode cipher. 🫶
It must hold that len(prefix) + nbits//8 + len(suffix) matches the block size of the underlying block cipher. Get that wrong and she will simply not work. 💀
Crypto.Util.number module
Crypto.Util.number.bytes_to_long(s)
Convert a byte string to a long integer (big endian). Straight up. ✨
In Python 3.2+, use the native method instead:
>>> int.from_bytes(s, 'big')
For instance:
>>> int.from_bytes(b'P', 'big')
80
This is (essentially) the inverse of long_to_bytes(). They're sisters, your honour. 🎀
Crypto.Util.number.ceil_div(n, d)
Return ceil(n/d), that is, the smallest integer r such that r*d >= n. Ceiling division but make it a whole function. We stan a tiny icon. 💅
Crypto.Util.number.getPrime(N, randfunc=None)
Return a random N-bit prime number.
N must be an integer larger than 1. If randfunc is omitted, then Random.get_random_bytes() is used. 🦋
Crypto.Util.number.getRandomInteger(N, randfunc=None)
Return a random number at most N bits long.
If randfunc is omitted, then Random.get_random_bytes() is used.
Deprecated since version 3.0: This function is for internal use only and may be renamed or removed in the future. Use Crypto.Random.random.getrandbits() instead. So like... use the other one, bestie 😭
Crypto.Util.number.getRandomNBitInteger(N, randfunc=None)
Return a random number with exactly N-bits, i.e. a random number between 2**(N-1) and (2**N)-1. Exactly N, not approximately, not "eh close enough". 🌸
If randfunc is omitted, then Random.get_random_bytes() is used.
Deprecated since version 3.0: This function is for internal use only and may be renamed or removed in the future. 🙈
Crypto.Util.number.getRandomRange(a, b, randfunc=None)
Return a random number n so that a <= n < b. Note the < on the right side — b is excluded, that's not a typo, that's on purpose. ⭐
If randfunc is omitted, then Random.get_random_bytes() is used.
Deprecated since version 3.0: This function is for internal use only and may be renamed or removed in the future. Use Crypto.Random.random.randrange() instead. 💀
Crypto.Util.number.getStrongPrime(N, e=0, false_positive_prob=1e-06, randfunc=None)
Return a random strong N-bit prime number. In this context, p is a strong prime if p-1 and p+1 both have at least one large prime factor. She's strong, she's balanced, she's everything. 👑💪
Parameters
N(integer) — the exact length of the strong prime. It must be a multiple of 128 and > 512. (Yes, both. No, you can't negotiate.)e(integer) — if provided, the returned prime (minus 1) will be coprime toeand thus suitable for RSA whereeis the public exponent. Very demure, very RSA-ready. 🎀false_positive_prob(float) — the statistical probability for the result not to be actually a prime. It defaults to 10-6. Note that the real probability of a false-positive is far less. This is just the mathematically provable limit. (She's being humble, the real number is way better. 😌)randfunc(callable) — a function that takes a parameterNand returns a random byte string of such length. If omitted,Crypto.Random.get_random_bytes()is used.
Returns
The new strong prime. Go off, queen. 🔥
Deprecated since version 3.0: This function is for internal use only and may be renamed or removed in the future. 😭
Crypto.Util.number.inverse(u, v)
The inverse of u mod v. Short, sweet, does exactly one thing and does it flawlessly. 💜
Crypto.Util.number.isPrime(N, false_positive_prob=1e-06, randfunc=None)
Test if a number N is a prime. Vibe check on a number, basically. 🦋✨
Parameters
false_positive_prob(float) — the statistical probability for the result not to be actually a prime. It defaults to 10-6. Note that the real probability of a false-positive is far less. This is just the mathematically provable limit. Same deal as before, she's being modest.randfunc(callable) — a function that takes a parameterNand that returns a random byte string of such length. If omitted,Crypto.Random.get_random_bytes()is used.
Returns
True if the input is indeed prime. That's it. That's the answer. 💅
Crypto.Util.number.long_to_bytes(n, blocksize=0)— takes a positive integernand turns it into a byte string, using big endian encoding 💖
If blocksize is absent or zero, the byte string will be of minimal length.
Otherwise, the length of the byte string is guaranteed to be a multiple of blocksize. If necessary, zeroes (\x00) are added at the left.
NOTE: in Python 3, if you're sure thatncan fit intoblocksizebytes, you can simply use the native method instead, bestie:
>>> n.to_bytes(blocksize, 'big')
For instance:
>>> n = 80
>>> n.to_bytes(2, 'big')
b'\x00P'
However, and unlike this long_to_bytes() function, an OverflowError exception is raised if n does not fit. 😭 So like… know your limits, okay?
Crypto.Util.number.size(N)— returns the size of the numberNin bits ✨
Okay so all the cryptographic functionalities are organized into sub-packages, and each sub-package is dedicated to solving a specific class of problems — it's giving extremely organized 👑
Crypto.Cipher— modules for protecting confidentiality, that is, for encrypting and decrypting data (example: AES) 🔐Crypto.Signature— modules for assuring authenticity, that is, for creating and verifying digital signatures of messages (example: PKCS#1 v1.5) ✍️Crypto.Hash— modules for creating cryptographic digests (example: SHA-256) 🧬Crypto.PublicKey— modules for generating, exporting or importing public keys (example: RSA or ECC) 🔑Crypto.Protocol— modules for faciliting secure communications between parties, in most cases by leveraging cryptographic primitives from other modules (example: Shamir's Secret Sharing scheme) 🤝Crypto.IO— modules for dealing with encodings commonly used for cryptographic data (example: PEM) 📄Crypto.Random— modules for generating random data 🎲Crypto.Util— general purpose routines (example: XOR for byte strings) 🧰
Lowkey, in certain cases there is some overlap between these categories, and honestly? It's fine. For instance, authenticity is also provided by Message Authentication Codes, and some can be built using digests, so they are included in the Crypto.Hash package (example: HMAC). Also, cryptographers have over time realized that encryption without authentication is often of limited value, so recent ciphers found in the Crypto.Cipher package embed it (example: GCM). The way that security just evolves 😌
PyCryptodome strives to maintain strong backward compatibility with the old PyCrypto's API (except for those few cases where that is harmful to security) so a few modules don't appear where they should (example: the ASN.1 module is under Crypto.Util as opposed to Crypto.IO). Yeah it's a little quirky, but it's iconic and we love it 💅
✨ EXAMPLES ✨
🔐 Encrypt data with AES
Okay so this first one is a whole vibe. The code below spins up a brand-new AES-128 key and encrypts a piece of data into a file. We're using CTR mode here — it's a classic mode of operation, simple and all, but not recommended anymore.
Here's the thing bestie: with CTR alone, the receiver literally can't tell if the ciphertext (that's the encrypted data, iykyk) got modified while it was in transit. To fix that risk, we also attach a MAC authentication tag (HMAC with SHA256), made with a second key. Two keys, one job each. 🎀
from Crypto.Cipher import AES
from Crypto.Hash import HMAC, SHA256
from Crypto.Random import get_random_bytes
data = 'secret data to transmit'.encode()
aes_key = get_random_bytes(16)
hmac_key = get_random_bytes(16)
cipher = AES.new(aes_key, AES.MODE_CTR)
ciphertext = cipher.encrypt(data)
hmac = HMAC.new(hmac_key, digestmod=SHA256)
tag = hmac.update(cipher.nonce + ciphertext).digest()
with open("encrypted.bin", "wb") as f:
f.write(tag)
f.write(cipher.nonce)
f.write(ciphertext)
# Share securely aes_key and hmac_key with the receiver
# encrypted.bin can be sent over an unsecure channel
At the other end, the receiver can securely load the piece of data back — assuming they know the two keys! 👑 Note that the code generates a ValueError exception when tampering is detected. So like, if someone messed with it, you will find out. 💅
import sys
from Crypto.Cipher import AES
from Crypto.Hash import HMAC, SHA256
# Somehow, the receiver securely get aes_key and hmac_key
# encrypted.bin can be sent over an unsecure channel
with open("encrypted.bin", "rb") as f:
tag = f.read(32)
nonce = f.read(8)
ciphertext = f.read()
try:
hmac = HMAC.new(hmac_key, digestmod=SHA256)
tag = hmac.update(nonce + ciphertext).verify(tag)
except ValueError:
print("The message was modified!")
sys.exit(1)
cipher = AES.new(aes_key, AES.MODE_CTR, nonce=nonce)
message = cipher.decrypt(ciphertext)
print("Message:", message.decode())
🦋 Encrypt and authenticate data in one step
So the code in the previous section is lowkey hiding three subtle-but-important design decisions: the nonce of the cipher is authenticated, the authentication is performed after encryption, and encryption and authentication use two uncorrelated keys. Combining cryptographic primitives securely is genuinely not easy, which is why more modern cryptographic cipher modes got created — like the OCB mode (see also other authenticated encryption modes like EAX, GCM, CCM, SIV). Slay. ✨
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
data = 'secret data to transmit'.encode()
aes_key = get_random_bytes(16)
cipher = AES.new(aes_key, AES.MODE_OCB)
ciphertext, tag = cipher.encrypt_and_digest(data)
assert len(cipher.nonce) == 15
with open("encrypted.bin", "wb") as f:
f.write(tag)
f.write(cipher.nonce)
f.write(ciphertext)
# Share securely aes_key with the receiver
# encrypted.bin can be sent over an unsecure channel
Decryption is also simpler, thank goodness 😭:
import sys
from Crypto.Cipher import AES
# Somehow, the receiver securely get aes_key and hmac_key
# encrypted.bin can be sent over an unsecure channel
with open("encrypted.bin", "rb") as f:
tag = f.read(16)
nonce = f.read(15)
ciphertext = f.read()
cipher = AES.new(aes_key, AES.MODE_OCB, nonce=nonce)
try:
message = cipher.decrypt_and_verify(ciphertext, tag)
except ValueError:
print("The message was modified!")
sys.exit(1)
print("Message:", message.decode())
🎀 Generate an RSA key
This next bit generates a new RSA key pair (secret, obvi) and saves it into a file, protected by a password. We use the scrypt key derivation function to thwart dictionary attacks — take that, attackers. 💀 At the end, the code prints out the RSA public key in ASCII/PEM format:
from Crypto.PublicKey import RSA
secret_code = "Unguessable"
key = RSA.generate(2048)
encrypted_key = key.export_key(passphrase=secret_code, pkcs=8,
protection="scryptAndAES128-CBC",
prot_params={'iteration_count':131072})
with open("rsa_key.bin", "wb") as f:
f.write(encrypted_key)
print(key.publickey().export_key())
And then this code reads the private RSA key back in, and prints the public key again:
from Crypto.PublicKey import RSA
secret_code = "Unguessable"
encoded_key = open("rsa_key.bin", "rb").read()
key = RSA.import_key(encoded_key, passphrase=secret_code)
print(key.publickey().export_key())
🧚 Generate public key and private key
The following code generates a public key stored in receiver.pem and a private key stored in private.pem. These files get used in the examples below, so like, bookmark them. Every time, it generates a different public key and private key pair. Fresh keys, fresh era. ⭐
from Crypto.PublicKey import RSA
key = RSA.generate(2048)
private_key = key.export_key()
with open("private.pem", "wb") as f:
f.write(private_key)
public_key = key.publickey().export_key()
with open("receiver.pem", "wb") as f:
f.write(public_key)
🌸 Encrypt data with RSA
Now this code encrypts a piece of data for a receiver whose RSA public key we have. That RSA public key is stored in a file called receiver.pem.
Since we want to be able to encrypt an arbitrary amount of data, we use a hybrid encryption scheme — we use RSA with PKCS#1 OAEP for the asymmetric encryption of an AES session key, and then that session key can be used to encrypt all the actual data. Two ciphers, bestie, teaming up. 🫶
Same as the first example, we use the EAX mode to allow detection of unauthorized modifications.
from Crypto.PublicKey import RSA
from Crypto.Random import get_random_bytes
from Crypto.Cipher import AES, PKCS1_OAEP
data = "I met aliens in UFO. Here is the map.".encode("utf-8")
recipient_key = RSA.import_key(open("receiver.pem").read())
session_key = get_random_bytes(16)
# Encrypt the session key with the public RSA key
cipher_rsa = PKCS1_OAEP.new(recipient_key)
enc_session_key = cipher_rsa.encrypt(session_key)
# Encrypt the data with the AES session key
cipher_aes = AES.new(session_key, AES.MODE_EAX)
ciphertext, tag = cipher_aes.encrypt_and_digest(data)
with open("encrypted_data.bin", "wb") as f:
f.write(enc_session_key)
f.write(cipher_aes.nonce)
f.write(tag)
f.write(ciphertext)
The receiver has the private RSA key. They'll use it to decrypt the session key first, and with that, the rest of the file:
from Crypto.PublicKey import RSA
from Crypto.Cipher import AES, PKCS1_OAEP
private_key = RSA.import_key(open("private.pem").read())
with open("encrypted_data.bin", "rb") as f:
enc_session_key = f.read(private_key.size_in_bytes())
nonce = f.read(16)
tag = f.read(16)
ciphertext = f.read()
# Decrypt the session key with the private RSA key
cipher_rsa = PKCS1_OAEP.new(private_key)
session_key = cipher_rsa.decrypt(enc_session_key)
# Decrypt the data with the AES session key
cipher_aes = AES.new(session_key, AES.MODE_EAX, nonce)
data = cipher_aes.decrypt_and_verify(ciphertext, tag)
print(data.decode("utf-8"))
💖 FREQUENTLY ASKED QUESTIONS 💖
🕰️ When will support for Python 2.7 stop?
There are no plans to drop support for Python 2.7. That may change when maintenance becomes too cumbersome.
However — and this is the important bit 👀 — new features will only be tested for Python 3.
🌈 How can I encrypt using an ECC key?
Use Hybrid Public Key Encryption (HPKE, RFC 9180) and the module Crypto.Protocol.HPKE. That's it, that's the answer. ✨
☕ Is CTR cipher mode compatible with Java?
Yes. When you instantiate your AES cipher in Java:
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
SecretKeySpec keySpec = new SecretKeySpec(new byte[16], "AES");
IvParameterSpec ivSpec = new IvParameterSpec(new byte[16]);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
You are effectively using CTR mode without a fixed nonce and with a 128-bit big endian counter starting at 0. The counter will wrap around only after 2¹²⁸ blocks. (Yes, that many. Don't worry about it. 💀)
You can replicate the same keystream in PyCryptodome with:
ivSpec = b'\x00' * 16
ctr = AES.new(keySpec, AES.MODE_CTR, initial_value=ivSpec, nonce=b'')
🎯 Are RSASSA-PSS signatures compatible with Java?
Yes! For Java, you must consider that by default the mask is generated by MGF1 with SHA-1 (regardless of how you hash the message) and the salt is 20 bytes long.
If you want to use another algorithm or another salt length, you must instantiate a PSSParameterSpec object, for instance:
Signature ss = Signature.getInstance("SHA256withRSA/PSS");
AlgorithmParameters pss1 = ss.getParameters();
PSSParameterSpec pssParameterSpec = new PSSParameterSpec("SHA-256", "MGF1", new MGF1ParameterSpec("SHA-256"), 32, 0xBC);
ss.setParameter(spec1);
🔥 Are RSASSA-PSS signatures compatible with OpenSSL?
Yes — but one quirk of OpenSSL (and of a few other libraries too, especially if they're wrappers to OpenSSL) is that the salt length is computed in two possible ways:
- Same as digest size — value for
EVP_PKEY_CTX_set_rsa_pss_saltlen()isRSA_PSS_SALTLEN_DIGEST, and for theopenssl pkeyutlcommand it's-pkeyopt rsa_pss_saltlen:digest - Maximized — value for
EVP_PKEY_CTX_set_rsa_pss_saltlen()isRSA_PSS_SALTLEN_MAX, and for theopenssl pkeyutlcommand it's-pkeyopt rsa_pss_saltlen:max
└─────────────────────┴────────────────────────────────────┴────────────────────────┘
In PyCryptodome, the salt length matches the digest size by default (which is what RFC8017 recommends, bestie, it's literally the standard). But you can also max out the salt length if you're feeling extra, like this:
key = RSA.import_key(open('privkey.der').read())
h = SHA256.new(message)
max_salt_bytes = key.size_in_bytes() - h.digest_size - 2
signature = pss.new(key, salt_bytes=max_salt_bytes).sign(h)
😭 Why do I get the error No module named Crypto on Windows?
Okay so check the directory where your Python packages live, something like:
/path/to/python/Lib/site-packages/
You might find a directory named crypto in there, sitting pretty with all the PyCryptodome files inside it. 🫠
The most likely cause is described right here, and you can fix the whole situation with:
pip uninstall crypto
pip uninstall pycryptodome
pip install pycryptodome
The root cause? In the past you most likely installed an unrelated but similarly named package called crypto, which just so happens to operate under the namespace crypto. Rude of it, honestly.
The Windows filesystem is case-insensitive, so crypto and Crypto are effectively considered the same thing. So when you subsequently install pycryptodome, pip finds that a directory named with the target namespace already exists (under the rules of the underlying filesystem), and therefore installs all the sub-packages of pycryptodome in it. Which is probably a reasonable behavior, if it wasn't that pip does not issue any warning even if it could detect the issue. Like… girl. 💀
💅 Why does strxor raise TypeError: argument 2 must be bytes, not bytearray?
Most probably you have installed both the pycryptodome and the old pycrypto packages. Classic mix-up, ngl.
Run pip uninstall pycrypto and try again. ✨
The old PyCrypto shipped with a strxor module written as a native library (.so or .dll file). If you install pycryptodome, the old native module will still take priority over the new Python extension that comes in the latter. It's giving rude ex energy, fr.
🦋 Why do I get a translation_unit_or_empty undefined error with pycparser?
Unfortunately, pycparser does not work with optimized (-O) Python builds, which strips out the docstrings, causing this error. This is a known issue and it will not be fixed. Periodt. 😮💨
The possible workarounds are:
- Do not run Python with
-O - Remove
cffiandcparser. PyCryptodome will fall back toctypesfor interfacing with the native modules. - Use an earlier version of
cparser(2.14)
👑 CONTRIBUTE AND SUPPORT 👑
- Do not be afraid to contribute with small and apparently insignificant improvements like correction to typos. Every change counts. 💖
- Read carefully the License of PyCryptodome. By submitting your code, you acknowledge that you accept to release it according to the BSD 2-clause license.
- You must disclaim which parts of your code in your contribution were partially copied or derived from an existing source. Ensure that the original is licensed in a way compatible to the BSD 2-clause license.
- You can propose changes in any way you find most convenient. However, the preferred approach is to:
- Clone the main repository on GitHub.
- Create a branch and modify the code.
- Send a pull request upstream with a meaningful description.
- Provide tests (in
Crypto.SelfTest) along with code. If you fix a bug add a test that fails in the current version and passes with your change. - If your change breaks backward compatibility, highlight it and include a justification.
- Ensure that your code complies to PEP8 and PEP257.
- If you add or modify a public interface, make sure the relevant type stubs remain up to date.
- Ensure that your code does not use constructs or includes modules not present in Python 2.6.
- Add a short summary of the change to the file
Changelog.rst. - Add your name to the list of contributors in the file
AUTHORS.rst.
The PyCryptodome mailing list is hosted on Google Groups. You can mail any comment or question to pycryptodome@googlegroups.com. 🎀
Bug reports can be filed on the GitHub tracker.
📜 CHANGELOG 📜
⭐ 3.23.0 (17 May 2025)
New features
- Added cipher modes Key Wrap (KW, RFC3394) and Key Wrap with Padding (KWP, RFC5649). Both are defined also in NIST SP 800-38F.
- Wheels for Windows ARM.
Resolved issues
- GH#862: For HashEdDSA and Ed448,
sign()andverify()modified the state of the XOF.
⭐ 3.22.0 (16 March 2025)
New features
- Added support for HPKE (RFC 9180).
Resolved issues
- GH#812: CCM ciphers will now fail before encrypting (or decrypting) data beyond the limit imposed by the nonce length.
- GH#846: fix infinite loop with RC4 for data larger than 4GB.
- GH#852: handle correctly invalid PEM files with less than 3 lines.
Other changes
- Remove support for Python 3.6.
⭐ 3.21.0 (30 September 2024)
New features
- By setting the
PYCRYPTODOME_DISABLE_GMPenvironment variable, the GMP library will not be used even if detected. - Add support for Curve25519 / X25519.
- Add support for Curve448 / X448.
- Add attribute
curvetoEccPointandEccXPointclasses, with the canonical name of the curve. - GH#781: the label for the
SP800_108_CounterKDF may now contain zero bytes. Thanks to Julien Rische. - GH#814: RSA keys for PSS can be imported.
Resolved issues
- GH#810: fixed negation of Ed25519 points.
- GH#819: accept an RFC5916
ECPrivateKeyeven if it doesn't contain any of the optional elements (parameters [0]andpublicKey[1]).
Other changes
- Remove support for Python 3.5.
⭐ 3.20.0 (9 January 2024)
New features
- Added support for TurboSHAKE128 and TurboSHAKE256.
- Added method
Crypto.Hash.new()to generate a hash object given a hash name. - Added support for AES-GCM encryption of PBES2 and PKCS#8 containers.
- Added support for SHA-2 and SHA-3 algorithms in PBKDF2 when creating PBES2 and PKCS#8 containers.
- Export of RSA keys accepts the
prot_paramsdictionary as parameter to control the number of iterations for PBKDF2 and scrypt. - C unit tests also run on non-x86 architectures.
Resolved issues
- GH#787: Fixed autodetect logic for GCC 14 in combination with LTO.
⭐ 3.19.1 (28 December 2023)
Resolved issues
- Fixed a side-channel leakage with OAEP decryption that could be exploited to carry out a Manger attack (CVE-2023-52323). Thanks to Hubert Kario.
🚨 That's a CVE, bestie. Leakage like that is not a vibe. Update.
⭐ 3.19.0 (16 September 2023)
New features
- The
update()methods of TupleHash128 and TupleHash256 objects can now hash multiple items (byte strings) at once. Thanks to Sylvain Pelissier. - Added support for ECDH, with
Crypto.Protocol.DH.
Resolved issues
- GH#754: due to a bug in
cffi, do not use it on Windows with Python 3.12+.
⭐ 3.18.0 (18 May 2023)
New features
- Added support for DER BOOLEAN encodings.
- The library now compiles on Windows ARM64. Thanks to Niyas Sait.
Resolved issues
- GH#722:
nonceattribute was not correctly set forXChaCha20_Poly1305ciphers. Thanks to Liam Haber. - GH#728: Workaround for a possible x86 emulator bug in Windows for ARM64.
- GH#739: OID encoding for arc 2 didn't accept children larger than 39. Thanks to James.
- Correctly check that the scalar matches the point when importing an ECC private key.
⭐ 3.17.0 (29 January 2023)
New features
- Added support for the Counter Mode KDF defined in SP 800-108 Rev 1.
- Reduce the minimum tag length for the EAX cipher to 2 bytes.
- An RSA object has 4 new properties for the CRT coefficients:
dp,dq,invqandinvq(invpis the same value as the existingu).
Resolved issues
- GH#526: improved typing for
RSA.construct. - GH#534: reduced memory consumption when using a large number of cipher objects.
- GH#598: fixed missing error handling for
Util.number.inverse. - GH#629: improved typing for
AES.newand the various mode-specific types it returns. Thanks to Greg Werbin. - GH#653: added workaround for an alleged GCC compiler bug that affected Ed25519 code compiled for AVX2.
- GH#658: attribute
curveof an ECC key was not always the preferred curve name, as it used to be in v3.15.0 (independently of the curve name specified when generating the key). - GH#637: fixed typing for legacy modules
PKCS1_v1_5andPKCS1_PSS, as theirverify()returned a boolean. - GH#664: with OCB mode, nonces of maximum length (15 bytes) were actually used as 14 bytes nonces. After this fix, data that was encrypted in past using the (default) nonce length of 15 bytes can still be decrypted by reducing the nonce to its first 14 bytes.
- GH#705: improved typing for
nonce,iv, andIVparameters of cipher objects.
Other changes
- Build PyPy wheels only for versions 3.8 and 3.9, and not for 3.7 anymore.
⭐ 3.16.0 (26 November 2022)
New features
- Build wheels for musl Linux. Thanks to Ben Raz.
Resolved issues
- GH#639: ARC4 now also works with 'keys' as short as 8 bits.
- GH#669: fix segfaults when running in a manylinux2010 i686 image.
💀 Segfaults in a manylinux2010 i686 image is such a cursed combo. Glad it's fixed.
⭐ 3.15.0 (22 June 2022)
New features
- Add support for curves Ed25519 and Ed448, including export and import of keys.
- Add support for EdDSA signatures.
- Add support for Asymmetric Key Packages (RFC5958) to import private keys.
Resolved issues
- GH#620: for
Crypto.Util.number.getPrime, do not sequentially scan numbers searching for a prime.
⭐ 3.14.1 (5 February 2022)
Resolved issues
- GH#595: Fixed memory leak for GMP integers. Thanks to Witalij Siebert and Pablo Quílez.
⭐ 3.14.0 (30 January 2022)
New features
- Add support for curve NIST P-192.
⭐ 3.13.0 (23 January 2022)
New features
- Add support for curve NIST P-224.
Resolved issues
- GH#590: Fixed typing info for
Crypto.PublicKey.ECC.
Other changes
- Relaxed ECDSA requirements for FIPS 186 signatures and accept any SHA-2 or SHA-3 hash.
sign()andverify()will be performed even if the hash is stronger than the ECC key.
⭐ 3.12.0 (4 December 2021)
New features
- ECC keys in the SEC1 format can be exported and imported.
- Add support for KMAC128, KMAC256, TupleHash128, and TupleHash256 (NIST SP-800 185).
- Add support for KangarooTwelve. 🦘✨
Resolved issues
- GH#563: An asymmetric key could not be imported as a
memoryview. - GH#566: cSHAKE128/256 generated a wrong output for customization strings longer than 255 bytes.
- GH#582: CBC decryption generated the wrong plaintext when the input and the output were the same buffer. Thanks to Michael K. Ashburn.
💫 3.11.0 (8 October 2021)
🩹 Resolved issues
- GH#512: okay so this one's funny — especially for very small bit sizes,
Crypto.Util.number.getPrime()was occasionally generating primes larger than the bit size you asked for 💀. Shoutout to Koki Takahashi for catching it. - GH#552: fixed the typing annotations for
PKCS115_Cipher.decrypt()— they were just wrong, bestie. - GH#555: the
decrypt()method of a PKCS#1v1.5 cipher was returning abytearrayinstead ofbytes. Not the vibe. Fixed. - GH#557: external DSA domain parameters were being accepted even when the modulus (
p) was not prime, which is a no-no 🙈. This affectedCrypto.PublicKey.DSA.generate()andCrypto.PublicKey.DSA.construct(). Thanks again to Koki Takahashi.
✨ New features
- Added
cSHAKE128andcSHAKE256(of the SHA-3 family). Thanks to Michael Schaffner, icon 👑. - GH#558: the flag
RTLD_DEEPBINDpassed todlopen()is not well supported by address sanitizers. So now you can set the environment variablePYCRYPTDOME_DISABLE_DEEPBINDto drop that flag and allow security testing. Slay. 🔥
💫 3.10.4 (25 September 2021)
🩹 Resolved issues
- The output of
Crypto.Util.number.long_to_bytes()was not always a multiple ofblocksize. Now it is. Periodt.
💫 3.10.3 (22 September 2021)
🩹 Resolved issues
- GH#376: fixed a symbol conflict between different versions of
libgmp. Messy, honestly. - GH#481: improved the robustness of PKCS#1v1.5 decryption against timing attacks. Very much needed 🫶.
- GH#506 and GH#509: fixed segmentation faults on Apple M1 and other Aarch64 SoCs when the GMP library was accessed via
ctypes. The fix: don't use GMP's ownsscanfandsnprintfroutines — instead, use simpler conversion routines. - GH#510: workaround for
cfficallingctypes.util.find_library(), which invokesgccandldon Linux and considerably slows down all imports. On certain configurations, that may also leave temporary files behind. Yikes 😭. - GH#517: fixed
RSAES-OAEP, because it didn't always fail when zero padding was incorrect. Rude of it.
✨ New features
- Added support for SHA-3 hash functions to HMAC.
🎀 Other changes
- The Windows wheels of Python 2.7 now require the VS2015 runtime to be installed in the system, because Microsoft stopped distributing the VS2008 compiler in April 2021. VS2008 was used to compile the Python 2.7 extensions. RIP bozo.
💫 3.10.1 (9 February 2021)
🎀 Other changes
- Python 3 wheels use the
abi3ABI tag. - Removed Appveyor CI. Bye 👋.
💫 3.10.0 (6 February 2021)
🩹 Resolved issues
- Fixed a potential memory leak when initializing block ciphers 🧠.
- GH#466:
Crypto.Math.miller_rabin_test()was still using the system random source and not the one provided as a parameter. Like, hello? Fixed. - GH#469: RSA objects now have the method
public_key()just like ECC objects. The old methodpublickey()is still available for backward compatibility, don't worry bestie 💜. - GH#476:
Crypto.Util.Padding.unpad()was raising an incorrect exception in case of zero-length inputs. Thanks to Captainowie 🙏. - GH#491: better exception message when
Counter.new()is called with an integerinitial_valuethat doesn't fit intonbitsbits. - GH#496: added the missing
block_sizemember for ECB cipher objects. Thanks to willem. - GH#500: the
noncemember of an XChaCha20 cipher object was not matching the original nonce. Thanks to Charles Machalow — good catch fr fr.
🎀 Other changes
- The bulk of the test vectors have been moved to the separate package
pycryptodome-test-vectors. As a result, the packagespycryptodomeandpycryptodomexbecome significantly smaller (from 14MB to 3MB). Glow-up 💅. - Moved CI tests and the build service from Travis CI to GitHub Actions.
💀 Breaks in compatibility
- Dropped support for Python 2.6 and 3.4.
💫 3.9.9 (2 November 2020)
🩹 Resolved issues
- GH#435: fixed
Crypto.Util.number.sizefor negative numbers.
✨ New features
- Build Python 3.9 wheels on Windows 🪟.
💫 3.9.8 (23 June 2020)
🩹 Resolved issues
- GH#426: plot twist — the Shamir's secret sharing implementation is not actually compatible with
ssss. Added an optional parameter to enable interoperability. - GH#427: skip altogether the loading of
gmp.dllon Windows. - GH#420: fix incorrect CFB decryption when the input and the output are the same buffer. Sneaky one.
✨ New features
- Sped up Shamir's secret sharing routines. Thanks to ncarve 🏎️.
💫 3.9.7 (20 February 2020)
🩹 Resolved issues
- GH#381: make notarization possible again on OS X when using wheels. Thanks to Colin Atkinson.
💫 3.9.6 (2 February 2020)
🩹 Resolved issues
- Fix building of wheels for OS X by explicitly setting the
sysrootlocation.
💫 3.9.5 (1 February 2020)
🩹 Resolved issues
- RSA OAEP decryption was not verifying that all PS bytes are zero 😤.
- GH#372: fixed a memory leak for operations that use memoryviews when
cffiis not installed. - Fixed a wrong ASN.1 OID for HMAC-SHA512 in PBE2.
✨ New features
- Updated Wycheproof test vectors to version 0.8r12.
💫 3.9.4 (18 November 2019)
🩹 Resolved issues
- GH#341: prevent
key_to_englishfrom creating invalid data when fed with keys of length not a multiple of 8. Thanks to vstoykovbg. - GH#347: fix blocking RSA signing/decryption when the key has a very small factor. Thanks to Martijn Pieters 🙌.
💫 3.9.3 (12 November 2019)
🩹 Resolved issues
- GH#308: align the stack of functions using SSE2 intrinsics to avoid crashes when compiled with
gccon 32-bit x86 platforms.
💫 3.9.2 (10 November 2019)
✨ New features
- Add Python 3.8 wheels for Mac 🍎.
🩹 Resolved issues
- GH#308: avoid allocating arrays of
__m128ion the stack, to cope with buggy compilers. - GH#322: remove the blanket
-O3optimization forgccandclang, to cope with buggy compilers. (Buggy compilers remain the villain of this whole changelog, ngl.) - GH#337: fix the typing stubs for signatures.
- GH#338: deal with
gccinstallations that don't havex86intrin.h.
💫 3.9.1 (1 November 2019)
✨ New features
- Add Python 3.8 wheels for Linux and Windows.
🩹 Resolved issues
- GH#328: minor speed-up when importing RSA ⚡.
💫 3.9.0 (27 August 2019)
✨ New features
- Add support for loading PEM files encrypted with AES256-CBC.
- Add support for XChaCha20 and XChaCha20-Poly1305 ciphers 🔐.
- Add support for the bcrypt key derivation function (
Crypto.Protocol.KDF.bcrypt). - Add support for left multiplication of an EC point by a scalar.
- Add support for importing ECC and RSA keys in the new OpenSSH format.
🩹 Resolved issues
- GH#312: it was not possible to invert an EC point anymore. Fixed, bestie.
- GH#316: fix printing of DSA keys.
- GH#317:
DSA.generate()was not always using therandfuncinput. - GH#285: the MD2 hash had block size of 64 bytes instead of 16; as a result the HMAC construction gave incorrect results. That's a big oof 😭.
💫 3.8.2 (30 May 2019)
🩹 Resolved issues
- GH#291: fix strict aliasing problem, which emerged with GCC 9.1.
💫 3.8.1 (4 April 2019)
✨ New features
- Add support for loading PEM files encrypted with AES192-CBC and AES256-GCM.
- When importing ECC keys in PEM format, ignore the redundant EC PARAMS section that was included by certain
opensslcommands. So polite of it to ignore it 💅.
🩹 Resolved issues
repr()did not work forECC.EccKeyobjects.- Fix installation in development mode (
setup install developorpip install -e .). - Minimal length for Blowfish cipher is 32 bits, not 40 bits.
- Various updates to docs 📚.
💫 3.8.0 (23 March 2019)
✨ New features
- Speed-up ECC performance. ECDSA is 33 times faster on the NIST P-256 curve. THIRTY-THREE. TIMES. 🔥
- Added support for NIST P-384 and P-521 curves.
EccKeyhas new methodssize_in_bits()andsize_in_bytes().- Support HMAC-SHA224, HMAC-SHA256, HMAC-SHA384, and HMAC-SHA512 in PBE2/PBKDF2.
🩹 Resolved issues
- DER objects were not rejected if their length field had a leading zero.
- Allow legacy RC2 ciphers to have 40-bit keys.
- ASN.1 Object IDs did not allow the value 0 in the path.
💀 Breaks in compatibility
point_at_infinity()becomes an instance method forCrypto.PublicKey.ECC.EccKey, from a static one.
💫 3.7.3 (19 January 2019)
🩹 Resolved issues
- GH#258: false positive on PSS signatures when the externally provided salt is too long.
- Include type stub files for
Crypto.IOandCrypto.Util.
💫 3.7.2 (26 November 2018)
🩹 Resolved issues
- GH#242: fixed compilation problem on ARM platforms.
💫 3.7.1 (25 November 2018)
✨ New features
- Added type stubs to enable static type checking with
mypy. Thanks to Michael Nix 🫶. - New
update_after_digestflag for CMAC.
🩹 Resolved issues
- GH#232: fixed problem with
gcc4.x when compilingghash_clmul.c. - GH#238: incorrect digest value produced by CMAC after cloning the object.
- Method
update()of an EAX cipher object was returning the underlying CMAC object, instead of the EAX object itself. Awkward 😬. - Method
update()of a CMAC object was not throwing an exception after the digest was computed (withdigest()orverify()).
💫 3.7.0 (27 October 2018)
✨ New features
- Added support for Poly1305 MAC (with AES and ChaCha20 ciphers for key derivation).
- Added support for the ChaCha20-Poly1305 AEAD cipher 🔐.
- New parameter
outputforCrypto.Util.strxor.strxor,Crypto.Util.strxor.strxor_c, and theencryptanddecryptmethods in symmetric ciphers (theCrypto.Cipherpackage).outputis a pre-allocated buffer (abytearrayor a writeablememoryview) where the result must be stored. This requires less memory for very large payloads; it is also more efficient when encrypting (or decrypting) several small payloads. Efficiency queen behavior 👑.
🩹 Resolved issues
- GH#266: AES-GCM hangs when processing more than 4GB at a time on x86 with the PCLMULQDQ instruction.
💀 Breaks in compatibility
- Drop support for Python 3.3.
- Remove
Crypto.Util.py3compat.unhexlifyandCrypto.Util.py3compat.hexlify. - With the old Python 2.6, use only
ctypes(and notcffi) to interface to native code.
💫 3.6.6 (17 August 2018)
🩹 Resolved issues
- GH#198: fix vulnerability on AESNI ECB with payloads smaller than 16 bytes (CVE-2018-15560). Take this one seriously, bestie 🚨.
💫 3.6.5 (12 August 2018)
🩹 Resolved issues
- GH#187: fixed incorrect AES encryption/decryption with AES acceleration on x86 due to
gcc's optimization and strict aliasing rules. - GH#188: more prime number candidates than necessary were discarded as composite, due to the limited way D values were searched in the Lucas test.
- Fixed
ResouceWarningsandDeprecationWarnings. (Yes, we're keeping that spelling, it's canon now 🎀.) - Workaround for the Python 3.7.0 bug on Windows (
https://bugs.python.org/issue34108).
💫 3.6.4 (10 July 2018)
✨ New features
- Build Python 3.7 wheels on Linux, Windows and Mac.
🩹 Resolved issues
- GH#178: rename the
_cpuidmodule to make upgrades more robust. - More meaningful exceptions in case of a mismatch in IV length (CBC/OFB/CFB modes).
- Fix compilation issues on Solaris 10/11.
💫 3.6.3 (21 June 2018)
🩹 Resolved issues
- GH#175: fixed incorrect results for CTR encryption/decryption with more than 8 blocks.
💫 3.6.2 (19 June 2018)
✨ New features
- ChaCha20 accepts 96-bit nonces (in addition to 64-bit nonces) as defined in
RFC7539. Living that standards-compliant life 🌈. - Accelerate AES-GCM on x86 using PCLMULQDQ instruction.
- Accelerate AES-ECB and AES-CTR on x86 by pipelining AESNI instructions.
- As result of the two improvements above, on x86 (Broadwell):
- AES-ECB and AES-CTR are 3x faster
- AES-GCM is 9x faster 💅
Resolved issues
- On Windows, MPIR library was stilled pulled in if renamed to
gmp.dll. 🙈
Breaks in compatibility
- In
Crypto.Util.number, functionsfloor_divandexact_divhave been removed. Also,ceil_divis limited to non-negative terms only. sobbing 😭
3.6.1 (15 April 2018)
New features
- Added Google Wycheproof tests (
https://github.com/google/wycheproof) for RSA, DSA, ECDSA, GCM, SIV, EAX, CMAC. 💖 - New parameter
mac_len(length of MAC tag) for CMAC.
Resolved issues
- In certain circumstances (at counter wrapping, which happens on average after 32 GB) AES GCM produced wrong ciphertexts. 💀 that's genuinely scary bestie
- Method
encrypt()of AES SIV cipher could be still called, whereas onlyencrypt_and_digest()is allowed.
3.6.0 (8 April 2018)
New features
- Introduced
export_keyand deprecatedexportKeyfor DSA and RSA key objects. - Ciphers and hash functions accept
memoryviewobjects in input. ✨ - Added support for SHA-512/224 and SHA-512/256.
Resolved issues
- Reintroduced
Crypto.__version__variable as in PyCrypto. - Fixed compilation problem with MinGW.
3.5.1 (8 March 2018)
Resolved issues
GH#142. Fix mismatch with declaration and definition ofaddmul128.
3.5.0 (7 March 2018)
New features
- Import and export of ECC curves in compressed form.
- The initial counter for a cipher in CTR mode can be a byte string (in addition to an integer).
- Faster PBKDF2 for HMAC-based PRFs (at least 20x for short passwords, more for longer passwords). Thanks to Christian Heimes for pointing out the implementation was under-optimized. 🫶
- The salt for PBKDF2 can be either a string or bytes (
GH#67). - Ciphers and hash functions accept data as
bytearray, not just binary strings. - The old SHA-1 and MD5 hash functions are available even when Python's own
hashlibdoes not include them. 👑
Resolved issues
- Without libgmp, modular exponentiation (since v3.4.8) crashed on 32-bit big-endian systems. 💀
Breaks in compatibility
- Removed support for Python < 2.6.
3.4.12 (5 February 2018)
Resolved issues
GH#129.pycryptodomexcould only be installed via wheels.
3.4.11 (5 February 2018)
Resolved issues
GH#121. the record list was still not correct due to PEP3147 and__pycache__directories. Thanks again to John O'Brien. 🙏
3.4.10 (2 February 2018)
Resolved issues
- When creating ElGamal keys, the generator wasn't a square residue: ElGamal encryption done with those keys cannot be secure under the DDH assumption. Thanks to Weikeng Chen. 🚨 like, actually not secure, so pay attention
3.4.9 (1 February 2018)
New features
- More meaningful error messages while importing an ECC key. 💖
Resolved issues
GH#123and#125. The SSE2 command line switch was not always passed on 32-bit x86 platforms.GH#121. The record list (--record) was not always correctly filled for thepycryptodomexpackage. Thanks to John W. O'Brien.
3.4.8 (27 January 2018)
New features
- Added a native extension in pure C for modular exponentiation, optimized for SSE2 on x86. In the process, we drop support for the arbitrary arithmetic library MPIR on Windows, which is painful to compile and deploy. The custom modular exponentiation is 130% (160%) slower on an Intel CPU in 32-bit (64-bit) mode, compared to MPIR. Still, that is much faster that CPython's own
pow()function which is 900% (855%) slower than MPIR. Support for the GMP library on Unix remains. - Added support for manylinux wheels. 🎡
- Support for Python 3.7.
Resolved issues
- The DSA parameter 'p' prime was created with 255 bits cleared (but still with the correct strength).
GH#106. Not all docs were included in the tar ball. Thanks to Christopher Hoskin.GH#109. ECDSA verification failed for DER encoded signatures. Thanks to Alastair Houghton.- Human-friendly messages for padding errors with ECB and CBC. 🫶
3.4.7 (26 August 2017)
New features
- API documentation is made with sphinx instead of epydoc.
- Start using
importlibinstead ofimpwhere available.
Resolved issues
GH#82. Fixed PEM header for RSA/DSA public keys.
3.4.6 (18 May 2017)
Resolved issues
GH#65. Keccak, SHA3, SHAKE and the seek functionality for ChaCha20 were not working on big endian machines. Fixed. Thanks to Mike Gilbert. 💜- A few fixes in the documentation.
3.4.5 (6 February 2017)
Resolved issues
- The library can also be compiled using MinGW.
3.4.4 (1 February 2017)
Resolved issues
- Removed use of
alloca(). - [Security] Removed implementation of deprecated "quick check" feature of PGP block cipher mode. 🚨
- Improved the performance of scrypt by converting some Python to C.
3.4.3 (17 October 2016)
Resolved issues
- Undefined warning was raised with libgmp version < 5
- Forgot inclusion of
alloca.h - Fixed a warning about type mismatch raised by recent versions of cffi
3.4.2 (8 March 2016)
Resolved issues
- Fix renaming of package for install command.
3.4.1 (21 February 2016)
New features
- Added option to install the library under the
Cryptodomepackage (instead ofCrypto).
3.4 (7 February 2016)
New features
- Added
Crypto.PublicKey.ECCmodule (NIST P-256 curve only), including export/import of ECC keys. 🌸 - Added support for ECDSA (FIPS 186-3 and RFC6979).
- For CBC/CFB/OFB/CTR cipher objects,
encrypt()anddecrypt()cannot be intermixed. - CBC/CFB/OFB, the cipher objects have both
IVandivattributes.new()acceptsIVas well asivas parameter. - For CFB/OPENPGP cipher object,
encrypt()anddecrypt()do not require the plaintext or ciphertext pieces to have length multiple of the CFB segment size. - Added dedicated tests for all cipher modes, including NIST test vectors
- CTR/CCM/EAX/GCM/SIV/Salsa20/ChaCha20 objects expose the
nonceattribute. - For performance reasons, CCM cipher optionally accepted a pre-declaration of the length of the associated data, but never checked if the actual data passed to the cipher really matched that length. Such check is now enforced. 💅
- CTR cipher objects accept parameter
nonceand possiblyinitial_valuein alternative tocounter(which is deprecated). - All
iv/IVandnonceparameters are optional. If not provided, they will be randomly generated (exception:noncefor CTR mode in case of block sizes smaller than 16 bytes). - Refactored ARC2 cipher.
- Added
Crypto.Cipher.DES3.adjust_key_parity()function. - Added
RSA.import_keyas an alias to the deprecatedRSA.importKey(same for the DSA module). - Added
size_in_bits()andsize_in_bytes()methods toRsaKey.
Resolved issues
- RSA key size is now returned correctly in
RsaKey.__repr__()method (kudos to hannesv). 🫶 - CTR mode does not modify anymore
counterparameter passed tonew()method. - CTR raises
OverflowErrorinstead ofValueErrorwhen the counter wraps around. - PEM files with Windows newlines could not be imported.
Crypto.IO.PEMandCrypto.IO.PKCS8used to accept empty passphrases.GH#6:NotImplementedErrornow raised for unsupported methodssign,verify,encrypt,decrypt,blind,unblindandsizein objectsRsaKey,DsaKey,ElGamalKey.
Breaks in compatibility
- Parameter
segment_sizecannot be0for the CFB mode. - For OCB ciphers, a final call without parameters to
encryptmust end a sequence of calls toencryptwith data (similarly fordecrypt). - Key size for ARC2, ARC4 and Blowfish must be at least 40 bits long (still very weak). 😬
- DES3 (Triple DES module) does not allow keys that degenerate to Single DES.
- Removed method
getRandomNumberinCrypto.Util.number. - Removed module
Crypto.pct_warnings. - Removed attribute
Crypto.PublicKey.RSA.algorithmIdentifier.
3.3.1 (1 November 2015)
New features
- Opt-in for
update()afterdigest()for SHA-3, keccak, BLAKE2 hashes
Resolved issues
- Removed unused SHA-3 and keccak test vectors, therefore significantly reducing the package from 13MB to 3MB. 💅 iconic
Breaks in compatibility
- Removed method
copy()from BLAKE2 hashes - Removed ability to
update()a BLAKE2 hash after the first call to(hex)digest()
3.3 (29 October 2015)
New features
- Windows wheels bundle the MPIR library
- Detection of faults occurring during secret RSA operations
- Detection of non-prime (weak)
qvalue in DSA domain parameters - Added original Keccak hash family (
b=1600only). In the process, simplified the C code base for SHA-3. - Added SHAKE128 and SHAKE256 (of SHA-3 family) ✨
Resolved issues
GH#3: gcc 4.4.7 unhappy about double typedef
Breaks in compatibility
- Removed method
copy()from all SHA-3 hashes - Removed ability to
update()a SHA-3 hash after the first call to(hex)digest()
3.2.1 (9 September 2015)
New features
- Windows wheels are automatically built on Appveyor
3.2 (6 September 2015)
New features
- Added hash functions BLAKE2b and BLAKE2s. 🦋
- Added stream cipher ChaCha20.
- Added OCB cipher mode.
- CMAC raises an exception whenever the message length is found to be too large and the chance of collisions not negligeable.
- New attribute
oidfor Hash objects with ASN.1 Object ID - Added
Crypto.Signature.pssandCrypto.Signature.pkcs1_15 - Added NIST test vectors (roughly 1200) for PKCS#1 v1.5 and PSS signatures.
Resolved issues
tomcrypt_macros.hasm error#1
Breaks in compatibility
- Removed keyword
verify_x509_certfrom module methodimportKey(RSA and DSA). - Reverted to original PyCrypto behavior of method
verifyinPKCS1_v1_5andPKCS1_PSS.
3.1 (15 March 2015)
New features
- Speed up execution of Public Key algorithms on PyPy, when backed by the Gnu Multiprecision (GMP) library. 🔥
- GMP headers and static libraries are not required anymore at the time PyCryptodome is built. Instead, the code will automatically use the GMP dynamic library (
.so/.DLL) if found in the system at runtime. - Reduced the amount of C code by almost 40% (4700 lines). Modularized and simplified all code (C and Python) related to block ciphers. Pycryptodome is now free of CPython extensions. 💅 slay
- Add support for CI in Windows via Appveyor.
- RSA and DSA key generation more closely follows FIPS 186-4 (though it is not 100% compliant).
Resolved issues
- None
💅 Breaks in compatibility 💅
- ngl, new dependency alert:
ctypeswith Python 2.4. 🐍 - the
counterparameter of a CTR mode cipher must be generated viaCrypto.Util.Counternow — it can't be a generic callable anymore, bestie, those days are gone 😭 - Removed the
Crypto.Random.Fortunapackage (due to lack of test vectors) — rip 🪦 - Removed the
Crypto.Hash.newfunction - the
allow_wraparoundparameter ofCrypto.Util.Counteris ignored now. An exception is always generated if the counter is reused. no cap, it will not let you 💀 DSA.generate,RSA.generateandElGamal.generatedo not accept theprogress_funcparameter anymore- Removed
Crypto.PublicKey.RSA.RSAImplementation - Removed
Crypto.PublicKey.DSA.DSAImplementation - Removed the ambiguous method
size()from RSA, DSA and ElGamal keys — yeah she was confusing everyone, she had to go 🙈
✨ 3.0 (24 June 2014) ✨
🎀 New features 🎀
- Initial support for PyPy 🏃♀️
- SHA-3 hash family based on the April 2014 draft of FIPS 202. See modules
Crypto.Hash.SHA3_224/256/384/512. Initial Keccak patch by Fabrizio Tarizzo. iconic behavior honestly 👑 - Salsa20 stream cipher. See module
Crypto.Cipher.Salsa20. Patch by Fabrizio Tarizzo 💃 - Colin Percival's scrypt key derivation function (
Crypto.Protocol.KDF.scrypt) - Proper interface to FIPS 186-3 DSA. See module
Crypto.Signature.DSS - Deterministic DSA (RFC6979). Again, see
Crypto.Signature.DSS✨ - HMAC-based Extract-and-Expand key derivation function (
Crypto.Protocol.KDF.HKDF, RFC5869) - Shamir's Secret Sharing protocol, compatible with ssss (128 bits only). See module
Crypto.Protocol.SecretSharing🤫 - Ability to generate a DSA key given the domain parameters 🗝️
- Ability to test your installation with a simple
python -m Crypto.SelfTest— literally the easiest vibe check ever 💖
🩹 Resolved issues 🩹
- LP#1193521:
mpz_powm_sec()(and Python) crashed when modulus was odd - Benchmarks work again (they broke when ECB stopped working if an IV was passed. Patch by Richard Mitchell.
- LP#1178485: removed some catch-all exception handlers. Patch by Richard Mitchell.
- LP#1209399: Removal of Python wrappers caused HMAC to silently produce the wrong data with SHA-2 algorithms 😱
- LP#1279231: remove dead code that does nothing in SHA-2 hashes. Patch by Richard Mitchell.
- LP#1327081: AESNI code accesses memory beyond buffer end 💀
- Stricter checks on ciphertext and plaintext size for textbook RSA (kudos to sharego) 🫶
💅 Breaks in compatibility 💅
- Removed support for Python < 2.4
- Removed the following methods from all 3 public key object types (RSA, DSA, ElGamal):
signverifyencryptdecryptblindunblind
Code that uses such methods is doomed anyway. It should be fixed ASAP to use the algorithms available in Crypto.Signature and Crypto.Cipher. like, girl, it's over for that code fr fr 😭
- The 3 public key object types (RSA, DSA, ElGamal) are now unpickable
- Symmetric ciphers do not have a default mode anymore (used to be ECB). An expression like
AES.new(key)will now fail. If ECB is the desired mode, one has to explicitly useAES.new(key, AES.MODE_ECB)⚠️ no vibes-based defaults anymore - Unsuccessful verification of a signature will now raise an exception [reverted in 3.2]
- Removed the
Crypto.Random.OSRNGpackage - Removed the
Crypto.Util.winrandommodule - Removed the
Crypto.Random.randpoolmodule - Removed the
Crypto.Cipher.XORmodule - Removed the
Crypto.Protocol.AllOrNothingmodule - Removed the
Crypto.Protocol.Chaffingmodule - Removed the parameters
disabled_shortcutandoverflowfromCrypto.Util.Counter.new
🌈 Other changes 🌈
Crypto.Randomstops being a userspace CSPRNG. It is now a pure wrapper overos.urandom🎯- Added certain resistance against side-channel attacks for GHASH (GCM) and DSA 🛡️
- More test vectors for HMAC-RIPEMD-160
- Update libtomcrypt headers and code to v1.17 (kudos to Richard Mitchell)
- RSA and DSA keys are checked for consistency as they are imported 👀
- Simplified build process by removing autoconf
- Speed optimization to PBKDF2 🏎️
- Add support for MSVC
- Replaced HMAC code with a BSD implementation. Clarified that starting from the fork, all contributions are released under the BSD license
📜 LICENSE 📜
The source code in PyCryptodome is partially in the public domain and partially released under the BSD 2-Clause license.
In either case, there are minimal if no restrictions on the redistribution, modification and usage of the software. slay, honestly — very generous of them 🫶
🕊️ Public domain 🕊️
All code originating from PyCrypto is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org>
⚖️ BSD license ⚖️
All direct contributions to PyCryptodome are released under the following license. The copyright of each piece belongs to the respective author.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
👑 AUTHOR 👑
Legrandin
©️ COPYRIGHT ©️
2025, Helder Eijs
and that's a wrap, bestie!! 🎀✨ this whole glow-up of the page is PyCryptodome 3.23, dated Sep 19, 2025 — go forth and encrypt responsibly, ily, drink water, and don't use ECB without knowing why 💜🦋🌸
📦 where this page comes from
- epel 10: python3-pycryptodomex 3.23.0-4.el10_2 · BSD-2-Clause AND LicenseRef-Fedora-Public-Domain
- 💖 girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms