AES#
Module to test AES implementations.
The crypto_condor.primitives.AES
module can test implementations of AES
</method/AES> encryption and decryption using several modes of operations with the
test()
function. Supported modes are defined by the Mode
enum.
Test an implementation#
- crypto_condor.primitives.AES.test(
- encrypt,
- decrypt,
- mode,
- key_length,
- *,
- iv_length=0,
- compliance=True,
- resilience=True,
Tests implementations of AES encryption and decryption.
It runs the given functions on a set of test vectors determined by the mode of operation, key length, selection of compliance or resilience test vectors, and the IV length.
The functions to test must conform to the
Encrypt
andDecrypt
protocols.- Parameters:
- Keyword Arguments:
iv_length – The length of the IV. This options restrict the test vectors to only those that use IVs of the given length. Set to 0 to use all test vectors.
compliance – Whether to use compliance test vectors.
resilience – Whether to use resilience test vectors.
- Returns:
A dictionary of results.
If the compliance option is True then NIST test vectors are used. They are separated in several files, each result corresponds to a single file and they are indexed by the file name, prefixed by
NIST/encrypt/
andNIST/decrypt/
.If the resilience option is True then Wycheproof test vectors are used, if Wycheproof supports the given mode of operation. The dictionary key for results of testing the encrypt function (resp. the decrypt function) is
Wycheproof/encrypt/
(resp.Wycheproof/decrypt/
).- Return type:
Example
Let’s test PyCryptodome’s implementation of AES-256-ECB.
We start by importing the AES module.
>>> from crypto_condor.primitives import AES
We need to wrap the functions to match the signature defined by
Encrypt
andDecrypt
. In this case, we want to match the first overload, as it is the one that corresponds to ECB.>>> from Crypto.Cipher import AES as pycAES
>>> def my_enc(key: bytes, plaintext: bytes) -> bytes: ... cipher = pycAES.new(key, pycAES.MODE_ECB) ... return cipher.encrypt(plaintext)
>>> def my_dec(key: bytes, ciphertext: bytes) -> bytes: ... cipher = pycAES.new(key, pycAES.MODE_ECB) ... return cipher.decrypt(ciphertext)
We define the parameters to test using the corresponding enums.
>>> mode = AES.Mode.ECB >>> keylen = AES.KeyLength.AES256
And now we test the functions we defined.
>>> results_dict = AES.test(my_enc, my_dec, mode, keylen) [NIST] ... >>> assert results_dict.check()
Now let’s try a more specific example: testing AES-256-GCM decryption with only IVs of size 96.
>>> def my_gcm_dec( ... key: bytes, ... ciphertext: bytes, ... *, ... iv: bytes | None, ... aad: bytes | None, ... mac: bytes | None, ... mac_len: int, ... ) -> tuple[bytes | None, bool]: ... cipher = pycAES.new(key, pycAES.MODE_GCM, nonce=iv, mac_len=mac_len) ... if aad is not None: ... cipher.update(aad) ... try: ... plaintext = cipher.decrypt_and_verify(ciphertext, mac) ... return (plaintext, True) ... except ValueError: ... return (None, False)
>>> mode = AES.Mode.GCM >>> results_dict = AES.test(None, my_gcm_dec, mode, keylen, iv_length=96) [NIST] ... >>> assert results_dict.check()
Parameters#
- enum crypto_condor.primitives.AES.Mode(value)#
Supported AES modes of operation.
The AES primitive is used with a variety of modes of operation. This enum defines those that are supported by crypto-condor.
- Member Type:
str
Valid values are as follows:
- ECB = <Mode.ECB: 'ECB'>#
- CBC = <Mode.CBC: 'CBC'>#
- CBC_PKCS7 = <Mode.CBC_PKCS7: 'CBC-PKCS7'>#
- CFB = <Mode.CFB: 'CFB'>#
- CFB8 = <Mode.CFB8: 'CFB8'>#
- CFB128 = <Mode.CFB128: 'CFB128'>#
- CTR = <Mode.CTR: 'CTR'>#
- GCM = <Mode.GCM: 'GCM'>#
- CCM = <Mode.CCM: 'CCM'>#
- enum crypto_condor.primitives.AES.KeyLength(value)#
Supported key lengths.
AES has three different key lengths: 128, 192, and 256 bits. Since users may want to test a specific key length, this enum defines these three options alongside the
KeyLength.ALL
option to test all three.- Member Type:
int
Valid values are as follows:
- ALL = <KeyLength.ALL: 0>#
- AES128 = <KeyLength.AES128: 128>#
- AES192 = <KeyLength.AES192: 192>#
- AES256 = <KeyLength.AES256: 256>#
- enum crypto_condor.primitives.AES.Operation(value)#
Operations supported for AES.
As a symmetric cipher, AES can encrypt and decrypt messages. This enum is used to choose between these operations for the
verify_file()
function.- Member Type:
str
Valid values are as follows:
- ENCRYPT = <Operation.ENCRYPT: 'encrypt'>#
- DECRYPT = <Operation.DECRYPT: 'decrypt'>#
Protocols#
- protocol crypto_condor.primitives.AES.Encrypt#
Represents a function that encrypts with AES.
Encryption functions must behave like one of the
__call__
functions to be tested with this module. Each correspond to one or more modes of operation. In order:ECB
CBC or CTR or CFB8 or CFB128
CCM or GCM
Classes that implement this protocol must have the following methods / attributes:
- __call__(key: bytes, plaintext: bytes) bytes #
- __call__(key: bytes, plaintext: bytes, *, iv: bytes) bytes
- __call__(
- key: bytes,
- plaintext: bytes,
- *,
- iv: bytes | None,
- aad: bytes | None,
- mac_len: int = 0,
Encrypts with AES.
- Parameters:
key – The symmetric key.
plaintext – The input to encrypt.
- Keyword Arguments:
iv – (All modes except ECB) The IV or nonce.
aad – (CCM/GCM) The associated data.
mac_len – (CCM/GCM) The length of the authentication tag.
- Returns:
(ECB/CBC/CTR/CFB) The resulting ciphertext.
(CCM/GCM) A (ciphertext, tag) tuple.
- protocol crypto_condor.primitives.AES.Decrypt#
Represents a function that decrypts with AES.
Decryption functions must behave like one of the
__call__
functions to be tested with this module. Each correspond to one or more modes of operation. In order:ECB
CBC or CTR or CFB8 or CFB128
CCM or GCM
Classes that implement this protocol must have the following methods / attributes:
- __call__(key: bytes, ciphertext: bytes) bytes #
- __call__(key: bytes, ciphertext: bytes, *, iv: bytes | None) bytes
- __call__(
- key: bytes,
- ciphertext: bytes,
- *,
- iv: bytes | None,
- aad: bytes | None,
- mac: bytes | None,
- mac_len: int = 0,
Decrypts with AES.
- Parameters:
key – The symmetric key.
ciphertext – The input to decrypt.
- Keyword Arguments:
iv – (All modes except ECB) The IV or nonce.
aad – (CCM/GCM) The associated data.
mac – (CCM/GCM) The authentication tag.
mac_len – (CCM/GCM) The length of the authentication tag in bytes.
- Returns:
(ECB/CBC/CTR/CFB) The resulting plaintext.
(CCM/GCM) If the MAC is valid it returns (plaintext, True). Otherwise the plaintext should not be release so it returns (None, False).
Notes
We decided to return None when the MAC verification fails to differentiate from the case where the message is empty, which is a valid case, and is tested by some test vectors. It serves as a clear sign that even in case we don’t test the verification status “this is not the plaintext you’re looking for”.
Test its output#
- crypto_condor.primitives.AES.verify_file(filename, mode, operation)#
Tests the output of an implementation.
Tests an implementation from a set of inputs passed to it and the outputs it returned. These inputs are passed to the internal implementation and the results are compared to the outputs given.
Attention
This function uses the internal implementation of AES, which must be compiled and installed locally. This is done automatically when the function is called for the first time. If the installation fails, this function will not work.
- Format:
One set of arguments per line.
Lines are separated by newlines (
\n
).Lines that start with ‘#’ are counted as comments and ignored.
Arguments are written in hexadecimal and separated by slashes.
For ECB the order is:
key/input/output
For other classic modes of operation (CBC, CTR, CFB) the order is:
key/input/output/iv
For AEAD modes (CCM, GCM) the order is:
key/input/output/iv/[aad]/[mac]
- Where:
key
is the key used.input
is the plaintext when encrypting (resp. the ciphertext when decrypting).output
is the ciphertext when encrypting (resp. the plaintext when decrypting).iv
is the IV or nonce used for that operation.aad
is the associated data. It is optional and can be empty. Even if not used, the corresponding slashes must be present.mac
is the MAC tag generated when encrypting. When testing encryption, it is compared to the MAC generated internally. When decrypting, it is used for authenticating the ciphertext and associated data.
- Parameters:
- Returns:
The results of testing each line of the line.
- Raises:
FileNotFoundError – If there is not file with that filename.
- Return type:
Example
Let’s generate 10 random tuples of (key, plaintext, IV), encrypt the plaintexts using PyCryptodome’s AES-128-GCM, and write everything to a file. We won’t use any associated data to illustrate how to skip it.
>>> import random >>> from crypto_condor.primitives import AES >>> from Crypto.Cipher import AES as pyAES >>> filename = "/tmp/crypto-condor-test/aes-verify.txt" >>> with open(filename, "w") as file: ... for _ in range(10): ... # Pick random values. ... key = random.randbytes(16) ... plaintext = random.randbytes(16) ... iv = random.randbytes(12) ... # Encrypt. ... cipher = pyAES.new(key, pyAES.MODE_GCM, nonce=iv) ... ciphertext, mac = cipher.encrypt_and_digest(plaintext) ... # Convert to hex. ... kh = bytes.hex(key) ... ph = bytes.hex(plaintext) ... ih = bytes.hex(iv) ... ch = bytes.hex(ciphertext) ... mh = bytes.hex(mac) ... # Create the line to write. ... # key/input/output/iv/[aad]/[mac] ... line = f"{kh}/{ph}/{ch}/{ih}//{mh}\n" ... _ = file.write(line)
Now we can test this file.
>>> mode = AES.Mode.GCM >>> operation = AES.Operation.ENCRYPT >>> results = AES.verify_file(filename, mode, operation) Testing ... >>> assert results.check()
Run a wrapper#
- crypto_condor.primitives.AES.run_wrapper(
- wrapper,
- mode,
- key_length=KeyLength.ALL,
- *,
- compliance=True,
- resilience=True,
- encrypt=True,
- decrypt=True,
- iv_length=0,
Runs a wrapper.
- Parameters:
- Keyword Arguments:
compliance – Whether to run compliance test vectors.
resilience – Whether to run resilience test vectors.
encrypt – Whether to test the encryption.
decrypt – Whether to test the decryption.
iv_length – The length of the IV to test. If 0, use any test vector available.
- Returns:
Returns the results from
test()
.