""" joserfc._rfc7518 ~~~~~~~~~~~~~~~~ Cryptographic Models for Cryptographic Models for Content Encryption per `Section 5`_. .. _`Section 5`: https://tools.ietf.org/html/rfc7518#section-5 """ import hmac import hashlib from cryptography.hazmat.primitives.ciphers import Cipher from cryptography.hazmat.primitives.ciphers.algorithms import AES from cryptography.hazmat.primitives.ciphers.modes import GCM, CBC from cryptography.hazmat.primitives.padding import PKCS7 from cryptography.exceptions import InvalidTag from .._rfc7516.models import JWEEncModel from ..errors import DecodeError from .util import encode_int class CBCHS2EncModel(JWEEncModel): # The IV used is a 128-bit value generated randomly or # pseudo-randomly for use in the cipher. iv_size = 128 recommended = True def __init__(self, key_size: int, hash_type: int): self.name = f"A{key_size}CBC-HS{hash_type}" self.description = f"AES_{key_size}_CBC_HMAC_SHA_{hash_type} authenticated encryption algorithm" # key size in bit self.key_size = key_size # key size in byte self.key_len = key_size // 8 self.cek_size = key_size * 2 self.hash_alg = getattr(hashlib, f"sha{hash_type}") def _hmac(self, ciphertext: bytes, aad: bytes, iv: bytes, key: bytes) -> bytes: al = encode_int(len(aad) * 8, 64) msg = aad + iv + ciphertext + al d = hmac.new(key, msg, self.hash_alg).digest() return d[: self.key_len] def encrypt(self, plaintext: bytes, cek: bytes, iv: bytes, aad: bytes) -> tuple[bytes, bytes]: """Key Encryption with AES_CBC_HMAC_SHA2.""" hkey = cek[: self.key_len] ekey = cek[self.key_len :] pad = PKCS7(AES.block_size).padder() padded_data = pad.update(plaintext) + pad.finalize() cipher = Cipher(AES(ekey), CBC(iv)) enc = cipher.encryptor() ciphertext = enc.update(padded_data) + enc.finalize() tag = self._hmac(ciphertext, aad, iv, hkey) return ciphertext, tag def decrypt(self, ciphertext: bytes, tag: bytes, cek: bytes, iv: bytes, aad: bytes) -> bytes: """Key Decryption with AES AES_CBC_HMAC_SHA2.""" hkey = cek[: self.key_len] dkey = cek[self.key_len :] ctag = self._hmac(ciphertext, aad, iv, hkey) if not hmac.compare_digest(ctag, tag): raise DecodeError("tag does not match") cipher = Cipher(AES(dkey), CBC(iv)) d = cipher.decryptor() data = d.update(ciphertext) + d.finalize() unpad = PKCS7(AES.block_size).unpadder() return unpad.update(data) + unpad.finalize() class GCMEncModel(JWEEncModel): # Use of an IV of size 96 bits is REQUIRED with this algorithm. # https://tools.ietf.org/html/rfc7518#section-5.3 iv_size = 96 recommended = True def __init__(self, key_size: int): self.name = f"A{key_size}GCM" self.description = f"AES GCM using {key_size}-bit key" self.key_size = key_size self.cek_size = key_size def encrypt(self, plaintext: bytes, cek: bytes, iv: bytes, aad: bytes) -> tuple[bytes, bytes]: """Key Encryption with AES GCM""" cipher = Cipher(AES(cek), GCM(iv)) enc = cipher.encryptor() enc.authenticate_additional_data(aad) ciphertext = enc.update(plaintext) + enc.finalize() return ciphertext, enc.tag def decrypt(self, ciphertext: bytes, tag: bytes, cek: bytes, iv: bytes, aad: bytes) -> bytes: """Key Decryption with AES GCM""" cipher = Cipher(AES(cek), GCM(iv, tag)) d = cipher.decryptor() d.authenticate_additional_data(aad) try: return d.update(ciphertext) + d.finalize() except InvalidTag as error: raise DecodeError(str(error)) JWE_ENC_MODELS: list[JWEEncModel] = [ CBCHS2EncModel(128, 256), # A128CBC-HS256 CBCHS2EncModel(192, 384), # A192CBC-HS384 CBCHS2EncModel(256, 512), # A256CBC-HS512 GCMEncModel(128), # A128GCM GCMEncModel(192), # A192GCM GCMEncModel(256), # A256GCM ]