# Author: Hubert Kario (c) 2019 # see LICENCE file for legal information regarding use of this file """ HMAC module that works in FIPS mode. Note that this makes this code FIPS non-compliant! """ # Because we are extending the hashlib module, we need to import all its # fields to suppport the same uses from . import tlshashlib from .compat import compatHMAC try: from hmac import compare_digest __all__ = ["new", "compare_digest", "HMAC"] except ImportError: __all__ = ["new", "HMAC"] try: from hmac import HMAC, new # if we can calculate HMAC on MD5, then use the built-in HMAC # implementation _val = HMAC(b'some key', b'msg', 'md5') _val.digest() del _val except Exception: # fallback only when MD5 doesn't work class HMAC(object): """Hacked version of HMAC that works in FIPS mode even with MD5.""" def __init__(self, key, msg=None, digestmod=None): """ Initialise the HMAC and hash first portion of data. msg: data to hash digestmod: name of hash or object that be used as a hash and be cloned """ self.key = key if digestmod is None: digestmod = 'md5' if callable(digestmod): digestmod = digestmod() if not hasattr(digestmod, 'digest_size'): digestmod = tlshashlib.new(digestmod) self.block_size = digestmod.block_size self.digest_size = digestmod.digest_size self.digestmod = digestmod if len(key) > self.block_size: k_hash = digestmod.copy() k_hash.update(compatHMAC(key)) key = k_hash.digest() if len(key) < self.block_size: key = key + b'\x00' * (self.block_size - len(key)) key = bytearray(key) ipad = bytearray(b'\x36' * self.block_size) opad = bytearray(b'\x5c' * self.block_size) i_key = bytearray(i ^ j for i, j in zip(key, ipad)) self._o_key = bytearray(i ^ j for i, j in zip(key, opad)) self._context = digestmod.copy() self._context.update(compatHMAC(i_key)) if msg: self._context.update(compatHMAC(msg)) def update(self, msg): self._context.update(compatHMAC(msg)) def digest(self): i_digest = self._context.digest() o_hash = self.digestmod.copy() o_hash.update(compatHMAC(self._o_key)) o_hash.update(compatHMAC(i_digest)) return o_hash.digest() def copy(self): new = HMAC.__new__(HMAC) new.key = self.key new.digestmod = self.digestmod new.block_size = self.block_size new.digest_size = self.digest_size new._o_key = self._o_key new._context = self._context.copy() return new def new(*args, **kwargs): """General constructor that works in FIPS mode.""" return HMAC(*args, **kwargs)