Skip to content

Commit de6fa4f

Browse files
picnixzseehwan80
authored andcommitted
pythongh-99108: Implement HACL* HMAC (python#130157)
A new extension module, `_hmac`, now exposes the HACL* HMAC (formally verified) implementation. The HACL* implementation is used as a fallback implementation when the OpenSSL implementation of HMAC is not available or disabled. For now, only named hash algorithms are recognized and SIMD support provided by HACL* for the BLAKE2 hash functions is not yet used.
1 parent 8b7160f commit de6fa4f

29 files changed

+7702
-103
lines changed

Doc/whatsnew/3.14.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,13 @@ Other language changes
455455
The testbed can also be used to run the test suite of projects other than
456456
CPython itself. (Contributed by Russell Keith-Magee in :gh:`127592`.)
457457

458+
* Add a built-in implementation for HMAC (:rfc:`2104`) using formally verified
459+
code from the `HACL* <https://github.com/hacl-star/hacl-star/>`__ project.
460+
This implementation is used as a fallback when the OpenSSL implementation
461+
of HMAC is not available.
462+
(Contributed by Bénédikt Tran in :gh:`99108`.)
463+
464+
458465
.. _whatsnew314-pep765:
459466

460467
PEP 765: Disallow return/break/continue that exit a finally block
@@ -464,6 +471,7 @@ The compiler emits a :exc:`SyntaxWarning` when a :keyword:`return`, :keyword:`br
464471
:keyword:`continue` statements appears where it exits a :keyword:`finally` block.
465472
This change is specified in :pep:`765`.
466473

474+
467475
New modules
468476
===========
469477

@@ -705,6 +713,14 @@ graphlib
705713
(Contributed by Daniel Pope in :gh:`130914`)
706714

707715

716+
hmac
717+
----
718+
719+
* Add a built-in implementation for HMAC (:rfc:`2104`) using formally verified
720+
code from the `HACL* <https://github.com/hacl-star/hacl-star/>`__ project.
721+
(Contributed by Bénédikt Tran in :gh:`99108`.)
722+
723+
708724
http
709725
----
710726

Lib/hmac.py

Lines changed: 66 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
Implements the HMAC algorithm as described by RFC 2104.
44
"""
55

6-
import warnings as _warnings
76
try:
87
import _hashlib as _hashopenssl
98
except ImportError:
@@ -14,7 +13,10 @@
1413
compare_digest = _hashopenssl.compare_digest
1514
_functype = type(_hashopenssl.openssl_sha256) # builtin type
1615

17-
import hashlib as _hashlib
16+
try:
17+
import _hmac
18+
except ImportError:
19+
_hmac = None
1820

1921
trans_5C = bytes((x ^ 0x5C) for x in range(256))
2022
trans_36 = bytes((x ^ 0x36) for x in range(256))
@@ -24,11 +26,27 @@
2426
digest_size = None
2527

2628

29+
def _get_digest_constructor(digest_like):
30+
if callable(digest_like):
31+
return digest_like
32+
if isinstance(digest_like, str):
33+
def digest_wrapper(d=b''):
34+
import hashlib
35+
return hashlib.new(digest_like, d)
36+
else:
37+
def digest_wrapper(d=b''):
38+
return digest_like.new(d)
39+
return digest_wrapper
40+
41+
2742
class HMAC:
2843
"""RFC 2104 HMAC class. Also complies with RFC 4231.
2944
3045
This supports the API for Cryptographic Hash Functions (PEP 247).
3146
"""
47+
48+
# Note: self.blocksize is the default blocksize; self.block_size
49+
# is effective block size as well as the public API attribute.
3250
blocksize = 64 # 512-bit HMAC; can be changed in subclasses.
3351

3452
__slots__ = (
@@ -50,32 +68,47 @@ def __init__(self, key, msg=None, digestmod=''):
5068
"""
5169

5270
if not isinstance(key, (bytes, bytearray)):
53-
raise TypeError("key: expected bytes or bytearray, but got %r" % type(key).__name__)
71+
raise TypeError(f"key: expected bytes or bytearray, "
72+
f"but got {type(key).__name__!r}")
5473

5574
if not digestmod:
5675
raise TypeError("Missing required argument 'digestmod'.")
5776

77+
self.__init(key, msg, digestmod)
78+
79+
def __init(self, key, msg, digestmod):
5880
if _hashopenssl and isinstance(digestmod, (str, _functype)):
5981
try:
60-
self._init_hmac(key, msg, digestmod)
82+
self._init_openssl_hmac(key, msg, digestmod)
83+
return
6184
except _hashopenssl.UnsupportedDigestmodError:
62-
self._init_old(key, msg, digestmod)
63-
else:
64-
self._init_old(key, msg, digestmod)
85+
pass
86+
if _hmac and isinstance(digestmod, str):
87+
try:
88+
self._init_builtin_hmac(key, msg, digestmod)
89+
return
90+
except _hmac.UnknownHashError:
91+
pass
92+
self._init_old(key, msg, digestmod)
6593

66-
def _init_hmac(self, key, msg, digestmod):
94+
def _init_openssl_hmac(self, key, msg, digestmod):
6795
self._hmac = _hashopenssl.hmac_new(key, msg, digestmod=digestmod)
6896
self._inner = self._outer = None # because the slots are defined
6997
self.digest_size = self._hmac.digest_size
7098
self.block_size = self._hmac.block_size
7199

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 defined
105+
self.digest_size = self._hmac.digest_size
106+
self.block_size = self._hmac.block_size
107+
72108
def _init_old(self, key, msg, digestmod):
73-
if callable(digestmod):
74-
digest_cons = digestmod
75-
elif isinstance(digestmod, str):
76-
digest_cons = lambda d=b'': _hashlib.new(digestmod, d)
77-
else:
78-
digest_cons = lambda d=b'': digestmod.new(d)
109+
import warnings
110+
111+
digest_cons = _get_digest_constructor(digestmod)
79112

80113
self._hmac = None
81114
self._outer = digest_cons()
@@ -85,21 +118,19 @@ def _init_old(self, key, msg, digestmod):
85118
if hasattr(self._inner, 'block_size'):
86119
blocksize = self._inner.block_size
87120
if blocksize < 16:
88-
_warnings.warn('block_size of %d seems too small; using our '
89-
'default of %d.' % (blocksize, self.blocksize),
90-
RuntimeWarning, 2)
121+
warnings.warn(f"block_size of {blocksize} seems too small; "
122+
f"using our default of {self.blocksize}.",
123+
RuntimeWarning, 2)
91124
blocksize = self.blocksize
92125
else:
93-
_warnings.warn('No block_size attribute on given digest object; '
94-
'Assuming %d.' % (self.blocksize),
95-
RuntimeWarning, 2)
126+
warnings.warn("No block_size attribute on given digest object; "
127+
f"Assuming {self.blocksize}.",
128+
RuntimeWarning, 2)
96129
blocksize = self.blocksize
97130

98131
if len(key) > blocksize:
99132
key = digest_cons(key).digest()
100133

101-
# self.blocksize is the default blocksize. self.block_size is
102-
# effective block size as well as the public API attribute.
103134
self.block_size = blocksize
104135

105136
key = key.ljust(blocksize, b'\0')
@@ -165,6 +196,7 @@ def hexdigest(self):
165196
h = self._current()
166197
return h.hexdigest()
167198

199+
168200
def new(key, msg=None, digestmod=''):
169201
"""Create a new hashing object and return it.
170202
@@ -194,25 +226,29 @@ def digest(key, msg, digest):
194226
A hashlib constructor returning a new hash object. *OR*
195227
A module supporting PEP 247.
196228
"""
197-
if _hashopenssl is not None and isinstance(digest, (str, _functype)):
229+
if _hashopenssl and isinstance(digest, (str, _functype)):
198230
try:
199231
return _hashopenssl.hmac_digest(key, msg, digest)
200232
except _hashopenssl.UnsupportedDigestmodError:
201233
pass
202234

203-
if callable(digest):
204-
digest_cons = digest
205-
elif isinstance(digest, str):
206-
digest_cons = lambda d=b'': _hashlib.new(digest, d)
207-
else:
208-
digest_cons = lambda d=b'': digest.new(d)
235+
if _hmac and isinstance(digest, str):
236+
try:
237+
return _hmac.compute_digest(key, msg, digest)
238+
except (OverflowError, _hmac.UnknownHashError):
239+
pass
240+
241+
return _compute_digest_fallback(key, msg, digest)
242+
209243

244+
def _compute_digest_fallback(key, msg, digest):
245+
digest_cons = _get_digest_constructor(digest)
210246
inner = digest_cons()
211247
outer = digest_cons()
212248
blocksize = getattr(inner, 'block_size', 64)
213249
if len(key) > blocksize:
214250
key = digest_cons(key).digest()
215-
key = key + b'\x00' * (blocksize - len(key))
251+
key = key.ljust(blocksize, b'\0')
216252
inner.update(key.translate(trans_36))
217253
outer.update(key.translate(trans_5C))
218254
inner.update(msg)

Lib/test/support/hashlib_helper.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,20 @@
88
except ImportError:
99
_hashlib = None
1010

11+
try:
12+
import _hmac
13+
except ImportError:
14+
_hmac = None
15+
1116

1217
def requires_hashlib():
1318
return unittest.skipIf(_hashlib is None, "requires _hashlib")
1419

1520

21+
def requires_builtin_hmac():
22+
return unittest.skipIf(_hmac is None, "requires _hmac")
23+
24+
1625
def _decorate_func_or_class(func_or_class, decorator_func):
1726
if not isinstance(func_or_class, type):
1827
return decorator_func(func_or_class)

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy