Source code for curldl.util.crypt
"""Cryptographic utilities for internal use"""
from __future__ import annotations
import hashlib
import logging
import os
log = logging.getLogger(__name__)
[docs]
class Cryptography:
"""Cryptographic utilities"""
FILE_CHUNK_BYTES = 8 * 1024**2
[docs]
@staticmethod
def get_available_digests() -> list[str]:
"""Returns list of fixed-size digest algorithms in :mod:`hashlib`.
Uses :data:`hashlib.algorithms_guaranteed`
because :data:`hashlib.algorithms_available` may result in runtime errors
due to deprecated algorithms being hidden by OpenSSL.
:return: guaranteed algorithms in :mod:`hashlib` that produce a fixed-size
digest
"""
return sorted(
algo
for algo in hashlib.algorithms_guaranteed
if hashlib.new(algo).digest_size != 0
)
[docs]
@classmethod
def verify_digest(
cls, path: str | os.PathLike[str], algo: str, digest: str
) -> None:
"""Verify file digest and raise :class:`ValueError` in case of mismatch.
:param path: input file path
:param algo: hash algorithm name accepted by :func:`hashlib.new`
:param digest: hexadecimal digest string to verify
:raises ValueError: ``digest`` has incorrect length or fails verification
"""
hash_obj = hashlib.new(algo)
digest_name = hash_obj.name.upper()
log.debug(
"Computing %s-bit %s for %s", hash_obj.digest_size * 8, digest_name, path
)
if hash_obj.digest_size * 2 != len(digest):
raise ValueError(
f"Expected {digest_name} for {path} has length != "
f"{hash_obj.digest_size} B"
)
with open(path, "rb") as path_obj:
while chunk := path_obj.read(cls.FILE_CHUNK_BYTES):
hash_obj.update(chunk)
if hash_obj.hexdigest().lower() != digest.lower():
raise ValueError(f"{digest_name} mismatch for {path}")
log.info("Successfully verified %s of %s", digest_name, path)