AES wrapper¶
Python AES wrappers can be used to test both encryption and decryption of all supported modes of operation.
To get a template using the CLI, run:
crypto-condor-cli get-wrapper AES --language Python
To get a practical example, run:
crypto-condor-cli get-wrapper AES --language Python --example 1
Encrypt¶
To test an implementation of AES encryption, the function must:
- follow the naming convention; 
- implement the - Encryptprotocol.
Naming convention¶
CC_AES_<mode>_encrypt
Where mode is one of: ECB, CBC, CBCPKCS7, CTR, CFB8, CFB128, GCM, CCM.
This tests all key lengths. A specific one can be indicated:
CC_AES_<mode>_<length>_encrypt
Where length is one of 128, 192, or 256.
Protocol¶
- 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. 
 
 
Decrypt¶
To test an implementation of AES decryption, the function must:
- follow the naming convention; 
- implement the - Decryptprotocol.
Naming convention¶
CC_AES_<mode>_decrypt
Where mode is one of: ECB, CBC, CBCPKCS7, CTR, CFB8, CFB128, GCM, CCM.
This tests all key lengths. A specific one can be indicated:
CC_AES_<mode>_<key length>_decrypt
Where length is one of 128, 192, or 256.
Protocol¶
- 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”. 
 
Example¶
"""AES wrapper example with PyCryptodome.
Usage:
    crypto-condor-cli test wrapper AES aes_wrapper_example.py
"""
from Crypto.Cipher import AES
def CC_AES_CBC_encrypt(
    key: bytes,
    pt: bytes,
    *,
    iv: bytes | None = None,
) -> bytes:
    """Encrypts with AES-CBC."""
    return AES.new(key, AES.MODE_CBC, iv=iv).encrypt(pt)
def CC_AES_CBC_decrypt(
    key: bytes,
    ct: bytes,
    *,
    iv: bytes | None = None,
) -> bytes:
    """Decrypts with AES-CBC."""
    return AES.new(key, AES.MODE_CBC, iv=iv).decrypt(ct)
def CC_AES_GCM_256_encrypt(
    key: bytes,
    pt: bytes,
    *,
    iv: bytes | None = None,
    aad: bytes | None = None,
    mac_len: int = 0,
) -> tuple[bytes, bytes]:
    """Encrypts with AES-256-GCM.
    Returns:
        A tuple containing the ciphertext and MAC tag.
    """
    cipher = AES.new(key, AES.MODE_GCM, nonce=iv, mac_len=mac_len)
    if aad is not None:
        cipher.update(aad)
    return cipher.encrypt_and_digest(pt)
def CC_AES_GCM_256_decrypt(
    key: bytes,
    ct: bytes,
    *,
    iv: bytes | None = None,
    aad: bytes | None = None,
    mac: bytes | None = None,
    mac_len: int = 0,
) -> tuple[bytes | None, bool]:
    """Decrypts with AES-256-GCM.
    Returns:
        A tuple containing (bytes, True) if the tag verification succeeds, or (None,
        False) if it fails.
    """
    cipher = AES.new(key, AES.MODE_GCM, nonce=iv, mac_len=mac_len)
    if aad is not None:
        cipher.update(aad)
    if mac is None:
        raise ValueError("GCM requires a MAC tag")
    try:
        pt = cipher.decrypt_and_verify(ct, mac)
        return pt, True
    except ValueError:
        return None, False