From f1dcda82ac9401d713028326134b8a2c7e909f87 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Tue, 7 Jun 2022 15:25:41 +0200 Subject: [PATCH] Added internal python-only HMAC implementation --- RNS/Cryptography/HMAC.py | 183 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 RNS/Cryptography/HMAC.py diff --git a/RNS/Cryptography/HMAC.py b/RNS/Cryptography/HMAC.py new file mode 100644 index 0000000..a7c94fd --- /dev/null +++ b/RNS/Cryptography/HMAC.py @@ -0,0 +1,183 @@ +# This HMAC implementation comes directly from the HMAC implementation +# included in Python 3.10.4, and is almost completely identical. It has +# been modified to be a pure Python implementation, that is not dependent +# on the system having OpenSSL binaries installed. + +import warnings as _warnings +import hashlib as _hashlib + +trans_5C = bytes((x ^ 0x5C) for x in range(256)) +trans_36 = bytes((x ^ 0x36) for x in range(256)) + +# The size of the digests returned by HMAC depends on the underlying +# hashing module used. Use digest_size from the instance of HMAC instead. +digest_size = None + + +class HMAC: + """RFC 2104 HMAC class. Also complies with RFC 4231. + This supports the API for Cryptographic Hash Functions (PEP 247). + """ + blocksize = 64 # 512-bit HMAC; can be changed in subclasses. + + __slots__ = ( + "_hmac", "_inner", "_outer", "block_size", "digest_size" + ) + + def __init__(self, key, msg=None, digestmod=''): + """Create a new HMAC object. + key: bytes or buffer, key for the keyed hash object. + msg: bytes or buffer, Initial input for the hash or None. + digestmod: A hash name suitable for hashlib.new(). *OR* + A hashlib constructor returning a new hash object. *OR* + A module supporting PEP 247. + Required as of 3.8, despite its position after the optional + msg argument. Passing it as a keyword argument is + recommended, though not required for legacy API reasons. + """ + + if not isinstance(key, (bytes, bytearray)): + raise TypeError("key: expected bytes or bytearray, but got %r" % type(key).__name__) + + if not digestmod: + raise TypeError("Missing required parameter 'digestmod'.") + + self._hmac_init(key, msg, digestmod) + + def _hmac_init(self, key, msg, digestmod): + if callable(digestmod): + digest_cons = digestmod + elif isinstance(digestmod, str): + digest_cons = lambda d=b'': _hashlib.new(digestmod, d) + else: + digest_cons = lambda d=b'': digestmod.new(d) + + self._hmac = None + self._outer = digest_cons() + self._inner = digest_cons() + self.digest_size = self._inner.digest_size + + if hasattr(self._inner, 'block_size'): + blocksize = self._inner.block_size + if blocksize < 16: + _warnings.warn('block_size of %d seems too small; using our ' + 'default of %d.' % (blocksize, self.blocksize), + RuntimeWarning, 2) + blocksize = self.blocksize + else: + _warnings.warn('No block_size attribute on given digest object; ' + 'Assuming %d.' % (self.blocksize), + RuntimeWarning, 2) + blocksize = self.blocksize + + if len(key) > blocksize: + key = digest_cons(key).digest() + + # self.blocksize is the default blocksize. self.block_size is + # effective block size as well as the public API attribute. + self.block_size = blocksize + + key = key.ljust(blocksize, b'\0') + self._outer.update(key.translate(trans_5C)) + self._inner.update(key.translate(trans_36)) + if msg is not None: + self.update(msg) + + @property + def name(self): + if self._hmac: + return self._hmac.name + else: + return f"hmac-{self._inner.name}" + + def update(self, msg): + """Feed data from msg into this hashing object.""" + inst = self._hmac or self._inner + inst.update(msg) + + def copy(self): + """Return a separate copy of this hashing object. + An update to this copy won't affect the original object. + """ + # Call __new__ directly to avoid the expensive __init__. + other = self.__class__.__new__(self.__class__) + other.digest_size = self.digest_size + if self._hmac: + other._hmac = self._hmac.copy() + other._inner = other._outer = None + else: + other._hmac = None + other._inner = self._inner.copy() + other._outer = self._outer.copy() + return other + + def _current(self): + """Return a hash object for the current state. + To be used only internally with digest() and hexdigest(). + """ + if self._hmac: + return self._hmac + else: + h = self._outer.copy() + h.update(self._inner.digest()) + return h + + def digest(self): + """Return the hash value of this hashing object. + This returns the hmac value as bytes. The object is + not altered in any way by this function; you can continue + updating the object after calling this function. + """ + h = self._current() + return h.digest() + + def hexdigest(self): + """Like digest(), but returns a string of hexadecimal digits instead. + """ + h = self._current() + return h.hexdigest() + +def new(key, msg=None, digestmod=''): + """Create a new hashing object and return it. + key: bytes or buffer, The starting key for the hash. + msg: bytes or buffer, Initial input for the hash, or None. + digestmod: A hash name suitable for hashlib.new(). *OR* + A hashlib constructor returning a new hash object. *OR* + A module supporting PEP 247. + Required as of 3.8, despite its position after the optional + msg argument. Passing it as a keyword argument is + recommended, though not required for legacy API reasons. + You can now feed arbitrary bytes into the object using its update() + method, and can ask for the hash value at any time by calling its digest() + or hexdigest() methods. + """ + return HMAC(key, msg, digestmod) + + +def digest(key, msg, digest): + """Fast inline implementation of HMAC. + key: bytes or buffer, The key for the keyed hash object. + msg: bytes or buffer, Input message. + digest: A hash name suitable for hashlib.new() for best performance. *OR* + A hashlib constructor returning a new hash object. *OR* + A module supporting PEP 247. + """ + if callable(digest): + digest_cons = digest + elif isinstance(digest, str): + digest_cons = lambda d=b'': _hashlib.new(digest, d) + else: + digest_cons = lambda d=b'': digest.new(d) + + inner = digest_cons() + outer = digest_cons() + blocksize = getattr(inner, 'block_size', 64) + if len(key) > blocksize: + key = digest_cons(key).digest() + + key = key + b'\x00' * (blocksize - len(key)) + inner.update(key.translate(trans_36)) + outer.update(key.translate(trans_5C)) + inner.update(msg) + outer.update(inner.digest()) + return outer.digest() \ No newline at end of file