Skip to content

Hashers

blake2signer.hashers

Hashers handlers.

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

BLAKE2Hasher

Bases: BLAKEHasher

Hasher interface with BLAKE2.

Source code in blake2signer/hashers/blakehashers.py
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
class BLAKE2Hasher(BLAKEHasher):
    """Hasher interface with BLAKE2."""

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

        Args:
            hasher: Hash function to use.

        Keyword Args:
            secrets: A tuple of secret values which are derived using BLAKE to produce
                the signing key, to ensure they fit the hasher limits (so they have
                no practical size limit), from oldest to newest. This allows secret
                rotation: signatures are checked against all of them, but the last one
                (the newest one) is used to sign.
            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, secrets=secrets, 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,
        *,
        key: bytes,
        salt: bytes,
    ) -> bytes:
        """Get a hash digest using the hasher in keyed hashing mode."""
        return self._hasher(
            data,
            digest_size=self._digest_size,
            key=key,
            salt=salt,
            person=self._person,
        ).digest()

__init__(hasher, *, secrets, digest_size, person)

BLAKE hasher to interface with different BLAKE versions.

Parameters:

Name Type Description Default
hasher HasherChoice

Hash function to use.

required

Other Parameters:

Name Type Description
secrets typing.Tuple[bytes, ...]

A tuple of secret values which are derived using BLAKE to produce the signing key, to ensure they fit the hasher limits (so they have no practical size limit), from oldest to newest. This allows secret rotation: signatures are checked against all of them, but the last one (the newest one) is used to sign.

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.

Raises:

Type Description
InvalidOptionError

A parameter is out of bounds.

Source code in blake2signer/hashers/blakehashers.py
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
def __init__(
    self,
    hasher: HasherChoice,
    *,
    secrets: typing.Tuple[bytes, ...],
    digest_size: int,
    person: bytes,
) -> None:
    """BLAKE hasher to interface with different BLAKE versions.

    Args:
        hasher: Hash function to use.

    Keyword Args:
        secrets: A tuple of secret values which are derived using BLAKE to produce
            the signing key, to ensure they fit the hasher limits (so they have
            no practical size limit), from oldest to newest. This allows secret
            rotation: signatures are checked against all of them, but the last one
            (the newest one) is used to sign.
        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, secrets=secrets, digest_size=digest_size, person=person)

_derive_key(secret, *, person)

Derive hasher key from given secret to ensure it fits the hasher correctly.

Source code in blake2signer/hashers/blakehashers.py
182
183
184
185
186
187
188
189
190
191
192
193
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()

_derive_person(person)

Derive given personalisation value to ensure it fits the hasher correctly.

Source code in blake2signer/hashers/blakehashers.py
178
179
180
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()

digest(data, *, key, salt)

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

Source code in blake2signer/hashers/blakehashers.py
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
def digest(
    self,
    data: bytes,
    *,
    key: bytes,
    salt: bytes,
) -> bytes:
    """Get a hash digest using the hasher in keyed hashing mode."""
    return self._hasher(
        data,
        digest_size=self._digest_size,
        key=key,
        salt=salt,
        person=self._person,
    ).digest()

max_digest_size()

Get the maximum digest size, if any.

Source code in blake2signer/hashers/blakehashers.py
173
174
175
176
@property
def max_digest_size(self) -> typing.Optional[int]:
    """Get the maximum digest size, if any."""
    return self._hasher.MAX_DIGEST_SIZE

salt_size()

Get the salt size of the hasher.

Source code in blake2signer/hashers/blakehashers.py
168
169
170
171
@property
def salt_size(self) -> int:
    """Get the salt size of the hasher."""
    return self._hasher.SALT_SIZE

BLAKE3Hasher

Bases: BLAKEHasher

Hasher interface with BLAKE3.

Source code in blake2signer/hashers/blakehashers.py
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
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,
        *,
        key: bytes,
        salt: bytes,
    ) -> 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=key)

        return hasher.digest(length=self._digest_size)

_derive_key(secret, *, person)

Derive hasher key from given secret to ensure it fits the hasher correctly.

Source code in blake2signer/hashers/blakehashers.py
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
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)

_derive_person(person)

Derive given personalisation value to ensure it fits the hasher correctly.

Source code in blake2signer/hashers/blakehashers.py
225
226
227
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

digest(data, *, key, salt)

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

Source code in blake2signer/hashers/blakehashers.py
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
def digest(
    self,
    data: bytes,
    *,
    key: bytes,
    salt: bytes,
) -> 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=key)

    return hasher.digest(length=self._digest_size)

max_digest_size()

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

Source code in blake2signer/hashers/blakehashers.py
220
221
222
223
@property
def max_digest_size(self) -> typing.Optional[int]:
    """Get the maximum digest size of the hasher, if any."""
    return None

salt_size()

Get the salt size of the hasher.

Source code in blake2signer/hashers/blakehashers.py
215
216
217
218
@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

HasherChoice

Bases: str, Enum

Hasher selection choices.

Source code in blake2signer/hashers/blakehashers.py
13
14
15
16
17
18
class HasherChoice(str, Enum):
    """Hasher selection choices."""

    blake2b = 'blake2b'
    blake2s = 'blake2s'
    blake3 = 'blake3'

has_blake3()

Return True if the blake3 package is installed.

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

blake3_package

BLAKE3 module to handle the optional blake3 package.

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

has_blake3()

Return True if the blake3 package is installed.

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

blakehashers

BLAKE hashers handlers.

BLAKE2Hasher

Bases: BLAKEHasher

Hasher interface with BLAKE2.

Source code in blake2signer/hashers/blakehashers.py
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
class BLAKE2Hasher(BLAKEHasher):
    """Hasher interface with BLAKE2."""

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

        Args:
            hasher: Hash function to use.

        Keyword Args:
            secrets: A tuple of secret values which are derived using BLAKE to produce
                the signing key, to ensure they fit the hasher limits (so they have
                no practical size limit), from oldest to newest. This allows secret
                rotation: signatures are checked against all of them, but the last one
                (the newest one) is used to sign.
            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, secrets=secrets, 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,
        *,
        key: bytes,
        salt: bytes,
    ) -> bytes:
        """Get a hash digest using the hasher in keyed hashing mode."""
        return self._hasher(
            data,
            digest_size=self._digest_size,
            key=key,
            salt=salt,
            person=self._person,
        ).digest()
__init__(hasher, *, secrets, digest_size, person)

BLAKE hasher to interface with different BLAKE versions.

Parameters:

Name Type Description Default
hasher HasherChoice

Hash function to use.

required

Other Parameters:

Name Type Description
secrets typing.Tuple[bytes, ...]

A tuple of secret values which are derived using BLAKE to produce the signing key, to ensure they fit the hasher limits (so they have no practical size limit), from oldest to newest. This allows secret rotation: signatures are checked against all of them, but the last one (the newest one) is used to sign.

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.

Raises:

Type Description
InvalidOptionError

A parameter is out of bounds.

Source code in blake2signer/hashers/blakehashers.py
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
def __init__(
    self,
    hasher: HasherChoice,
    *,
    secrets: typing.Tuple[bytes, ...],
    digest_size: int,
    person: bytes,
) -> None:
    """BLAKE hasher to interface with different BLAKE versions.

    Args:
        hasher: Hash function to use.

    Keyword Args:
        secrets: A tuple of secret values which are derived using BLAKE to produce
            the signing key, to ensure they fit the hasher limits (so they have
            no practical size limit), from oldest to newest. This allows secret
            rotation: signatures are checked against all of them, but the last one
            (the newest one) is used to sign.
        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, secrets=secrets, digest_size=digest_size, person=person)
_derive_key(secret, *, person)

Derive hasher key from given secret to ensure it fits the hasher correctly.

Source code in blake2signer/hashers/blakehashers.py
182
183
184
185
186
187
188
189
190
191
192
193
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()
_derive_person(person)

Derive given personalisation value to ensure it fits the hasher correctly.

Source code in blake2signer/hashers/blakehashers.py
178
179
180
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()
digest(data, *, key, salt)

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

Source code in blake2signer/hashers/blakehashers.py
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
def digest(
    self,
    data: bytes,
    *,
    key: bytes,
    salt: bytes,
) -> bytes:
    """Get a hash digest using the hasher in keyed hashing mode."""
    return self._hasher(
        data,
        digest_size=self._digest_size,
        key=key,
        salt=salt,
        person=self._person,
    ).digest()
max_digest_size()

Get the maximum digest size, if any.

Source code in blake2signer/hashers/blakehashers.py
173
174
175
176
@property
def max_digest_size(self) -> typing.Optional[int]:
    """Get the maximum digest size, if any."""
    return self._hasher.MAX_DIGEST_SIZE
salt_size()

Get the salt size of the hasher.

Source code in blake2signer/hashers/blakehashers.py
168
169
170
171
@property
def salt_size(self) -> int:
    """Get the salt size of the hasher."""
    return self._hasher.SALT_SIZE

BLAKE3Hasher

Bases: BLAKEHasher

Hasher interface with BLAKE3.

Source code in blake2signer/hashers/blakehashers.py
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
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,
        *,
        key: bytes,
        salt: bytes,
    ) -> 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=key)

        return hasher.digest(length=self._digest_size)
_derive_key(secret, *, person)

Derive hasher key from given secret to ensure it fits the hasher correctly.

Source code in blake2signer/hashers/blakehashers.py
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
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)
_derive_person(person)

Derive given personalisation value to ensure it fits the hasher correctly.

Source code in blake2signer/hashers/blakehashers.py
225
226
227
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
digest(data, *, key, salt)

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

Source code in blake2signer/hashers/blakehashers.py
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
def digest(
    self,
    data: bytes,
    *,
    key: bytes,
    salt: bytes,
) -> 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=key)

    return hasher.digest(length=self._digest_size)
max_digest_size()

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

Source code in blake2signer/hashers/blakehashers.py
220
221
222
223
@property
def max_digest_size(self) -> typing.Optional[int]:
    """Get the maximum digest size of the hasher, if any."""
    return None
salt_size()

Get the salt size of the hasher.

Source code in blake2signer/hashers/blakehashers.py
215
216
217
218
@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

BLAKEHasher

Bases: ABC

BLAKE interface to manage payload signing using different BLAKE versions.

Source code in blake2signer/hashers/blakehashers.py
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
class BLAKEHasher(ABC):
    """BLAKE interface to manage payload signing using different BLAKE versions."""

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

        Args:
            hasher: Hash function to use.

        Keyword Args:
            secrets: A tuple of secret values which are derived using BLAKE to produce
                the signing key, to ensure they fit the hasher limits (so they have
                no practical size limit), from oldest to newest. This allows secret
                rotation: signatures are checked against all of them, but the last one
                (the newest one) is used to sign.
            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._keys = self._derive_keys(secrets, person=self._person)

    @property
    def keys(self) -> typing.Tuple[bytes, ...]:
        """Get hasher keys, from oldest to newest."""
        return self._keys

    @property
    def signing_key(self) -> bytes:
        """Get the hasher signing key (the newest one)."""
        return self._keys[-1]

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

        Args:
            digest_size: digest size to validate.

        Returns:
            Validated digest size.

        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."""

    def _derive_keys(
        self,
        secrets: typing.Tuple[bytes, ...],
        *,
        person: bytes,
    ) -> typing.Tuple[bytes, ...]:
        """Derive hasher keys from given secrets to ensure they fit the hasher."""
        return tuple(self._derive_key(secret, person=person) for secret in secrets)

    @abstractmethod
    def digest(
        self,
        data: bytes,
        *,
        key: bytes,
        salt: bytes,
    ) -> bytes:
        """Get a hash digest using the hasher in keyed hashing mode."""
__init__(hasher, *, secrets, digest_size, person)

BLAKE hasher to interface with different BLAKE versions.

Parameters:

Name Type Description Default
hasher HasherChoice

Hash function to use.

required

Other Parameters:

Name Type Description
secrets typing.Tuple[bytes, ...]

A tuple of secret values which are derived using BLAKE to produce the signing key, to ensure they fit the hasher limits (so they have no practical size limit), from oldest to newest. This allows secret rotation: signatures are checked against all of them, but the last one (the newest one) is used to sign.

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.

Raises:

Type Description
InvalidOptionError

A parameter is out of bounds.

MissingDependencyError

A required dependency is not met.

Source code in blake2signer/hashers/blakehashers.py
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
def __init__(
    self,
    hasher: HasherChoice,
    *,
    secrets: typing.Tuple[bytes, ...],
    digest_size: int,
    person: bytes,
) -> None:
    """BLAKE hasher to interface with different BLAKE versions.

    Args:
        hasher: Hash function to use.

    Keyword Args:
        secrets: A tuple of secret values which are derived using BLAKE to produce
            the signing key, to ensure they fit the hasher limits (so they have
            no practical size limit), from oldest to newest. This allows secret
            rotation: signatures are checked against all of them, but the last one
            (the newest one) is used to sign.
        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._keys = self._derive_keys(secrets, person=self._person)
_derive_key(secret, *, person)

Derive hasher key from given secret to ensure it fits the hasher correctly.

Source code in blake2signer/hashers/blakehashers.py
104
105
106
107
108
109
110
111
@abstractmethod
def _derive_key(
    self,
    secret: bytes,
    *,
    person: bytes,
) -> bytes:
    """Derive hasher key from given secret to ensure it fits the hasher correctly."""
_derive_keys(secrets, *, person)

Derive hasher keys from given secrets to ensure they fit the hasher.

Source code in blake2signer/hashers/blakehashers.py
113
114
115
116
117
118
119
120
def _derive_keys(
    self,
    secrets: typing.Tuple[bytes, ...],
    *,
    person: bytes,
) -> typing.Tuple[bytes, ...]:
    """Derive hasher keys from given secrets to ensure they fit the hasher."""
    return tuple(self._derive_key(secret, person=person) for secret in secrets)
_derive_person(person)

Derive given personalisation value to ensure it fits the hasher correctly.

Source code in blake2signer/hashers/blakehashers.py
100
101
102
@abstractmethod
def _derive_person(self, person: bytes) -> bytes:
    """Derive given personalisation value to ensure it fits the hasher correctly."""
_validate_digest_size(digest_size)

Validate the digest_size value and return it clean.

Parameters:

Name Type Description Default
digest_size int

digest size to validate.

required

Returns:

Type Description
int

Validated digest size.

Raises:

Type Description
InvalidOptionError

The value is out of bounds.

Source code in blake2signer/hashers/blakehashers.py
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
def _validate_digest_size(self, digest_size: int) -> int:
    """Validate the digest_size value and return it clean.

    Args:
        digest_size: digest size to validate.

    Returns:
        Validated digest size.

    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
digest(data, *, key, salt)

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

Source code in blake2signer/hashers/blakehashers.py
122
123
124
125
126
127
128
129
130
@abstractmethod
def digest(
    self,
    data: bytes,
    *,
    key: bytes,
    salt: bytes,
) -> bytes:
    """Get a hash digest using the hasher in keyed hashing mode."""
keys()

Get hasher keys, from oldest to newest.

Source code in blake2signer/hashers/blakehashers.py
60
61
62
63
@property
def keys(self) -> typing.Tuple[bytes, ...]:
    """Get hasher keys, from oldest to newest."""
    return self._keys
max_digest_size()

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

Source code in blake2signer/hashers/blakehashers.py
95
96
97
98
@property
@abstractmethod
def max_digest_size(self) -> typing.Optional[int]:
    """Get the maximum digest size of the hasher, if any."""
salt_size()

Get the salt size of the hasher.

Source code in blake2signer/hashers/blakehashers.py
90
91
92
93
@property
@abstractmethod
def salt_size(self) -> int:
    """Get the salt size of the hasher."""
signing_key()

Get the hasher signing key (the newest one).

Source code in blake2signer/hashers/blakehashers.py
65
66
67
68
@property
def signing_key(self) -> bytes:
    """Get the hasher signing key (the newest one)."""
    return self._keys[-1]

HasherChoice

Bases: str, Enum

Hasher selection choices.

Source code in blake2signer/hashers/blakehashers.py
13
14
15
16
17
18
class HasherChoice(str, Enum):
    """Hasher selection choices."""

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