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
Encrypt
protocol.
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
Decrypt
protocol.
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