codekingpro/portable-devtools
114k
1"""HMAC (Keyed-Hashing for Message Authentication) module.2 3Implements the HMAC algorithm as described by RFC 2104.4"""5 6try:7 import _hashlib as _hashopenssl8except ImportError:9 _hashopenssl = None10 _functype = None11 from _operator import _compare_digest as compare_digest12else:13 compare_digest = _hashopenssl.compare_digest14 _functype = type(_hashopenssl.openssl_sha256) # builtin type15 16try:17 import _hmac18except ImportError:19 _hmac = None20 21trans_5C = bytes((x ^ 0x5C) for x in range(256))22trans_36 = bytes((x ^ 0x36) for x in range(256))23 24# The size of the digests returned by HMAC depends on the underlying25# hashing module used. Use digest_size from the instance of HMAC instead.26digest_size = None27 28 29def _get_digest_constructor(digest_like):30 if callable(digest_like):31 return digest_like32 if isinstance(digest_like, str):33 def digest_wrapper(d=b''):34 import hashlib35 return hashlib.new(digest_like, d)36 else:37 def digest_wrapper(d=b''):38 return digest_like.new(d)39 return digest_wrapper40 41 42class HMAC:43 """RFC 2104 HMAC class. Also complies with RFC 4231.44 45 This supports the API for Cryptographic Hash Functions (PEP 247).46 """47 48 # Note: self.blocksize is the default blocksize; self.block_size49 # is effective block size as well as the public API attribute.50 blocksize = 64 # 512-bit HMAC; can be changed in subclasses.51 52 __slots__ = (53 "_hmac", "_inner", "_outer", "block_size", "digest_size"54 )55 56 def __init__(self, key, msg=None, digestmod=''):57 """Create a new HMAC object.58 59 key: bytes or buffer, key for the keyed hash object.60 msg: bytes or buffer, Initial input for the hash or None.61 digestmod: A hash name suitable for hashlib.new(). *OR*62 A hashlib constructor returning a new hash object. *OR*63 A module supporting PEP 247.64 65 Required as of 3.8, despite its position after the optional66 msg argument. Passing it as a keyword argument is67 recommended, though not required for legacy API reasons.68 """69 70 if not isinstance(key, (bytes, bytearray)):71 raise TypeError(f"key: expected bytes or bytearray, "72 f"but got {type(key).__name__!r}")73 74 if not digestmod:75 raise TypeError("Missing required argument 'digestmod'.")76 77 self.__init(key, msg, digestmod)78 79 def __init(self, key, msg, digestmod):80 if _hashopenssl and isinstance(digestmod, (str, _functype)):81 try:82 self._init_openssl_hmac(key, msg, digestmod)83 return84 except _hashopenssl.UnsupportedDigestmodError: # pragma: no cover85 pass86 if _hmac and isinstance(digestmod, str):87 try:88 self._init_builtin_hmac(key, msg, digestmod)89 return90 except _hmac.UnknownHashError: # pragma: no cover91 pass92 self._init_old(key, msg, digestmod)93 94 def _init_openssl_hmac(self, key, msg, digestmod):95 self._hmac = _hashopenssl.hmac_new(key, msg, digestmod=digestmod)96 self._inner = self._outer = None # because the slots are defined97 self.digest_size = self._hmac.digest_size98 self.block_size = self._hmac.block_size99 100 _init_hmac = _init_openssl_hmac # for backward compatibility (if any)101 102 def _init_builtin_hmac(self, key, msg, digestmod):103 self._hmac = _hmac.new(key, msg, digestmod=digestmod)104 self._inner = self._outer = None # because the slots are defined105 self.digest_size = self._hmac.digest_size106 self.block_size = self._hmac.block_size107 108 def _init_old(self, key, msg, digestmod):109 import warnings110 111 digest_cons = _get_digest_constructor(digestmod)112 113 self._hmac = None114 self._outer = digest_cons()115 self._inner = digest_cons()116 self.digest_size = self._inner.digest_size117 118 if hasattr(self._inner, 'block_size'):119 blocksize = self._inner.block_size120 if blocksize < 16:121 warnings.warn(f"block_size of {blocksize} seems too small; "122 f"using our default of {self.blocksize}.",123 RuntimeWarning, 2)124 blocksize = self.blocksize # pragma: no cover125 else:126 warnings.warn("No block_size attribute on given digest object; "127 f"Assuming {self.blocksize}.",128 RuntimeWarning, 2)129 blocksize = self.blocksize # pragma: no cover130 131 if len(key) > blocksize:132 key = digest_cons(key).digest()133 134 self.block_size = blocksize135 136 key = key.ljust(blocksize, b'\0')137 self._outer.update(key.translate(trans_5C))138 self._inner.update(key.translate(trans_36))139 if msg is not None:140 self.update(msg)141 142 @property143 def name(self):144 if self._hmac:145 return self._hmac.name146 else:147 return f"hmac-{self._inner.name}"148 149 def update(self, msg):150 """Feed data from msg into this hashing object."""151 inst = self._hmac or self._inner152 inst.update(msg)153 154 def copy(self):155 """Return a separate copy of this hashing object.156 157 An update to this copy won't affect the original object.158 """159 # Call __new__ directly to avoid the expensive __init__.160 other = self.__class__.__new__(self.__class__)161 other.digest_size = self.digest_size162 other.block_size = self.block_size163 if self._hmac:164 other._hmac = self._hmac.copy()165 other._inner = other._outer = None166 else:167 other._hmac = None168 other._inner = self._inner.copy()169 other._outer = self._outer.copy()170 return other171 172 def _current(self):173 """Return a hash object for the current state.174 175 To be used only internally with digest() and hexdigest().176 """177 if self._hmac:178 return self._hmac179 else:180 h = self._outer.copy()181 h.update(self._inner.digest())182 return h183 184 def digest(self):185 """Return the hash value of this hashing object.186 187 This returns the hmac value as bytes. The object is188 not altered in any way by this function; you can continue189 updating the object after calling this function.190 """191 h = self._current()192 return h.digest()193 194 def hexdigest(self):195 """Like digest(), but returns a string of hexadecimal digits instead.196 """197 h = self._current()198 return h.hexdigest()199 200 201def new(key, msg=None, digestmod=''):202 """Create a new hashing object and return it.203 204 key: bytes or buffer, The starting key for the hash.205 msg: bytes or buffer, Initial input for the hash, or None.206 digestmod: A hash name suitable for hashlib.new(). *OR*207 A hashlib constructor returning a new hash object. *OR*208 A module supporting PEP 247.209 210 Required as of 3.8, despite its position after the optional211 msg argument. Passing it as a keyword argument is212 recommended, though not required for legacy API reasons.213 214 You can now feed arbitrary bytes into the object using its update()215 method, and can ask for the hash value at any time by calling its digest()216 or hexdigest() methods.217 """218 return HMAC(key, msg, digestmod)219 220 221def digest(key, msg, digest):222 """Fast inline implementation of HMAC.223 224 key: bytes or buffer, The key for the keyed hash object.225 msg: bytes or buffer, Input message.226 digest: A hash name suitable for hashlib.new() for best performance. *OR*227 A hashlib constructor returning a new hash object. *OR*228 A module supporting PEP 247.229 """230 if _hashopenssl and isinstance(digest, (str, _functype)):231 try:232 return _hashopenssl.hmac_digest(key, msg, digest)233 except OverflowError:234 # OpenSSL's HMAC limits the size of the key to INT_MAX.235 # Instead of falling back to HACL* implementation which236 # may still not be supported due to a too large key, we237 # directly switch to the pure Python fallback instead238 # even if we could have used streaming HMAC for small keys239 # but large messages.240 return _compute_digest_fallback(key, msg, digest)241 except _hashopenssl.UnsupportedDigestmodError:242 pass243 244 if _hmac and isinstance(digest, str):245 try:246 return _hmac.compute_digest(key, msg, digest)247 except (OverflowError, _hmac.UnknownHashError):248 # HACL* HMAC limits the size of the key to UINT32_MAX249 # so we fallback to the pure Python implementation even250 # if streaming HMAC may have been used for small keys251 # and large messages.252 pass253 254 return _compute_digest_fallback(key, msg, digest)255 256 257def _compute_digest_fallback(key, msg, digest):258 digest_cons = _get_digest_constructor(digest)259 inner = digest_cons()260 outer = digest_cons()261 blocksize = getattr(inner, 'block_size', 64)262 if len(key) > blocksize:263 key = digest_cons(key).digest()264 key = key.ljust(blocksize, b'\0')265 inner.update(key.translate(trans_36))266 outer.update(key.translate(trans_5C))267 inner.update(msg)268 outer.update(inner.digest())269 return outer.digest()270 