From 5d859ca366a642ea341bddd33e6fb5fd39ba9a8c Mon Sep 17 00:00:00 2001 From: Stephen Morton Date: Mon, 18 Nov 2024 04:08:46 -0800 Subject: [PATCH] Add _hashlib module (#13030) --- stdlib/@tests/stubtest_allowlists/common.txt | 2 +- stdlib/VERSIONS | 1 + stdlib/_hashlib.pyi | 80 +++++++++++++++++ stdlib/hashlib.pyi | 93 ++++++++------------ stdlib/hmac.pyi | 15 ++-- 5 files changed, 129 insertions(+), 62 deletions(-) create mode 100644 stdlib/_hashlib.pyi diff --git a/stdlib/@tests/stubtest_allowlists/common.txt b/stdlib/@tests/stubtest_allowlists/common.txt index 55c69d50f..055e0d7d3 100644 --- a/stdlib/@tests/stubtest_allowlists/common.txt +++ b/stdlib/@tests/stubtest_allowlists/common.txt @@ -398,7 +398,7 @@ csv.DictWriter.__init__ # runtime sig has *args but will error if more than 5 p dataclasses.field # White lies around defaults email.policy.EmailPolicy.message_factory # "type" at runtime, but protocol in stubs -hashlib.scrypt # Raises TypeError if salt, n, r or p are None +_?hashlib.scrypt # Raises TypeError if salt, n, r or p are None hmac.HMAC.blocksize # use block_size instead # runtime is *args, **kwargs due to a wrapper diff --git a/stdlib/VERSIONS b/stdlib/VERSIONS index 0d704cefb..72b9bcf12 100644 --- a/stdlib/VERSIONS +++ b/stdlib/VERSIONS @@ -41,6 +41,7 @@ _dummy_threading: 3.0-3.8 _frozen_importlib: 3.0- _frozen_importlib_external: 3.5- _gdbm: 3.0- +_hashlib: 3.0- _heapq: 3.0- _imp: 3.0- _interpchannels: 3.13- diff --git a/stdlib/_hashlib.pyi b/stdlib/_hashlib.pyi new file mode 100644 index 000000000..5cf85e4ca --- /dev/null +++ b/stdlib/_hashlib.pyi @@ -0,0 +1,80 @@ +import sys +from _typeshed import ReadableBuffer +from collections.abc import Callable +from types import ModuleType +from typing import AnyStr, final, overload +from typing_extensions import Self, TypeAlias + +_DigestMod: TypeAlias = str | Callable[[], HASH] | ModuleType | None + +openssl_md_meth_names: frozenset[str] + +class HASH: + @property + def digest_size(self) -> int: ... + @property + def block_size(self) -> int: ... + @property + def name(self) -> str: ... + def copy(self) -> Self: ... + def digest(self) -> bytes: ... + def hexdigest(self) -> str: ... + def update(self, obj: ReadableBuffer, /) -> None: ... + +if sys.version_info >= (3, 10): + class UnsupportedDigestmodError(ValueError): ... + +if sys.version_info >= (3, 9): + class HASHXOF(HASH): + def digest(self, length: int) -> bytes: ... # type: ignore[override] + def hexdigest(self, length: int) -> str: ... # type: ignore[override] + + @final + class HMAC: + @property + def digest_size(self) -> int: ... + @property + def block_size(self) -> int: ... + @property + def name(self) -> str: ... + def copy(self) -> Self: ... + def digest(self) -> bytes: ... + def hexdigest(self) -> str: ... + def update(self, msg: ReadableBuffer) -> None: ... + + @overload + def compare_digest(a: ReadableBuffer, b: ReadableBuffer, /) -> bool: ... + @overload + def compare_digest(a: AnyStr, b: AnyStr, /) -> bool: ... + def get_fips_mode() -> int: ... + def hmac_new(key: bytes | bytearray, msg: ReadableBuffer = b"", digestmod: _DigestMod = None) -> HMAC: ... + def new(name: str, string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ... + def openssl_md5(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ... + def openssl_sha1(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ... + def openssl_sha224(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ... + def openssl_sha256(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ... + def openssl_sha384(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ... + def openssl_sha512(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ... + def openssl_sha3_224(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ... + def openssl_sha3_256(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ... + def openssl_sha3_384(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ... + def openssl_sha3_512(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ... + def openssl_shake_128(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASHXOF: ... + def openssl_shake_256(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASHXOF: ... + +else: + def new(name: str, string: ReadableBuffer = b"") -> HASH: ... + def openssl_md5(string: ReadableBuffer = b"") -> HASH: ... + def openssl_sha1(string: ReadableBuffer = b"") -> HASH: ... + def openssl_sha224(string: ReadableBuffer = b"") -> HASH: ... + def openssl_sha256(string: ReadableBuffer = b"") -> HASH: ... + def openssl_sha384(string: ReadableBuffer = b"") -> HASH: ... + def openssl_sha512(string: ReadableBuffer = b"") -> HASH: ... + +def hmac_digest(key: bytes | bytearray, msg: ReadableBuffer, digest: str) -> bytes: ... +def pbkdf2_hmac( + hash_name: str, password: ReadableBuffer, salt: ReadableBuffer, iterations: int, dklen: int | None = None +) -> bytes: ... +def scrypt( + password: ReadableBuffer, *, salt: ReadableBuffer, n: int, r: int, p: int, maxmem: int = 0, dklen: int = 64 +) -> bytes: ... diff --git a/stdlib/hashlib.pyi b/stdlib/hashlib.pyi index 74687d9ab..db6f86350 100644 --- a/stdlib/hashlib.pyi +++ b/stdlib/hashlib.pyi @@ -1,9 +1,19 @@ import sys from _blake2 import blake2b as blake2b, blake2s as blake2s +from _hashlib import ( + HASH, + openssl_md5 as md5, + openssl_sha1 as sha1, + openssl_sha224 as sha224, + openssl_sha256 as sha256, + openssl_sha384 as sha384, + openssl_sha512 as sha512, + pbkdf2_hmac as pbkdf2_hmac, + scrypt as scrypt, +) from _typeshed import ReadableBuffer from collections.abc import Callable, Set as AbstractSet -from typing import Protocol -from typing_extensions import Self +from typing import Protocol, type_check_only if sys.version_info >= (3, 11): __all__ = ( @@ -49,67 +59,35 @@ else: "pbkdf2_hmac", ) -class _Hash: - @property - def digest_size(self) -> int: ... - @property - def block_size(self) -> int: ... - @property - def name(self) -> str: ... - def copy(self) -> Self: ... - def digest(self) -> bytes: ... - def hexdigest(self) -> str: ... - def update(self, data: ReadableBuffer, /) -> None: ... - -class _VarLenHash: - digest_size: int - block_size: int - name: str - def copy(self) -> _VarLenHash: ... - def digest(self, length: int, /) -> bytes: ... - def hexdigest(self, length: int, /) -> str: ... - def update(self, data: ReadableBuffer, /) -> None: ... - if sys.version_info >= (3, 9): - def new(name: str, data: ReadableBuffer = b"", *, usedforsecurity: bool = ...) -> _Hash: ... - def md5(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> _Hash: ... - def sha1(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> _Hash: ... - def sha224(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> _Hash: ... - def sha256(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> _Hash: ... - def sha384(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> _Hash: ... - def sha512(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> _Hash: ... - def sha3_224(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> _Hash: ... - def sha3_256(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> _Hash: ... - def sha3_384(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> _Hash: ... - def sha3_512(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> _Hash: ... - def shake_128(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> _VarLenHash: ... - def shake_256(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> _VarLenHash: ... + def new(name: str, data: ReadableBuffer = b"", *, usedforsecurity: bool = ...) -> HASH: ... + from _hashlib import ( + openssl_sha3_224 as sha3_224, + openssl_sha3_256 as sha3_256, + openssl_sha3_384 as sha3_384, + openssl_sha3_512 as sha3_512, + openssl_shake_128 as shake_128, + openssl_shake_256 as shake_256, + ) else: - def new(name: str, data: ReadableBuffer = b"") -> _Hash: ... - def md5(string: ReadableBuffer = b"") -> _Hash: ... - def sha1(string: ReadableBuffer = b"") -> _Hash: ... - def sha224(string: ReadableBuffer = b"") -> _Hash: ... - def sha256(string: ReadableBuffer = b"") -> _Hash: ... - def sha384(string: ReadableBuffer = b"") -> _Hash: ... - def sha512(string: ReadableBuffer = b"") -> _Hash: ... - def sha3_224(string: ReadableBuffer = b"") -> _Hash: ... - def sha3_256(string: ReadableBuffer = b"") -> _Hash: ... - def sha3_384(string: ReadableBuffer = b"") -> _Hash: ... - def sha3_512(string: ReadableBuffer = b"") -> _Hash: ... + @type_check_only + class _VarLenHash(HASH): + def digest(self, length: int) -> bytes: ... # type: ignore[override] + def hexdigest(self, length: int) -> str: ... # type: ignore[override] + + def new(name: str, data: ReadableBuffer = b"") -> HASH: ... + # At runtime these aren't functions but classes imported from _sha3 + def sha3_224(string: ReadableBuffer = b"") -> HASH: ... + def sha3_256(string: ReadableBuffer = b"") -> HASH: ... + def sha3_384(string: ReadableBuffer = b"") -> HASH: ... + def sha3_512(string: ReadableBuffer = b"") -> HASH: ... def shake_128(string: ReadableBuffer = b"") -> _VarLenHash: ... def shake_256(string: ReadableBuffer = b"") -> _VarLenHash: ... algorithms_guaranteed: AbstractSet[str] algorithms_available: AbstractSet[str] -def pbkdf2_hmac( - hash_name: str, password: ReadableBuffer, salt: ReadableBuffer, iterations: int, dklen: int | None = None -) -> bytes: ... -def scrypt( - password: ReadableBuffer, *, salt: ReadableBuffer, n: int, r: int, p: int, maxmem: int = 0, dklen: int = 64 -) -> bytes: ... - if sys.version_info >= (3, 11): class _BytesIOLike(Protocol): def getbuffer(self) -> ReadableBuffer: ... @@ -119,5 +97,8 @@ if sys.version_info >= (3, 11): def readable(self) -> bool: ... def file_digest( - fileobj: _BytesIOLike | _FileDigestFileObj, digest: str | Callable[[], _Hash], /, *, _bufsize: int = 262144 - ) -> _Hash: ... + fileobj: _BytesIOLike | _FileDigestFileObj, digest: str | Callable[[], HASH], /, *, _bufsize: int = 262144 + ) -> HASH: ... + +# Legacy typing-only alias +_Hash = HASH diff --git a/stdlib/hmac.pyi b/stdlib/hmac.pyi index eccfbdc23..efd649ec3 100644 --- a/stdlib/hmac.pyi +++ b/stdlib/hmac.pyi @@ -1,6 +1,7 @@ +import sys +from _hashlib import HASH as _HashlibHash from _typeshed import ReadableBuffer, SizedBuffer from collections.abc import Callable -from hashlib import _Hash as _HashlibHash from types import ModuleType from typing import AnyStr, overload from typing_extensions import TypeAlias @@ -30,8 +31,12 @@ class HMAC: def hexdigest(self) -> str: ... def copy(self) -> HMAC: ... -@overload -def compare_digest(a: ReadableBuffer, b: ReadableBuffer, /) -> bool: ... -@overload -def compare_digest(a: AnyStr, b: AnyStr, /) -> bool: ... def digest(key: SizedBuffer, msg: ReadableBuffer, digest: _DigestMod) -> bytes: ... + +if sys.version_info >= (3, 9): + from _hashlib import compare_digest as compare_digest +else: + @overload + def compare_digest(a: ReadableBuffer, b: ReadableBuffer, /) -> bool: ... + @overload + def compare_digest(a: AnyStr, b: AnyStr, /) -> bool: ...