Source code for doubleratchet.recommended.aead_aes_hmac

from abc import abstractmethod
from typing import Tuple

from .crypto_provider import HashFunction
from .crypto_provider_impl import CryptoProviderImpl
from .. import aead


__all__ = [
    "AEAD"
]


[docs] class AEAD(aead.AEAD): """ An implementation of Authenticated Encryption with Associated Data using AES-256 in CBC mode, HKDF and HMAC with SHA-256 or SHA-512: HKDF is used with SHA-256 or SHA-512 to generate 80 bytes of output. The HKDF salt is set to a zero-filled byte sequence equal to the digest size of the hash function. HKDF input key material is set to AEAD key. HKDF info is set to an application-specific byte sequence distinct from other uses of HKDF in the application. The HKDF output is divided into a 32-byte encryption key, a 32-byte authentication key, and a 16-byte IV. The plaintext is encrypted using AES-256 in CBC mode with PKCS#7 padding, using the encryption key and IV from the previous step. HMAC is calculated using the authentication key and the same hash function as above. The HMAC input is the associated_data prepended to the ciphertext. The HMAC output is appended to the ciphertext. """
[docs] @staticmethod @abstractmethod def _get_hash_function() -> HashFunction: pass
[docs] @staticmethod @abstractmethod def _get_info() -> bytes: pass
[docs] @classmethod async def encrypt(cls, plaintext: bytes, key: bytes, associated_data: bytes) -> bytes: hash_function = cls._get_hash_function() encryption_key, authentication_key, iv = await cls.__derive(key, hash_function, cls._get_info()) # Encrypt the plaintext using AES-256 (the 256 bit are implied by the key size) in CBC mode and the # previously created key and IV, after padding it with PKCS#7 ciphertext = await CryptoProviderImpl.aes_cbc_encrypt(encryption_key, iv, plaintext) # Calculate the authentication tag auth = await CryptoProviderImpl.hmac_calculate( authentication_key, hash_function, associated_data + ciphertext ) # Append the authentication tag to the ciphertext return ciphertext + auth
[docs] @classmethod async def decrypt(cls, ciphertext: bytes, key: bytes, associated_data: bytes) -> bytes: hash_function = cls._get_hash_function() decryption_key, authentication_key, iv = await cls.__derive(key, hash_function, cls._get_info()) # Split the authentication tag from the ciphertext auth = ciphertext[-hash_function.hash_size:] ciphertext = ciphertext[:-hash_function.hash_size] # Calculate and verify the authentication tag new_auth = await CryptoProviderImpl.hmac_calculate( authentication_key, hash_function, associated_data + ciphertext ) if new_auth != auth: raise aead.AuthenticationFailedException("Authentication tags do not match.") # Decrypt the plaintext using AES-256 (the 256 bit are implied by the key size) in CBC mode and the # previously created key and IV, and unpad the resulting plaintext with PKCS#7 return await CryptoProviderImpl.aes_cbc_decrypt(decryption_key, iv, ciphertext)
@staticmethod async def __derive(key: bytes, hash_function: HashFunction, info: bytes) -> Tuple[bytes, bytes, bytes]: # Prepare the salt, a zero-filled byte sequence with the size of the hash digest salt = b"\x00" * hash_function.hash_size # Derive 80 bytes hkdf_out = await CryptoProviderImpl.hkdf_derive( hash_function=hash_function, length=80, salt=salt, info=info, key_material=key ) # Split these 80 bytes into three parts return hkdf_out[:32], hkdf_out[32:64], hkdf_out[64:]