Skip to content

Hashers

blake2signer.hashers special

Hashers handlers.

These are not meant to be used directly, but otherwise through a signer.

blake3_package

BLAKE3 module to handle the optional blake3 package.

When, and if, this package gets to the Python core, we can remove this.

blake3(*_, **__)

BLAKE3 function stub.

Source code in blake2signer/hashers/blake3_package.py
def blake3(*_: typing.Any, **__: typing.Any) -> typing.Any:
    """BLAKE3 function stub."""
    raise MissingDependencyError(
        'blake3 can not be selected if it is not installed: '
        'python3 -m pip install blake3',
    )

has_blake3()

Return True if the blake3 package is installed.

Source code in blake2signer/hashers/blake3_package.py
def has_blake3() -> bool:
    """Return True if the `blake3` package is installed."""
    return _has_blake3

blakehashers

BLAKE hashers handlers.

BLAKE2Hasher (BLAKEHasher)

Hasher interface with BLAKE2.

Source code in blake2signer/hashers/blakehashers.py
class BLAKE2Hasher(BLAKEHasher):
    """Hasher interface with BLAKE2."""

    def __init__(
        self,
        hasher: HasherChoice,
        *,
        secret: bytes,
        digest_size: int,
        person: bytes,
    ) -> None:
        """BLAKE hasher to interface with different BLAKE versions.

        Args:
            hasher: Hash function to use.

        Keyword Args:
            secret: Secret value which will be derived using BLAKE to produce
                the signing key. It is derived using BLAKE to ensure it fits the
                hasher limits, so it has no practical size limit.
            digest_size: Size of digest in bytes.
            person: Personalisation string to force the hash function to produce
                different digests for the same input. It is derived using BLAKE to
                ensure it fits the hasher limits, so it has no practical size limit.

        Raises:
            InvalidOptionError: A parameter is out of bounds.
        """
        self._hasher: typing.Type[typing.Union[hashlib.blake2b, hashlib.blake2s]]
        self._hasher = getattr(hashlib, hasher)

        super().__init__(hasher, secret=secret, digest_size=digest_size, person=person)

    @property
    def salt_size(self) -> int:
        """Get the salt size of the hasher."""
        return self._hasher.SALT_SIZE

    @property
    def max_digest_size(self) -> typing.Optional[int]:
        """Get the maximum digest size, if any."""
        return self._hasher.MAX_DIGEST_SIZE

    def _derive_person(self, person: bytes) -> bytes:
        """Derive given personalisation value to ensure it fits the hasher correctly."""
        return self._hasher(person, digest_size=self._hasher.PERSON_SIZE).digest()

    def _derive_key(
        self,
        secret: bytes,
        *,
        person: bytes,
    ) -> bytes:
        """Derive hasher key from given secret to ensure it fits the hasher correctly."""
        return self._hasher(
            secret,
            digest_size=self._hasher.MAX_KEY_SIZE,
            person=person,
        ).digest()

    def digest(
        self,
        data: bytes,
        *,
        salt: bytes = b'',
    ) -> bytes:
        """Get a hash digest using the hasher in keyed hashing mode."""
        return self._hasher(
            data,
            digest_size=self._digest_size,
            key=self._key,
            salt=salt,
            person=self._person,
        ).digest()
max_digest_size: Optional[int] property readonly

Get the maximum digest size, if any.

salt_size: int property readonly

Get the salt size of the hasher.

__init__(self, hasher, *, secret, digest_size, person) special

BLAKE hasher to interface with different BLAKE versions.

Parameters:

Name Type Description Default
hasher HasherChoice

Hash function to use.

required

Keyword arguments:

Name Type Description
secret bytes

Secret value which will be derived using BLAKE to produce the signing key. It is derived using BLAKE to ensure it fits the hasher limits, so it has no practical size limit.

digest_size int

Size of digest in bytes.

person bytes

Personalisation string to force the hash function to produce different digests for the same input. It is derived using BLAKE to ensure it fits the hasher limits, so it has no practical size limit.

Exceptions:

Type Description
InvalidOptionError

A parameter is out of bounds.

Source code in blake2signer/hashers/blakehashers.py
def __init__(
    self,
    hasher: HasherChoice,
    *,
    secret: bytes,
    digest_size: int,
    person: bytes,
) -> None:
    """BLAKE hasher to interface with different BLAKE versions.

    Args:
        hasher: Hash function to use.

    Keyword Args:
        secret: Secret value which will be derived using BLAKE to produce
            the signing key. It is derived using BLAKE to ensure it fits the
            hasher limits, so it has no practical size limit.
        digest_size: Size of digest in bytes.
        person: Personalisation string to force the hash function to produce
            different digests for the same input. It is derived using BLAKE to
            ensure it fits the hasher limits, so it has no practical size limit.

    Raises:
        InvalidOptionError: A parameter is out of bounds.
    """
    self._hasher: typing.Type[typing.Union[hashlib.blake2b, hashlib.blake2s]]
    self._hasher = getattr(hashlib, hasher)

    super().__init__(hasher, secret=secret, digest_size=digest_size, person=person)
digest(self, data, *, salt=b'')

Get a hash digest using the hasher in keyed hashing mode.

Source code in blake2signer/hashers/blakehashers.py
def digest(
    self,
    data: bytes,
    *,
    salt: bytes = b'',
) -> bytes:
    """Get a hash digest using the hasher in keyed hashing mode."""
    return self._hasher(
        data,
        digest_size=self._digest_size,
        key=self._key,
        salt=salt,
        person=self._person,
    ).digest()

BLAKE3Hasher (BLAKEHasher)

Hasher interface with BLAKE3.

Source code in blake2signer/hashers/blakehashers.py
class BLAKE3Hasher(BLAKEHasher):
    """Hasher interface with BLAKE3."""

    @property
    def salt_size(self) -> int:
        """Get the salt size of the hasher."""
        return 16  # Arbitrary as there is no salt in BLAKE3, so use the same as blake2b

    @property
    def max_digest_size(self) -> typing.Optional[int]:
        """Get the maximum digest size of the hasher, if any."""
        return None

    def _derive_person(self, person: bytes) -> bytes:
        """Derive given personalisation value to ensure it fits the hasher correctly."""
        return person  # No need for deriving, BLAKE3 doesn't have this "person" concept

    def _derive_key(
        self,
        secret: bytes,
        *,
        person: bytes,
    ) -> bytes:
        """Derive hasher key from given secret to ensure it fits the hasher correctly."""
        # A side effect of this method is that it is called during the class
        # instantiation, and if the `blake3` package is not installed, will raise a
        # proper exception. However, should this not be called during instantiation,
        # then the exception will be raised further at `self.digest(...)`, which could
        # be unexpected, or worst, captured and swallowed by a try/except block for
        # signing/unsigning, which is why I leave this note here: if this method is no
        # longer called during init, add a check then to ensure that we raise the
        # exception there and not further (there's a test for this anyway).
        ctx = 'blake2signer 2021-12-29 18:04:37 BLAKE3Hasher key derivation'
        hasher = blake3(secret, derive_key_context=ctx)
        hasher.update(person)

        return hasher.digest(length=hasher.key_size)

    def digest(
        self,
        data: bytes,
        *,
        salt: bytes = b'',
    ) -> bytes:
        """Get a hash digest using the hasher in keyed hashing mode."""
        # BLAKE3 doesn't support salt nor personalisation, so there are a few
        # options to consider. Check following snippet:
        # https://gitlab.com/hackancuba/blake2signer/-/snippets/2132545
        payload = salt + self._person + data
        hasher = blake3(payload, key=self._key)

        return hasher.digest(length=self._digest_size)
max_digest_size: Optional[int] property readonly

Get the maximum digest size of the hasher, if any.

salt_size: int property readonly

Get the salt size of the hasher.

digest(self, data, *, salt=b'')

Get a hash digest using the hasher in keyed hashing mode.

Source code in blake2signer/hashers/blakehashers.py
def digest(
    self,
    data: bytes,
    *,
    salt: bytes = b'',
) -> bytes:
    """Get a hash digest using the hasher in keyed hashing mode."""
    # BLAKE3 doesn't support salt nor personalisation, so there are a few
    # options to consider. Check following snippet:
    # https://gitlab.com/hackancuba/blake2signer/-/snippets/2132545
    payload = salt + self._person + data
    hasher = blake3(payload, key=self._key)

    return hasher.digest(length=self._digest_size)

BLAKEHasher (ABC)

BLAKE interface to manage payload signing using different BLAKE versions.

Source code in blake2signer/hashers/blakehashers.py
class BLAKEHasher(ABC):
    """BLAKE interface to manage payload signing using different BLAKE versions."""

    def __init__(
        self,
        hasher: HasherChoice,
        *,
        secret: bytes,
        digest_size: int,
        person: bytes,
    ) -> None:
        """BLAKE hasher to interface with different BLAKE versions.

        Args:
            hasher: Hash function to use.

        Keyword Args:
            secret: Secret value which will be derived using BLAKE to produce
                the signing key. It is derived using BLAKE to ensure it fits the
                hasher limits, so it has no practical size limit.
            digest_size: Size of digest in bytes.
            person: Personalisation string to force the hash function to produce
                different digests for the same input. It is derived using BLAKE to
                ensure it fits the hasher limits, so it has no practical size limit.

        Raises:
            InvalidOptionError: A parameter is out of bounds.
            MissingDependencyError: A required dependency is not met.
        """
        # I'm purposefully omitting further validation of this hasher choice, because
        # that is handled by the signer, which is also responsible for casting a
        # BLAKE2Hasher or BLAKE3Hasher correspondingly.
        self._hasher_choice = hasher
        self._digest_size = self._validate_digest_size(digest_size)
        self._person = self._derive_person(person)
        self._key = self._derive_key(secret, person=self._person)  # bye secret :)

    def _validate_digest_size(self, digest_size: int) -> int:
        """Validate the digest_size value and return it clean.

        Raises:
            InvalidOptionError: The value is out of bounds.
        """
        max_digest_size = self.max_digest_size
        if max_digest_size is not None and digest_size > max_digest_size:
            raise errors.InvalidOptionError(
                f'digest_size should be lower than or equal to {max_digest_size}',
            )

        return digest_size

    @property
    @abstractmethod
    def salt_size(self) -> int:
        """Get the salt size of the hasher."""

    @property
    @abstractmethod
    def max_digest_size(self) -> typing.Optional[int]:
        """Get the maximum digest size of the hasher, if any."""

    @abstractmethod
    def _derive_person(self, person: bytes) -> bytes:
        """Derive given personalisation value to ensure it fits the hasher correctly."""

    @abstractmethod
    def _derive_key(
        self,
        secret: bytes,
        *,
        person: bytes,
    ) -> bytes:
        """Derive hasher key from given secret to ensure it fits the hasher correctly."""

    @abstractmethod
    def digest(
        self,
        data: bytes,
        *,
        salt: bytes = b'',
    ) -> bytes:
        """Get a hash digest using the hasher in keyed hashing mode."""
max_digest_size: Optional[int] property readonly

Get the maximum digest size of the hasher, if any.

salt_size: int property readonly

Get the salt size of the hasher.

__init__(self, hasher, *, secret, digest_size, person) special

BLAKE hasher to interface with different BLAKE versions.

Parameters:

Name Type Description Default
hasher HasherChoice

Hash function to use.

required

Keyword arguments:

Name Type Description
secret bytes

Secret value which will be derived using BLAKE to produce the signing key. It is derived using BLAKE to ensure it fits the hasher limits, so it has no practical size limit.

digest_size int

Size of digest in bytes.

person bytes

Personalisation string to force the hash function to produce different digests for the same input. It is derived using BLAKE to ensure it fits the hasher limits, so it has no practical size limit.

Exceptions:

Type Description
InvalidOptionError

A parameter is out of bounds.

MissingDependencyError

A required dependency is not met.

Source code in blake2signer/hashers/blakehashers.py
def __init__(
    self,
    hasher: HasherChoice,
    *,
    secret: bytes,
    digest_size: int,
    person: bytes,
) -> None:
    """BLAKE hasher to interface with different BLAKE versions.

    Args:
        hasher: Hash function to use.

    Keyword Args:
        secret: Secret value which will be derived using BLAKE to produce
            the signing key. It is derived using BLAKE to ensure it fits the
            hasher limits, so it has no practical size limit.
        digest_size: Size of digest in bytes.
        person: Personalisation string to force the hash function to produce
            different digests for the same input. It is derived using BLAKE to
            ensure it fits the hasher limits, so it has no practical size limit.

    Raises:
        InvalidOptionError: A parameter is out of bounds.
        MissingDependencyError: A required dependency is not met.
    """
    # I'm purposefully omitting further validation of this hasher choice, because
    # that is handled by the signer, which is also responsible for casting a
    # BLAKE2Hasher or BLAKE3Hasher correspondingly.
    self._hasher_choice = hasher
    self._digest_size = self._validate_digest_size(digest_size)
    self._person = self._derive_person(person)
    self._key = self._derive_key(secret, person=self._person)  # bye secret :)
digest(self, data, *, salt=b'')

Get a hash digest using the hasher in keyed hashing mode.

Source code in blake2signer/hashers/blakehashers.py
@abstractmethod
def digest(
    self,
    data: bytes,
    *,
    salt: bytes = b'',
) -> bytes:
    """Get a hash digest using the hasher in keyed hashing mode."""

HasherChoice (str, Enum)

Hasher selection choices.

Source code in blake2signer/hashers/blakehashers.py
class HasherChoice(str, Enum):
    """Hasher selection choices."""

    blake2b = 'blake2b'
    blake2s = 'blake2s'
    blake3 = 'blake3'
Back to top