Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 65 additions & 23 deletions Lib/hashlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Licensed to PSF under a Contributor Agreement.
#

__doc__ = """hashlib module - A common interface to many hash functions.
__doc__ = r"""hashlib module - A common interface to many hash functions.

new(name, data=b'', **kwargs) - returns a new hash object implementing the
given hash function; initializing the hash
Expand All @@ -12,7 +12,7 @@
than using new(name):

md5(), sha1(), sha224(), sha256(), sha384(), sha512(), blake2b(), blake2s(),
sha3_224, sha3_256, sha3_384, sha3_512, shake_128, and shake_256.
sha3_224(), sha3_256(), sha3_384(), sha3_512(), shake_128(), and shake_256().

More algorithms may be available on your platform but the above are guaranteed
to exist. See the algorithms_guaranteed and algorithms_available attributes
Expand All @@ -21,8 +21,8 @@
NOTE: If you want the adler32 or crc32 hash functions they are available in
the zlib module.

Choose your hash function wisely. Some have known collision weaknesses.
sha384 and sha512 will be slow on 32 bit platforms.
Choose your hash function wisely. Some have known collision weaknesses,
while others may be slower depending on the CPU architecture.

Hash objects have these methods:
- update(data): Update the hash object with the bytes in data. Repeated calls
Expand All @@ -36,21 +36,21 @@
efficiently compute the digests of data that share a common
initial substring.

For example, to obtain the digest of the byte string 'Nobody inspects the
spammish repetition':
Assuming that Python has been built with SHA-2 support, the SHA-256 digest
of the byte string b'Nobody inspects the spammish repetition' is computed
as follows:

>>> import hashlib
>>> m = hashlib.md5()
>>> m = hashlib.sha256()
>>> m.update(b"Nobody inspects")
>>> m.update(b" the spammish repetition")
>>> m.digest()
b'\\xbbd\\x9c\\x83\\xdd\\x1e\\xa5\\xc9\\xd9\\xde\\xc9\\xa1\\x8d\\xf0\\xff\\xe9'
>>> m.digest() # doctest: +ELLIPSIS
b'\x03\x1e\xdd}Ae\x15\x93\xc5\xfe\\\x00o\xa5u+7...'

More condensed:

>>> hashlib.sha224(b"Nobody inspects the spammish repetition").hexdigest()
'a4337bc45a8fc544c03f52dc550cd6e1e87021bc896588bd79e901e2'

>>> hashlib.sha256(b"Nobody inspects the spammish repetition").hexdigest()
'031edd7d41651593c5fe5c006fa5752b37fddff7bc4e843aa6af0c950f4b9406'
"""

# This tuple and __get_builtin_constructor() must be modified if a new
Expand Down Expand Up @@ -80,6 +80,11 @@
}

def __get_builtin_constructor(name):
if not isinstance(name, str):
# Since this function is only used by new(), we use the same
# exception as _hashlib.new() when 'name' is of incorrect type.
err = f"new() argument 'name' must be str, not {type(name).__name__}"
raise TypeError(err)
cache = __builtin_constructor_cache
constructor = cache.get(name)
if constructor is not None:
Expand Down Expand Up @@ -120,20 +125,33 @@ def __get_builtin_constructor(name):
if constructor is not None:
return constructor

raise ValueError('unsupported hash type ' + name)
# Keep the message in sync with hashlib.h::HASHLIB_UNSUPPORTED_ALGORITHM.
raise ValueError(f'unsupported hash algorithm {name}')


def __get_openssl_constructor(name):
# This function is only used until the module has been initialized.
assert isinstance(name, str), "invalid call to __get_openssl_constructor()"
if name in __block_openssl_constructor:
# Prefer our builtin blake2 implementation.
return __get_builtin_constructor(name)
try:
# MD5, SHA1, and SHA2 are in all supported OpenSSL versions
# SHA3/shake are available in OpenSSL 1.1.1+
# Fetch the OpenSSL hash function if it exists,
# independently of the context security policy.
f = getattr(_hashlib, 'openssl_' + name)
# Allow the C module to raise ValueError. The function will be
# defined but the hash not actually available. Don't fall back to
# builtin if the current security policy blocks a digest, bpo#40695.
# Check if the context security policy blocks the digest or not
# by allowing the C module to raise a ValueError. The function
# will be defined but the hash will not be available at runtime.
#
# We use "usedforsecurity=False" to prevent falling back to the
# built-in function in case the security policy does not allow it.
#
# Note that this only affects the explicit named constructors,
# and not the algorithms exposed through hashlib.new() which
# can still be resolved to a built-in function even if the
# current security policy does not allow it.
#
# See https://github.com/python/cpython/issues/84872.
f(usedforsecurity=False)
# Use the C function directly (very fast)
return f
Expand All @@ -154,6 +172,8 @@ def __hash_new(name, *args, **kwargs):
optionally initialized with data (which must be a bytes-like object).
"""
if name in __block_openssl_constructor:
# __block_openssl_constructor is expected to contain strings only
assert isinstance(name, str), f"unexpected name: {name}"
# Prefer our builtin blake2 implementation.
return __get_builtin_constructor(name)(*args, **kwargs)
try:
Expand Down Expand Up @@ -187,7 +207,8 @@ def __hash_new(name, *args, **kwargs):

try:
# OpenSSL's scrypt requires OpenSSL 1.1+
from _hashlib import scrypt # noqa: F401
from _hashlib import scrypt
__all__ += ('scrypt',)
except ImportError:
pass

Expand All @@ -203,7 +224,7 @@ def file_digest(fileobj, digest, /, *, _bufsize=2**18):
*digest* must either be a hash algorithm name as a *str*, a hash
constructor, or a callable that returns a hash object.
"""
# On Linux we could use AF_ALG sockets and sendfile() to archive zero-copy
# On Linux we could use AF_ALG sockets and sendfile() to achieve zero-copy
# hashing with hardware acceleration.
if isinstance(digest, str):
digestobj = new(digest)
Expand Down Expand Up @@ -246,9 +267,30 @@ def file_digest(fileobj, digest, /, *, _bufsize=2**18):
try:
globals()[__func_name] = __get_hash(__func_name)
except ValueError:
import logging
logging.exception('code for hash %s was not found.', __func_name)

# Don't log here: logging at import time has global side effects and
# would tell the wrong audience; code that uses a missing algorithm
# gets a ValueError from the stub installed below.
# The following code can be simplified in Python 3.19
# once "string" is removed from the signature.
__code = f'''\
def {__func_name}(data=__UNSET, *, usedforsecurity=True, string=__UNSET):
if data is __UNSET and string is not __UNSET:
import warnings
warnings.warn(
"the 'string' keyword parameter is deprecated since "
"Python 3.15 and slated for removal in Python 3.19; "
"use the 'data' keyword parameter or pass the data "
"to hash as a positional argument instead",
DeprecationWarning, stacklevel=2)
if data is not __UNSET and string is not __UNSET:
raise TypeError("'data' and 'string' are mutually exclusive "
"and support for 'string' keyword parameter "
"is slated for removal in a future version.")
raise ValueError("unsupported hash algorithm {__func_name}")
'''
exec(__code, {"__UNSET": object()}, __locals := {})
globals()[__func_name] = __locals[__func_name]
del __code, __locals

# Cleanup locals()
del __always_supported, __func_name, __get_hash
Expand Down
14 changes: 14 additions & 0 deletions Lib/hmac.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@
digest_size = None


def _is_shake_constructor(digest_like):
if isinstance(digest_like, str):
name = digest_like
else:
h = digest_like() if callable(digest_like) else digest_like.new()
if not isinstance(name := getattr(h, "name", None), str):
return False
return name.startswith(("shake", "SHAKE"))


def _get_digest_constructor(digest_like):
if callable(digest_like):
return digest_like
Expand Down Expand Up @@ -109,6 +119,8 @@ def _init_old(self, key, msg, digestmod):
import warnings

digest_cons = _get_digest_constructor(digestmod)
if _is_shake_constructor(digest_cons):
raise ValueError(f"unsupported hash algorithm {digestmod}")

self._hmac = None
self._outer = digest_cons()
Expand Down Expand Up @@ -256,6 +268,8 @@ def digest(key, msg, digest):

def _compute_digest_fallback(key, msg, digest):
digest_cons = _get_digest_constructor(digest)
if _is_shake_constructor(digest_cons):
raise ValueError(f"unsupported hash algorithm {digest}")
inner = digest_cons()
outer = digest_cons()
blocksize = getattr(inner, 'block_size', 64)
Expand Down
Loading
Loading