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'