Skip to content

Bases

Bases: base classes for signers.

blake2signer.bases.Base

Bases: Mixin, ABC

Base class containing the minimum for a signer.

Source code in blake2signer/bases.py
 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
131
132
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
210
211
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
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
class Base(Mixin, ABC):
    """Base class containing the minimum for a signer."""

    Hashers = HasherChoice  # Sugar to avoid having to import the enum

    MIN_SECRET_SIZE: int = 16
    """Minimum secret size allowed (during instantiation)."""

    MIN_DIGEST_SIZE: int = 16
    """Minimum digest size allowed (during instantiation)."""

    DEFAULT_DIGEST_SIZE: int = 16  # 16 bytes is good security/size tradeoff
    """Default digest size to use when no digest size is indicated."""

    def __init__(
        self,
        secret: typing.Union[Secret, typing.Sequence[Secret]],
        *,
        personalisation: typing.Union[str, bytes] = b'',
        digest_size: typing.Optional[int] = None,
        hasher: typing.Union[HasherChoice, str] = HasherChoice.blake2b,
        deterministic: bool = False,
        separator: typing.Union[str, bytes] = b'.',
    ) -> None:
        """Sign and verify signed data using BLAKE in keyed hashing mode.

        Args:
            secret: Secret value which will be derived using BLAKE to produce the
                signing key. The minimum secret size is enforced to 16 bytes and there
                is no maximum. You can optionally provide a sequence of secrets, oldest
                to newest, that are used during signature check to allow for secret
                rotation. The last, newest, secret is used for signing.

        Keyword Args:
            personalisation (optional): 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. It defaults to the class name.
            digest_size (optional): Size of output signature (digest) in bytes
                (defaults to 16 bytes). The minimum size is enforced to 16 bytes.
            hasher (optional): Hash function to use: blake2b (default), blake2s
                or blake3.
            deterministic (optional): Define if signatures are deterministic or
                non-deterministic (default). Non-deterministic sigs are preferred,
                and achieved through the use of a random salt. For deterministic
                sigs, no salt is used: this means that for the same payload, the
                same sig is obtained (the advantage is that the sig is shorter).
            separator (optional): Character to separate the signature and the
                payload. It must not belong to the encoder alphabet and be ASCII
                (defaults to ".").

        Raises:
            ConversionError: A bytes parameter is not bytes and can't be converted
                to bytes.
            InvalidOptionError: A parameter is out of bounds.
        """
        hasher_choice = self._validate_hasher_choice(hasher)
        digest_size = self._validate_digest_size(digest_size)
        separator = self._validate_separator(separator)
        person = self._validate_person(personalisation)
        secrets = self._validate_secret(secret)

        if deterministic:
            person += b'Deterministic'
        person += self.__class__.__name__.encode()

        self._deterministic: bool = deterministic
        self._separator: bytes = separator

        self._hasher = self._get_hasher(
            hasher_choice,
            secrets=secrets,
            digest_size=digest_size,
            person=person,
        )

    def _validate_secret(
        self,
        secret: typing.Union[Secret, typing.Sequence[Secret]],
    ) -> typing.Tuple[bytes, ...]:
        """Validate the secret value and return it clean.

        Args:
            secret: Secret value to validate.

        Returns:
            Cleaned secrets tuple.

        Raises:
            ConversionError: The value is not bytes and can't be converted to bytes.
            InvalidOptionError: The value is out of bounds.
        """

        def coerce(secret_: Secret, position_: int) -> bytes:
            """Coerce a given secret into bytes.

            Args:
                secret_: Secret value to validate.
                position_: Position of the secret in the sequence.

            Returns:
                A secret value as bytes.

            Raises:
                InvalidOptionError: the secret in given position is invalid.
            """
            coerced = self._force_bytes(secret_)
            if len(coerced) < self.MIN_SECRET_SIZE:
                raise InvalidOptionError(
                    f'the {ordinal(position_)} secret should be longer than '
                    + f'{self.MIN_SECRET_SIZE} bytes',
                )

            return coerced

        secrets: typing.Sequence[Secret]
        if isinstance(secret, (str, bytes)):
            secrets = [secret]
        else:
            secrets = secret

        return tuple(coerce(dirty, position) for position, dirty in enumerate(secrets, start=1))

    def _validate_person(self, person: typing.Union[str, bytes]) -> bytes:
        """Validate the personalisation value and return it clean.

        Args:
            person: Personalisation value to validate.

        Returns:
            Cleaned personalisation value.

        Raises:
            ConversionError: The value is not bytes and can't be converted to bytes.
        """
        return self._force_bytes(person)

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

        Args:
            digest_size: Digest size value to validate.

        Returns:
            Cleaned digest size value.

        Raises:
            InvalidOptionError: The value is out of bounds.
        """
        if digest_size is None:
            digest_size = self.DEFAULT_DIGEST_SIZE

        if digest_size < self.MIN_DIGEST_SIZE:
            raise InvalidOptionError(
                f'digest_size should be bigger than or equal to {self.MIN_DIGEST_SIZE}',
            )

        return digest_size

    @staticmethod
    def _validate_hasher_choice(hasher: typing.Union[HasherChoice, str]) -> HasherChoice:
        """Validate the hasher choice.

        Args:
            hasher: the hasher choice to validate.

        Returns:
            A validated hasher choice as HasherChoice.

        Raises:
            InvalidOptionError: Invalid hasher choice.
        """
        try:
            choice = HasherChoice(hasher)
        except ValueError:
            raise InvalidOptionError(
                f'invalid hasher choice, must be one of: {", ".join(h for h in HasherChoice)}',
            )

        return choice

    def _validate_separator(self, separator: typing.Union[str, bytes]) -> bytes:
        """Validate the separator value and return it clean.

        Args:
            separator: Separator value to validate.

        Returns:
            Cleaned separator value.

        Raises:
            ConversionError: The value is not bytes and can't be converted to bytes.
            InvalidOptionError:  The value is out of bounds.
        """
        if not separator:
            raise InvalidOptionError('the separator character must have a value')

        if not separator.isascii():
            raise InvalidOptionError('the separator character must be ASCII')

        return self._force_bytes(separator)

    @staticmethod
    def _get_hasher(
        hasher: HasherChoice,
        *,
        secrets: typing.Tuple[bytes, ...],
        digest_size: int,
        person: bytes,
    ) -> BLAKEHasher:
        """Get the proper hasher instance regarding the choice."""
        hasher_class: typing.Type[BLAKEHasher]

        if hasher in {HasherChoice.blake2b, HasherChoice.blake2s}:
            hasher_class = BLAKE2Hasher
        else:
            hasher_class = BLAKE3Hasher

        return hasher_class(
            hasher,
            secrets=secrets,
            digest_size=digest_size,
            person=person,
        )

DEFAULT_DIGEST_SIZE: int = 16 class-attribute

Default digest size to use when no digest size is indicated.

MIN_DIGEST_SIZE: int = 16 class-attribute

Minimum digest size allowed (during instantiation).

MIN_SECRET_SIZE: int = 16 class-attribute

Minimum secret size allowed (during instantiation).

__init__(secret, *, personalisation=b'', digest_size=None, hasher=HasherChoice.blake2b, deterministic=False, separator=b'.')

Sign and verify signed data using BLAKE in keyed hashing mode.

Parameters:

Name Type Description Default
secret typing.Union[Secret, typing.Sequence[Secret]]

Secret value which will be derived using BLAKE to produce the signing key. The minimum secret size is enforced to 16 bytes and there is no maximum. You can optionally provide a sequence of secrets, oldest to newest, that are used during signature check to allow for secret rotation. The last, newest, secret is used for signing.

required

Other Parameters:

Name Type Description
personalisation optional

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. It defaults to the class name.

digest_size optional

Size of output signature (digest) in bytes (defaults to 16 bytes). The minimum size is enforced to 16 bytes.

hasher optional

Hash function to use: blake2b (default), blake2s or blake3.

deterministic optional

Define if signatures are deterministic or non-deterministic (default). Non-deterministic sigs are preferred, and achieved through the use of a random salt. For deterministic sigs, no salt is used: this means that for the same payload, the same sig is obtained (the advantage is that the sig is shorter).

separator optional

Character to separate the signature and the payload. It must not belong to the encoder alphabet and be ASCII (defaults to ".").

Raises:

Type Description
ConversionError

A bytes parameter is not bytes and can't be converted to bytes.

InvalidOptionError

A parameter is out of bounds.

Source code in blake2signer/bases.py
 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
131
132
133
134
135
def __init__(
    self,
    secret: typing.Union[Secret, typing.Sequence[Secret]],
    *,
    personalisation: typing.Union[str, bytes] = b'',
    digest_size: typing.Optional[int] = None,
    hasher: typing.Union[HasherChoice, str] = HasherChoice.blake2b,
    deterministic: bool = False,
    separator: typing.Union[str, bytes] = b'.',
) -> None:
    """Sign and verify signed data using BLAKE in keyed hashing mode.

    Args:
        secret: Secret value which will be derived using BLAKE to produce the
            signing key. The minimum secret size is enforced to 16 bytes and there
            is no maximum. You can optionally provide a sequence of secrets, oldest
            to newest, that are used during signature check to allow for secret
            rotation. The last, newest, secret is used for signing.

    Keyword Args:
        personalisation (optional): 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. It defaults to the class name.
        digest_size (optional): Size of output signature (digest) in bytes
            (defaults to 16 bytes). The minimum size is enforced to 16 bytes.
        hasher (optional): Hash function to use: blake2b (default), blake2s
            or blake3.
        deterministic (optional): Define if signatures are deterministic or
            non-deterministic (default). Non-deterministic sigs are preferred,
            and achieved through the use of a random salt. For deterministic
            sigs, no salt is used: this means that for the same payload, the
            same sig is obtained (the advantage is that the sig is shorter).
        separator (optional): Character to separate the signature and the
            payload. It must not belong to the encoder alphabet and be ASCII
            (defaults to ".").

    Raises:
        ConversionError: A bytes parameter is not bytes and can't be converted
            to bytes.
        InvalidOptionError: A parameter is out of bounds.
    """
    hasher_choice = self._validate_hasher_choice(hasher)
    digest_size = self._validate_digest_size(digest_size)
    separator = self._validate_separator(separator)
    person = self._validate_person(personalisation)
    secrets = self._validate_secret(secret)

    if deterministic:
        person += b'Deterministic'
    person += self.__class__.__name__.encode()

    self._deterministic: bool = deterministic
    self._separator: bytes = separator

    self._hasher = self._get_hasher(
        hasher_choice,
        secrets=secrets,
        digest_size=digest_size,
        person=person,
    )

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

Get the proper hasher instance regarding the choice.

Source code in blake2signer/bases.py
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
@staticmethod
def _get_hasher(
    hasher: HasherChoice,
    *,
    secrets: typing.Tuple[bytes, ...],
    digest_size: int,
    person: bytes,
) -> BLAKEHasher:
    """Get the proper hasher instance regarding the choice."""
    hasher_class: typing.Type[BLAKEHasher]

    if hasher in {HasherChoice.blake2b, HasherChoice.blake2s}:
        hasher_class = BLAKE2Hasher
    else:
        hasher_class = BLAKE3Hasher

    return hasher_class(
        hasher,
        secrets=secrets,
        digest_size=digest_size,
        person=person,
    )

_validate_digest_size(digest_size)

Validate the digest_size value and return it clean.

Parameters:

Name Type Description Default
digest_size typing.Optional[int]

Digest size value to validate.

required

Returns:

Type Description
int

Cleaned digest size value.

Raises:

Type Description
InvalidOptionError

The value is out of bounds.

Source code in blake2signer/bases.py
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
def _validate_digest_size(self, digest_size: typing.Optional[int]) -> int:
    """Validate the digest_size value and return it clean.

    Args:
        digest_size: Digest size value to validate.

    Returns:
        Cleaned digest size value.

    Raises:
        InvalidOptionError: The value is out of bounds.
    """
    if digest_size is None:
        digest_size = self.DEFAULT_DIGEST_SIZE

    if digest_size < self.MIN_DIGEST_SIZE:
        raise InvalidOptionError(
            f'digest_size should be bigger than or equal to {self.MIN_DIGEST_SIZE}',
        )

    return digest_size

_validate_hasher_choice(hasher)

Validate the hasher choice.

Parameters:

Name Type Description Default
hasher typing.Union[HasherChoice, str]

the hasher choice to validate.

required

Returns:

Type Description
HasherChoice

A validated hasher choice as HasherChoice.

Raises:

Type Description
InvalidOptionError

Invalid hasher choice.

Source code in blake2signer/bases.py
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
@staticmethod
def _validate_hasher_choice(hasher: typing.Union[HasherChoice, str]) -> HasherChoice:
    """Validate the hasher choice.

    Args:
        hasher: the hasher choice to validate.

    Returns:
        A validated hasher choice as HasherChoice.

    Raises:
        InvalidOptionError: Invalid hasher choice.
    """
    try:
        choice = HasherChoice(hasher)
    except ValueError:
        raise InvalidOptionError(
            f'invalid hasher choice, must be one of: {", ".join(h for h in HasherChoice)}',
        )

    return choice

_validate_person(person)

Validate the personalisation value and return it clean.

Parameters:

Name Type Description Default
person typing.Union[str, bytes]

Personalisation value to validate.

required

Returns:

Type Description
bytes

Cleaned personalisation value.

Raises:

Type Description
ConversionError

The value is not bytes and can't be converted to bytes.

Source code in blake2signer/bases.py
184
185
186
187
188
189
190
191
192
193
194
195
196
def _validate_person(self, person: typing.Union[str, bytes]) -> bytes:
    """Validate the personalisation value and return it clean.

    Args:
        person: Personalisation value to validate.

    Returns:
        Cleaned personalisation value.

    Raises:
        ConversionError: The value is not bytes and can't be converted to bytes.
    """
    return self._force_bytes(person)

_validate_secret(secret)

Validate the secret value and return it clean.

Parameters:

Name Type Description Default
secret typing.Union[Secret, typing.Sequence[Secret]]

Secret value to validate.

required

Returns:

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

Cleaned secrets tuple.

Raises:

Type Description
ConversionError

The value is not bytes and can't be converted to bytes.

InvalidOptionError

The value is out of bounds.

Source code in blake2signer/bases.py
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
def _validate_secret(
    self,
    secret: typing.Union[Secret, typing.Sequence[Secret]],
) -> typing.Tuple[bytes, ...]:
    """Validate the secret value and return it clean.

    Args:
        secret: Secret value to validate.

    Returns:
        Cleaned secrets tuple.

    Raises:
        ConversionError: The value is not bytes and can't be converted to bytes.
        InvalidOptionError: The value is out of bounds.
    """

    def coerce(secret_: Secret, position_: int) -> bytes:
        """Coerce a given secret into bytes.

        Args:
            secret_: Secret value to validate.
            position_: Position of the secret in the sequence.

        Returns:
            A secret value as bytes.

        Raises:
            InvalidOptionError: the secret in given position is invalid.
        """
        coerced = self._force_bytes(secret_)
        if len(coerced) < self.MIN_SECRET_SIZE:
            raise InvalidOptionError(
                f'the {ordinal(position_)} secret should be longer than '
                + f'{self.MIN_SECRET_SIZE} bytes',
            )

        return coerced

    secrets: typing.Sequence[Secret]
    if isinstance(secret, (str, bytes)):
        secrets = [secret]
    else:
        secrets = secret

    return tuple(coerce(dirty, position) for position, dirty in enumerate(secrets, start=1))

_validate_separator(separator)

Validate the separator value and return it clean.

Parameters:

Name Type Description Default
separator typing.Union[str, bytes]

Separator value to validate.

required

Returns:

Type Description
bytes

Cleaned separator value.

Raises:

Type Description
ConversionError

The value is not bytes and can't be converted to bytes.

InvalidOptionError

The value is out of bounds.

Source code in blake2signer/bases.py
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
def _validate_separator(self, separator: typing.Union[str, bytes]) -> bytes:
    """Validate the separator value and return it clean.

    Args:
        separator: Separator value to validate.

    Returns:
        Cleaned separator value.

    Raises:
        ConversionError: The value is not bytes and can't be converted to bytes.
        InvalidOptionError:  The value is out of bounds.
    """
    if not separator:
        raise InvalidOptionError('the separator character must have a value')

    if not separator.isascii():
        raise InvalidOptionError('the separator character must be ASCII')

    return self._force_bytes(separator)

blake2signer.bases.Blake2SignerBase

Bases: EncoderMixin, Base, ABC

Base class for a signer based on BLAKE in keyed hashing mode.

Source code in blake2signer/bases.py
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
class Blake2SignerBase(EncoderMixin, Base, ABC):
    """Base class for a signer based on BLAKE in keyed hashing mode."""

    def __init__(
        self,
        secret: typing.Union[Secret, typing.Sequence[Secret]],
        *,
        personalisation: typing.Union[str, bytes] = b'',
        digest_size: typing.Optional[int] = None,
        hasher: typing.Union[HasherChoice, str] = HasherChoice.blake2b,
        deterministic: bool = False,
        separator: typing.Union[str, bytes] = b'.',
        encoder: typing.Type[EncoderInterface] = B64URLEncoder,
    ) -> None:
        """Sign and verify signed data using BLAKE in keyed hashing mode.

        Args:
            secret: Secret value which will be derived using BLAKE to produce the
                signing key. The minimum secret size is enforced to 16 bytes and there
                is no maximum. You can optionally provide a sequence of secrets, oldest
                to newest, that are used during signature check to allow for secret
                rotation. The last, newest, secret is used for signing.

        Keyword Args:
            personalisation (optional): 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. It defaults to the class name.
            digest_size (optional): Size of output signature (digest) in bytes
                (defaults to 16 bytes). The minimum size is enforced to 16 bytes.
            hasher (optional): Hash function to use: blake2b (default), blake2s
                or blake3.
            deterministic (optional): Define if signatures are deterministic or
                non-deterministic (default). Non-deterministic sigs are preferred,
                and achieved through the use of a random salt. For deterministic
                sigs, no salt is used: this means that for the same payload, the
                same sig is obtained (the advantage is that the sig is shorter).
            separator (optional): Character to separate the signature and the
                payload. It must not belong to the encoder alphabet and be ASCII
                (defaults to ".").
            encoder (optional): Encoder class to use for the signature, nothing
                else is encoded (defaults to a Base64 URL safe encoder).

        Raises:
            ConversionError: A bytes parameter is not bytes and can't be converted
                to bytes.
            InvalidOptionError: A parameter is out of bounds.
        """
        super().__init__(
            secret,
            personalisation=personalisation,
            digest_size=digest_size,
            hasher=hasher,
            separator=separator,
            deterministic=deterministic,
            encoder=encoder,
        )

    def _validate_separator(self, separator: typing.Union[str, bytes]) -> bytes:
        """Validate the separator value and return it clean.

        Args:
            separator: Separator value to validate.

        Returns:
            Cleaned separator value.

        Raises:
            ConversionError: The value is not bytes and can't be converted to bytes.
            InvalidOptionError: The value is out of bounds.
        """
        sep = super()._validate_separator(separator)

        if sep in self._encoder.alphabet:
            raise InvalidOptionError(
                'the separator character must not belong to the encoder alphabet',
            )

        return sep

    def _get_salt(self) -> bytes:
        """Get a salt for the signature considering its type.

        For non-deterministic signatures, a pseudo random salt is generated.

        Returns:
            A pseudorandom salt for the signature if non-deterministic, otherwise an
            empty bytes string.
        """
        if self._deterministic:
            return b''

        salt_size = self._hasher.salt_size
        salt = os.urandom(salt_size)
        # Produce an encoded salt to use it as is, so we don't have to deal with
        # decoding it when unsigning. The only downside is that we loose a few
        # bits, but it's tolerable since we are using the maximum allowed size.
        return self._encode(salt)[:salt_size]

    def _force_bytes_parts(
        self,
        signature: typing.Union[Blake2Signature, Blake2SignatureDump],
    ) -> Blake2Signature:
        """Force given value into bytes, meaning a Blake2Signature container."""
        return Blake2Signature(
            data=self._force_bytes(signature.data),
            signature=self._force_bytes(signature.signature),
        )

    def _compose(self, data: bytes, *, signature: bytes) -> bytes:
        """Compose data and signature into a single stream."""
        return signature + self._separator + data

    def _decompose(self, signed_data: bytes) -> SignedDataParts:
        """Decompose a signed data stream into its parts.

        Args:
            signed_data: the signed data stream to decompose.

        Returns:
            The decomposed signed data in a container.

        Raises:
            SignatureError: Invalid signed data.
        """
        if self._separator not in signed_data:
            raise SignatureError('separator not found in signed data')

        composite_signature, data = signed_data.split(self._separator, 1)

        if not composite_signature:
            raise SignatureError('signature information is missing')

        if self._deterministic:
            salt = b''
            signature = composite_signature
        else:
            salt_size = self._hasher.salt_size
            salt = composite_signature[:salt_size]
            signature = composite_signature[salt_size:]

        return SignedDataParts(data=data, salt=salt, signature=signature)

    def _signify(self, *, data: bytes, salt: bytes, key: bytes) -> bytes:
        """Return signature for given data using salt and all the hasher options.

        The signature is encoded using the chosen encoder.

        Keyword Args:
            data: data to sign.
            salt: salt to use in the signature.
            key: the signing key.

        Returns:
            An encoded signature.
        """
        signature = self._hasher.digest(data, key=key, salt=salt)

        return self._encode(signature)

    def _sign(self, data: bytes) -> bytes:
        """Sign given data and produce a signature stream composed of salt and signature.

        The signature stream (salt and signature) is encoded using the chosen encoder.

        Args:
            data: data to sign.

        Returns:
            A signature stream containing salt and signature.
        """
        salt = self._get_salt()
        signature = self._signify(data=data, salt=salt, key=self._hasher.signing_key)

        return salt + signature

    def _unsign(self, parts: SignedDataParts) -> bytes:
        """Verify signed data parts and recover original data.

        Args:
            parts: Signed data parts to unsign.

        Returns:
            Original data.

        Raises:
            InvalidSignatureError: Signed data signature is invalid.
        """
        for key in reversed(self._hasher.keys):
            signature = self._signify(data=parts.data, salt=parts.salt, key=key)
            if compare_digest(signature, parts.signature):
                return parts.data

        raise InvalidSignatureError('signature is not valid')

__init__(secret, *, personalisation=b'', digest_size=None, hasher=HasherChoice.blake2b, deterministic=False, separator=b'.', encoder=B64URLEncoder)

Sign and verify signed data using BLAKE in keyed hashing mode.

Parameters:

Name Type Description Default
secret typing.Union[Secret, typing.Sequence[Secret]]

Secret value which will be derived using BLAKE to produce the signing key. The minimum secret size is enforced to 16 bytes and there is no maximum. You can optionally provide a sequence of secrets, oldest to newest, that are used during signature check to allow for secret rotation. The last, newest, secret is used for signing.

required

Other Parameters:

Name Type Description
personalisation optional

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. It defaults to the class name.

digest_size optional

Size of output signature (digest) in bytes (defaults to 16 bytes). The minimum size is enforced to 16 bytes.

hasher optional

Hash function to use: blake2b (default), blake2s or blake3.

deterministic optional

Define if signatures are deterministic or non-deterministic (default). Non-deterministic sigs are preferred, and achieved through the use of a random salt. For deterministic sigs, no salt is used: this means that for the same payload, the same sig is obtained (the advantage is that the sig is shorter).

separator optional

Character to separate the signature and the payload. It must not belong to the encoder alphabet and be ASCII (defaults to ".").

encoder optional

Encoder class to use for the signature, nothing else is encoded (defaults to a Base64 URL safe encoder).

Raises:

Type Description
ConversionError

A bytes parameter is not bytes and can't be converted to bytes.

InvalidOptionError

A parameter is out of bounds.

Source code in blake2signer/bases.py
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
def __init__(
    self,
    secret: typing.Union[Secret, typing.Sequence[Secret]],
    *,
    personalisation: typing.Union[str, bytes] = b'',
    digest_size: typing.Optional[int] = None,
    hasher: typing.Union[HasherChoice, str] = HasherChoice.blake2b,
    deterministic: bool = False,
    separator: typing.Union[str, bytes] = b'.',
    encoder: typing.Type[EncoderInterface] = B64URLEncoder,
) -> None:
    """Sign and verify signed data using BLAKE in keyed hashing mode.

    Args:
        secret: Secret value which will be derived using BLAKE to produce the
            signing key. The minimum secret size is enforced to 16 bytes and there
            is no maximum. You can optionally provide a sequence of secrets, oldest
            to newest, that are used during signature check to allow for secret
            rotation. The last, newest, secret is used for signing.

    Keyword Args:
        personalisation (optional): 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. It defaults to the class name.
        digest_size (optional): Size of output signature (digest) in bytes
            (defaults to 16 bytes). The minimum size is enforced to 16 bytes.
        hasher (optional): Hash function to use: blake2b (default), blake2s
            or blake3.
        deterministic (optional): Define if signatures are deterministic or
            non-deterministic (default). Non-deterministic sigs are preferred,
            and achieved through the use of a random salt. For deterministic
            sigs, no salt is used: this means that for the same payload, the
            same sig is obtained (the advantage is that the sig is shorter).
        separator (optional): Character to separate the signature and the
            payload. It must not belong to the encoder alphabet and be ASCII
            (defaults to ".").
        encoder (optional): Encoder class to use for the signature, nothing
            else is encoded (defaults to a Base64 URL safe encoder).

    Raises:
        ConversionError: A bytes parameter is not bytes and can't be converted
            to bytes.
        InvalidOptionError: A parameter is out of bounds.
    """
    super().__init__(
        secret,
        personalisation=personalisation,
        digest_size=digest_size,
        hasher=hasher,
        separator=separator,
        deterministic=deterministic,
        encoder=encoder,
    )

_compose(data, *, signature)

Compose data and signature into a single stream.

Source code in blake2signer/bases.py
396
397
398
def _compose(self, data: bytes, *, signature: bytes) -> bytes:
    """Compose data and signature into a single stream."""
    return signature + self._separator + data

_decompose(signed_data)

Decompose a signed data stream into its parts.

Parameters:

Name Type Description Default
signed_data bytes

the signed data stream to decompose.

required

Returns:

Type Description
SignedDataParts

The decomposed signed data in a container.

Raises:

Type Description
SignatureError

Invalid signed data.

Source code in blake2signer/bases.py
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
def _decompose(self, signed_data: bytes) -> SignedDataParts:
    """Decompose a signed data stream into its parts.

    Args:
        signed_data: the signed data stream to decompose.

    Returns:
        The decomposed signed data in a container.

    Raises:
        SignatureError: Invalid signed data.
    """
    if self._separator not in signed_data:
        raise SignatureError('separator not found in signed data')

    composite_signature, data = signed_data.split(self._separator, 1)

    if not composite_signature:
        raise SignatureError('signature information is missing')

    if self._deterministic:
        salt = b''
        signature = composite_signature
    else:
        salt_size = self._hasher.salt_size
        salt = composite_signature[:salt_size]
        signature = composite_signature[salt_size:]

    return SignedDataParts(data=data, salt=salt, signature=signature)

_force_bytes_parts(signature)

Force given value into bytes, meaning a Blake2Signature container.

Source code in blake2signer/bases.py
386
387
388
389
390
391
392
393
394
def _force_bytes_parts(
    self,
    signature: typing.Union[Blake2Signature, Blake2SignatureDump],
) -> Blake2Signature:
    """Force given value into bytes, meaning a Blake2Signature container."""
    return Blake2Signature(
        data=self._force_bytes(signature.data),
        signature=self._force_bytes(signature.signature),
    )

_get_salt()

Get a salt for the signature considering its type.

For non-deterministic signatures, a pseudo random salt is generated.

Returns:

Type Description
bytes

A pseudorandom salt for the signature if non-deterministic, otherwise an

bytes

empty bytes string.

Source code in blake2signer/bases.py
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
def _get_salt(self) -> bytes:
    """Get a salt for the signature considering its type.

    For non-deterministic signatures, a pseudo random salt is generated.

    Returns:
        A pseudorandom salt for the signature if non-deterministic, otherwise an
        empty bytes string.
    """
    if self._deterministic:
        return b''

    salt_size = self._hasher.salt_size
    salt = os.urandom(salt_size)
    # Produce an encoded salt to use it as is, so we don't have to deal with
    # decoding it when unsigning. The only downside is that we loose a few
    # bits, but it's tolerable since we are using the maximum allowed size.
    return self._encode(salt)[:salt_size]

_sign(data)

Sign given data and produce a signature stream composed of salt and signature.

The signature stream (salt and signature) is encoded using the chosen encoder.

Parameters:

Name Type Description Default
data bytes

data to sign.

required

Returns:

Type Description
bytes

A signature stream containing salt and signature.

Source code in blake2signer/bases.py
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
def _sign(self, data: bytes) -> bytes:
    """Sign given data and produce a signature stream composed of salt and signature.

    The signature stream (salt and signature) is encoded using the chosen encoder.

    Args:
        data: data to sign.

    Returns:
        A signature stream containing salt and signature.
    """
    salt = self._get_salt()
    signature = self._signify(data=data, salt=salt, key=self._hasher.signing_key)

    return salt + signature

_signify(*, data, salt, key)

Return signature for given data using salt and all the hasher options.

The signature is encoded using the chosen encoder.

Other Parameters:

Name Type Description
data bytes

data to sign.

salt bytes

salt to use in the signature.

key bytes

the signing key.

Returns:

Type Description
bytes

An encoded signature.

Source code in blake2signer/bases.py
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
def _signify(self, *, data: bytes, salt: bytes, key: bytes) -> bytes:
    """Return signature for given data using salt and all the hasher options.

    The signature is encoded using the chosen encoder.

    Keyword Args:
        data: data to sign.
        salt: salt to use in the signature.
        key: the signing key.

    Returns:
        An encoded signature.
    """
    signature = self._hasher.digest(data, key=key, salt=salt)

    return self._encode(signature)

_unsign(parts)

Verify signed data parts and recover original data.

Parameters:

Name Type Description Default
parts SignedDataParts

Signed data parts to unsign.

required

Returns:

Type Description
bytes

Original data.

Raises:

Type Description
InvalidSignatureError

Signed data signature is invalid.

Source code in blake2signer/bases.py
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
def _unsign(self, parts: SignedDataParts) -> bytes:
    """Verify signed data parts and recover original data.

    Args:
        parts: Signed data parts to unsign.

    Returns:
        Original data.

    Raises:
        InvalidSignatureError: Signed data signature is invalid.
    """
    for key in reversed(self._hasher.keys):
        signature = self._signify(data=parts.data, salt=parts.salt, key=key)
        if compare_digest(signature, parts.signature):
            return parts.data

    raise InvalidSignatureError('signature is not valid')

_validate_separator(separator)

Validate the separator value and return it clean.

Parameters:

Name Type Description Default
separator typing.Union[str, bytes]

Separator value to validate.

required

Returns:

Type Description
bytes

Cleaned separator value.

Raises:

Type Description
ConversionError

The value is not bytes and can't be converted to bytes.

InvalidOptionError

The value is out of bounds.

Source code in blake2signer/bases.py
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
def _validate_separator(self, separator: typing.Union[str, bytes]) -> bytes:
    """Validate the separator value and return it clean.

    Args:
        separator: Separator value to validate.

    Returns:
        Cleaned separator value.

    Raises:
        ConversionError: The value is not bytes and can't be converted to bytes.
        InvalidOptionError: The value is out of bounds.
    """
    sep = super()._validate_separator(separator)

    if sep in self._encoder.alphabet:
        raise InvalidOptionError(
            'the separator character must not belong to the encoder alphabet',
        )

    return sep

blake2signer.bases.Blake2TimestampSignerBase

Bases: Blake2SignerBase, ABC

Base class for a timestamp signer based on BLAKE in keyed hashing mode.

Source code in blake2signer/bases.py
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
class Blake2TimestampSignerBase(Blake2SignerBase, ABC):
    """Base class for a timestamp signer based on BLAKE in keyed hashing mode."""

    def _get_timestamp(self) -> bytes:
        """Get the encoded timestamp value."""
        timestamp = int(time())  # It's easier to encode and decode an integer
        try:
            timestamp_b = timestamp.to_bytes(4, 'big', signed=False)
        except OverflowError:  # This will happen in ~2106-02-07
            raise RuntimeError(
                'can not represent this timestamp in bytes: this library is '
                + 'too old and needs to be updated!',
            )

        return self._encode(timestamp_b)

    def _decode_timestamp(self, encoded_timestamp: bytes) -> int:
        """Decode an encoded timestamp whose signature should have been validated.

        Args:
            encoded_timestamp: encoded timestamp to decode.

        Returns:
            An integer timestamp.

        Raises:
            DecodeError: Timestamp can't be decoded.
        """
        return int.from_bytes(self._decode(encoded_timestamp), 'big', signed=False)

    def _compose_timestamp(self, data: bytes, *, timestamp: bytes) -> bytes:
        """Compose timestamp value with data."""
        return timestamp + self._separator + data

    def _decompose_timestamp(self, timestamped_data: bytes) -> TimestampedDataParts:
        """Decompose data + timestamp value.

        Args:
            timestamped_data: composed data with timestamp to decompose.

        Returns:
            A container with data and timestamp.

        Raises:
            SignatureError: Invalid timestamped data.
            DecodeError: Timestamp can't be decoded.
        """
        if self._separator not in timestamped_data:
            raise SignatureError('separator not found in timestamped data')

        encoded_timestamp, data = timestamped_data.split(self._separator, 1)

        if not encoded_timestamp:
            raise SignatureError('timestamp information is missing')

        timestamp = self._decode_timestamp(encoded_timestamp)

        return TimestampedDataParts(data=data, timestamp=timestamp)

    @staticmethod
    def _get_ttl_from_max_age(max_age: typing.Union[int, float, timedelta]) -> float:
        """Get the time-to-live value in seconds."""
        if isinstance(max_age, timedelta):
            return max_age.total_seconds()

        return float(max_age)

    def _sign_with_timestamp(self, data: bytes) -> bytes:
        """Sign given data and produce a timestamped signature stream.

        The timestamped signature stream (timestamp, signature and salt) is
        encoded using the chosen encoder.

        Args:
            data: data to sign.

        Returns:
            A signature stream composed of salt, signature and timestamp.
        """
        timestamp = self._get_timestamp()
        timestamped_data = self._compose_timestamp(data, timestamp=timestamp)

        return self._compose(timestamp, signature=self._sign(timestamped_data))

    def _unsign_with_timestamp(
        self,
        parts: SignedDataParts,
        *,
        max_age: typing.Union[None, int, float, timedelta],
    ) -> bytes:
        """Verify signed data parts with timestamp and recover original data.

        If `max_age` is not provided, then the timestamp is not checked (the
        signature is always checked).

        Args:
            parts: Signed data parts to unsign.

        Keyword Args:
            max_age (optional): Ensure the signature is not older than this time
                in seconds.

        Returns:
            Original data.

        Raises:
            SignatureError: Signed data structure is not valid.
            InvalidSignatureError: Signed data signature is invalid.
            ExpiredSignatureError: Signed data signature has expired.
            DecodeError: Timestamp can't be decoded.
        """
        timestamped_data = self._unsign(parts)

        timestamped_parts = self._decompose_timestamp(timestamped_data)

        if max_age is None:
            return timestamped_parts.data

        now = time()
        age = now - timestamped_parts.timestamp
        ttl = self._get_ttl_from_max_age(max_age)

        if age > ttl:
            raise ExpiredSignatureError(
                f'signature has expired, age {age} > {ttl} seconds',
                timestamp=timestamp_to_aware_datetime(timestamped_parts.timestamp),
            )

        if age < 0:  # Signed in the future
            raise ExpiredSignatureError(
                f'signature has expired, age {age} < 0 seconds',
                timestamp=timestamp_to_aware_datetime(timestamped_parts.timestamp),
            )

        return timestamped_parts.data

_compose_timestamp(data, *, timestamp)

Compose timestamp value with data.

Source code in blake2signer/bases.py
513
514
515
def _compose_timestamp(self, data: bytes, *, timestamp: bytes) -> bytes:
    """Compose timestamp value with data."""
    return timestamp + self._separator + data

_decode_timestamp(encoded_timestamp)

Decode an encoded timestamp whose signature should have been validated.

Parameters:

Name Type Description Default
encoded_timestamp bytes

encoded timestamp to decode.

required

Returns:

Type Description
int

An integer timestamp.

Raises:

Type Description
DecodeError

Timestamp can't be decoded.

Source code in blake2signer/bases.py
499
500
501
502
503
504
505
506
507
508
509
510
511
def _decode_timestamp(self, encoded_timestamp: bytes) -> int:
    """Decode an encoded timestamp whose signature should have been validated.

    Args:
        encoded_timestamp: encoded timestamp to decode.

    Returns:
        An integer timestamp.

    Raises:
        DecodeError: Timestamp can't be decoded.
    """
    return int.from_bytes(self._decode(encoded_timestamp), 'big', signed=False)

_decompose_timestamp(timestamped_data)

Decompose data + timestamp value.

Parameters:

Name Type Description Default
timestamped_data bytes

composed data with timestamp to decompose.

required

Returns:

Type Description
TimestampedDataParts

A container with data and timestamp.

Raises:

Type Description
SignatureError

Invalid timestamped data.

DecodeError

Timestamp can't be decoded.

Source code in blake2signer/bases.py
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
def _decompose_timestamp(self, timestamped_data: bytes) -> TimestampedDataParts:
    """Decompose data + timestamp value.

    Args:
        timestamped_data: composed data with timestamp to decompose.

    Returns:
        A container with data and timestamp.

    Raises:
        SignatureError: Invalid timestamped data.
        DecodeError: Timestamp can't be decoded.
    """
    if self._separator not in timestamped_data:
        raise SignatureError('separator not found in timestamped data')

    encoded_timestamp, data = timestamped_data.split(self._separator, 1)

    if not encoded_timestamp:
        raise SignatureError('timestamp information is missing')

    timestamp = self._decode_timestamp(encoded_timestamp)

    return TimestampedDataParts(data=data, timestamp=timestamp)

_get_timestamp()

Get the encoded timestamp value.

Source code in blake2signer/bases.py
486
487
488
489
490
491
492
493
494
495
496
497
def _get_timestamp(self) -> bytes:
    """Get the encoded timestamp value."""
    timestamp = int(time())  # It's easier to encode and decode an integer
    try:
        timestamp_b = timestamp.to_bytes(4, 'big', signed=False)
    except OverflowError:  # This will happen in ~2106-02-07
        raise RuntimeError(
            'can not represent this timestamp in bytes: this library is '
            + 'too old and needs to be updated!',
        )

    return self._encode(timestamp_b)

_get_ttl_from_max_age(max_age)

Get the time-to-live value in seconds.

Source code in blake2signer/bases.py
542
543
544
545
546
547
548
@staticmethod
def _get_ttl_from_max_age(max_age: typing.Union[int, float, timedelta]) -> float:
    """Get the time-to-live value in seconds."""
    if isinstance(max_age, timedelta):
        return max_age.total_seconds()

    return float(max_age)

_sign_with_timestamp(data)

Sign given data and produce a timestamped signature stream.

The timestamped signature stream (timestamp, signature and salt) is encoded using the chosen encoder.

Parameters:

Name Type Description Default
data bytes

data to sign.

required

Returns:

Type Description
bytes

A signature stream composed of salt, signature and timestamp.

Source code in blake2signer/bases.py
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
def _sign_with_timestamp(self, data: bytes) -> bytes:
    """Sign given data and produce a timestamped signature stream.

    The timestamped signature stream (timestamp, signature and salt) is
    encoded using the chosen encoder.

    Args:
        data: data to sign.

    Returns:
        A signature stream composed of salt, signature and timestamp.
    """
    timestamp = self._get_timestamp()
    timestamped_data = self._compose_timestamp(data, timestamp=timestamp)

    return self._compose(timestamp, signature=self._sign(timestamped_data))

_unsign_with_timestamp(parts, *, max_age)

Verify signed data parts with timestamp and recover original data.

If max_age is not provided, then the timestamp is not checked (the signature is always checked).

Parameters:

Name Type Description Default
parts SignedDataParts

Signed data parts to unsign.

required

Other Parameters:

Name Type Description
max_age optional

Ensure the signature is not older than this time in seconds.

Returns:

Type Description
bytes

Original data.

Raises:

Type Description
SignatureError

Signed data structure is not valid.

InvalidSignatureError

Signed data signature is invalid.

ExpiredSignatureError

Signed data signature has expired.

DecodeError

Timestamp can't be decoded.

Source code in blake2signer/bases.py
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
def _unsign_with_timestamp(
    self,
    parts: SignedDataParts,
    *,
    max_age: typing.Union[None, int, float, timedelta],
) -> bytes:
    """Verify signed data parts with timestamp and recover original data.

    If `max_age` is not provided, then the timestamp is not checked (the
    signature is always checked).

    Args:
        parts: Signed data parts to unsign.

    Keyword Args:
        max_age (optional): Ensure the signature is not older than this time
            in seconds.

    Returns:
        Original data.

    Raises:
        SignatureError: Signed data structure is not valid.
        InvalidSignatureError: Signed data signature is invalid.
        ExpiredSignatureError: Signed data signature has expired.
        DecodeError: Timestamp can't be decoded.
    """
    timestamped_data = self._unsign(parts)

    timestamped_parts = self._decompose_timestamp(timestamped_data)

    if max_age is None:
        return timestamped_parts.data

    now = time()
    age = now - timestamped_parts.timestamp
    ttl = self._get_ttl_from_max_age(max_age)

    if age > ttl:
        raise ExpiredSignatureError(
            f'signature has expired, age {age} > {ttl} seconds',
            timestamp=timestamp_to_aware_datetime(timestamped_parts.timestamp),
        )

    if age < 0:  # Signed in the future
        raise ExpiredSignatureError(
            f'signature has expired, age {age} < 0 seconds',
            timestamp=timestamp_to_aware_datetime(timestamped_parts.timestamp),
        )

    return timestamped_parts.data

blake2signer.bases.Blake2DualSignerBase

Bases: Blake2TimestampSignerBase, ABC

Base class for a dual signer: with and without timestamp.

Source code in blake2signer/bases.py
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
class Blake2DualSignerBase(Blake2TimestampSignerBase, ABC):
    """Base class for a dual signer: with and without timestamp."""

    def __init__(
        self,
        secret: typing.Union[Secret, typing.Sequence[Secret]],
        *,
        max_age: typing.Union[None, int, float, timedelta] = None,
        personalisation: typing.Union[str, bytes] = b'',
        digest_size: typing.Optional[int] = None,
        hasher: typing.Union[HasherChoice, str] = HasherChoice.blake2b,
        deterministic: bool = False,
        separator: typing.Union[str, bytes] = b'.',
        encoder: typing.Type[EncoderInterface] = B64URLEncoder,
    ) -> None:
        """Sign and verify signed and optionally timestamped data using BLAKE.

        It uses BLAKE in keyed hashing mode.

        Setting `max_age` will produce a timestamped signed stream.

        Args:
            secret: Secret value which will be derived using BLAKE to produce the
                signing key. The minimum secret size is enforced to 16 bytes and there
                is no maximum. You can optionally provide a sequence of secrets, oldest
                to newest, that are used during signature check to allow for secret
                rotation. The last, newest, secret is used for signing.

        Keyword Args:
            max_age (optional): Use a timestamp signer instead of a regular one
                to ensure that the signature is not older than this time in seconds.
            personalisation (optional): 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. It defaults to the class name.
            digest_size (optional): Size of output signature (digest) in bytes
                (defaults to 16 bytes). The minimum size is enforced to 16 bytes.
            hasher (optional): Hash function to use: blake2b (default), blake2s
                or blake3.
            deterministic (optional): Define if signatures are deterministic or
                non-deterministic (default). Non-deterministic sigs are preferred,
                and achieved through the use of a random salt. For deterministic
                sigs, no salt is used: this means that for the same payload, the
                same sig is obtained (the advantage is that the sig is shorter).
            separator (optional): Character to separate the signature and the
                payload. It must not belong to the encoder alphabet and be ASCII
                (defaults to ".").
            encoder (optional): Encoder class to use (defaults to a Base64 URL
                safe encoder).

        Raises:
            ConversionError: A bytes parameter is not bytes and can't be converted
                to bytes.
            InvalidOptionError: A parameter is out of bounds.
        """
        if max_age is not None:
            personalisation = self._force_bytes(personalisation) + b'Timestamp'

        self._max_age: typing.Union[None, int, float, timedelta] = max_age

        super().__init__(
            secret,
            personalisation=personalisation,
            digest_size=digest_size,
            hasher=hasher,
            deterministic=deterministic,
            separator=separator,
            encoder=encoder,
        )

    def _proper_sign(self, data: bytes) -> bytes:
        """Sign given data with a (timestamp) signer producing a signature stream.

        The signature stream (salt, signature and/or timestamp) is encoded using
        the chosen encoder.

        Args:
            data: data to sign.

        Returns:
            A signature stream of the salt, signature, and timestamp, if corresponds.
        """
        if self._max_age is None:
            return self._sign(data)

        return self._sign_with_timestamp(data)

    def _proper_unsign(self, parts: SignedDataParts) -> bytes:
        """Unsign signed data properly with the corresponding signer.

        Args:
            parts: signed data parts to unsign.

        Returns:
            Original data.

        Raises:
            SignatureError: Signed data structure is not valid.
            InvalidSignatureError: Signed data signature is invalid.
            ExpiredSignatureError: Signed data signature has expired.
            DecodeError: Timestamp can't be decoded.
        """
        if self._max_age is None:
            return self._unsign(parts)

        return self._unsign_with_timestamp(parts, max_age=self._max_age)

__init__(secret, *, max_age=None, personalisation=b'', digest_size=None, hasher=HasherChoice.blake2b, deterministic=False, separator=b'.', encoder=B64URLEncoder)

Sign and verify signed and optionally timestamped data using BLAKE.

It uses BLAKE in keyed hashing mode.

Setting max_age will produce a timestamped signed stream.

Parameters:

Name Type Description Default
secret typing.Union[Secret, typing.Sequence[Secret]]

Secret value which will be derived using BLAKE to produce the signing key. The minimum secret size is enforced to 16 bytes and there is no maximum. You can optionally provide a sequence of secrets, oldest to newest, that are used during signature check to allow for secret rotation. The last, newest, secret is used for signing.

required

Other Parameters:

Name Type Description
max_age optional

Use a timestamp signer instead of a regular one to ensure that the signature is not older than this time in seconds.

personalisation optional

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. It defaults to the class name.

digest_size optional

Size of output signature (digest) in bytes (defaults to 16 bytes). The minimum size is enforced to 16 bytes.

hasher optional

Hash function to use: blake2b (default), blake2s or blake3.

deterministic optional

Define if signatures are deterministic or non-deterministic (default). Non-deterministic sigs are preferred, and achieved through the use of a random salt. For deterministic sigs, no salt is used: this means that for the same payload, the same sig is obtained (the advantage is that the sig is shorter).

separator optional

Character to separate the signature and the payload. It must not belong to the encoder alphabet and be ASCII (defaults to ".").

encoder optional

Encoder class to use (defaults to a Base64 URL safe encoder).

Raises:

Type Description
ConversionError

A bytes parameter is not bytes and can't be converted to bytes.

InvalidOptionError

A parameter is out of bounds.

Source code in blake2signer/bases.py
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
def __init__(
    self,
    secret: typing.Union[Secret, typing.Sequence[Secret]],
    *,
    max_age: typing.Union[None, int, float, timedelta] = None,
    personalisation: typing.Union[str, bytes] = b'',
    digest_size: typing.Optional[int] = None,
    hasher: typing.Union[HasherChoice, str] = HasherChoice.blake2b,
    deterministic: bool = False,
    separator: typing.Union[str, bytes] = b'.',
    encoder: typing.Type[EncoderInterface] = B64URLEncoder,
) -> None:
    """Sign and verify signed and optionally timestamped data using BLAKE.

    It uses BLAKE in keyed hashing mode.

    Setting `max_age` will produce a timestamped signed stream.

    Args:
        secret: Secret value which will be derived using BLAKE to produce the
            signing key. The minimum secret size is enforced to 16 bytes and there
            is no maximum. You can optionally provide a sequence of secrets, oldest
            to newest, that are used during signature check to allow for secret
            rotation. The last, newest, secret is used for signing.

    Keyword Args:
        max_age (optional): Use a timestamp signer instead of a regular one
            to ensure that the signature is not older than this time in seconds.
        personalisation (optional): 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. It defaults to the class name.
        digest_size (optional): Size of output signature (digest) in bytes
            (defaults to 16 bytes). The minimum size is enforced to 16 bytes.
        hasher (optional): Hash function to use: blake2b (default), blake2s
            or blake3.
        deterministic (optional): Define if signatures are deterministic or
            non-deterministic (default). Non-deterministic sigs are preferred,
            and achieved through the use of a random salt. For deterministic
            sigs, no salt is used: this means that for the same payload, the
            same sig is obtained (the advantage is that the sig is shorter).
        separator (optional): Character to separate the signature and the
            payload. It must not belong to the encoder alphabet and be ASCII
            (defaults to ".").
        encoder (optional): Encoder class to use (defaults to a Base64 URL
            safe encoder).

    Raises:
        ConversionError: A bytes parameter is not bytes and can't be converted
            to bytes.
        InvalidOptionError: A parameter is out of bounds.
    """
    if max_age is not None:
        personalisation = self._force_bytes(personalisation) + b'Timestamp'

    self._max_age: typing.Union[None, int, float, timedelta] = max_age

    super().__init__(
        secret,
        personalisation=personalisation,
        digest_size=digest_size,
        hasher=hasher,
        deterministic=deterministic,
        separator=separator,
        encoder=encoder,
    )

_proper_sign(data)

Sign given data with a (timestamp) signer producing a signature stream.

The signature stream (salt, signature and/or timestamp) is encoded using the chosen encoder.

Parameters:

Name Type Description Default
data bytes

data to sign.

required

Returns:

Type Description
bytes

A signature stream of the salt, signature, and timestamp, if corresponds.

Source code in blake2signer/bases.py
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
def _proper_sign(self, data: bytes) -> bytes:
    """Sign given data with a (timestamp) signer producing a signature stream.

    The signature stream (salt, signature and/or timestamp) is encoded using
    the chosen encoder.

    Args:
        data: data to sign.

    Returns:
        A signature stream of the salt, signature, and timestamp, if corresponds.
    """
    if self._max_age is None:
        return self._sign(data)

    return self._sign_with_timestamp(data)

_proper_unsign(parts)

Unsign signed data properly with the corresponding signer.

Parameters:

Name Type Description Default
parts SignedDataParts

signed data parts to unsign.

required

Returns:

Type Description
bytes

Original data.

Raises:

Type Description
SignatureError

Signed data structure is not valid.

InvalidSignatureError

Signed data signature is invalid.

ExpiredSignatureError

Signed data signature has expired.

DecodeError

Timestamp can't be decoded.

Source code in blake2signer/bases.py
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
def _proper_unsign(self, parts: SignedDataParts) -> bytes:
    """Unsign signed data properly with the corresponding signer.

    Args:
        parts: signed data parts to unsign.

    Returns:
        Original data.

    Raises:
        SignatureError: Signed data structure is not valid.
        InvalidSignatureError: Signed data signature is invalid.
        ExpiredSignatureError: Signed data signature has expired.
        DecodeError: Timestamp can't be decoded.
    """
    if self._max_age is None:
        return self._unsign(parts)

    return self._unsign_with_timestamp(parts, max_age=self._max_age)

blake2signer.bases.Blake2SerializerSignerBase

Bases: Blake2DualSignerBase, ABC

Base class for a serializer signer that implements dumps and loads.

Source code in blake2signer/bases.py
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
class Blake2SerializerSignerBase(Blake2DualSignerBase, ABC):
    """Base class for a serializer signer that implements `dumps` and `loads`."""

    @abstractmethod
    def _dumps(self, data: typing.Any, **kwargs: typing.Any) -> bytes:
        """Dump data serializing it.

        Implement this method with all the tasks necessary to serialize data, such
        as encoding, compression, etc.

        Args:
            data: Data to serialize.

        Keyword Args:
            **kwargs: Additional keyword only arguments for the method.

        Returns:
            Serialized data.
        """

    @abstractmethod
    def _loads(self, dumped_data: bytes, **kwargs: typing.Any) -> typing.Any:
        """Load serialized data to recover it.

        Implement this method with all the tasks necessary to unserialize data,
        such as decoding, decompression, etc.

        Args:
            dumped_data: Data to unserialize.

        Keyword Args
            **kwargs: Additional keyword only arguments for the method.

        Returns:
            Original data.
        """

    @staticmethod
    def _read(file: typing.IO) -> typing.AnyStr:
        """Read data from a file.

        Args:
            file: file to read data from.

        Returns:
            File contents.

        Raises:
            FileError: File can't be read.
        """
        try:
            return file.read()
        except OSError as exc:
            raise FileError('file can not be read') from exc

    def _write(self, file: typing.IO, data: str) -> None:
        """Write data to file.

        Args:
            file: file to write data to.
            data: data to write to the file.

        Notes:
            The file can be either in text or binary mode, therefore given data
            is properly converted before writing.

        Raises:
            FileError: File can't be written.
            ConversionError: Data can't be converted to bytes (can happen when
                file is in binary mode).
        """
        data_ = data if file_mode_is_text(file) else self._force_bytes(data)

        try:
            file.write(data_)
        except OSError as exc:
            raise FileError('file can not be written') from exc

_dumps(data, **kwargs)

Dump data serializing it.

Implement this method with all the tasks necessary to serialize data, such as encoding, compression, etc.

Parameters:

Name Type Description Default
data typing.Any

Data to serialize.

required

Other Parameters:

Name Type Description
**kwargs typing.Any

Additional keyword only arguments for the method.

Returns:

Type Description
bytes

Serialized data.

Source code in blake2signer/bases.py
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
@abstractmethod
def _dumps(self, data: typing.Any, **kwargs: typing.Any) -> bytes:
    """Dump data serializing it.

    Implement this method with all the tasks necessary to serialize data, such
    as encoding, compression, etc.

    Args:
        data: Data to serialize.

    Keyword Args:
        **kwargs: Additional keyword only arguments for the method.

    Returns:
        Serialized data.
    """

_loads(dumped_data, **kwargs)

Load serialized data to recover it.

Implement this method with all the tasks necessary to unserialize data, such as decoding, decompression, etc.

Parameters:

Name Type Description Default
dumped_data bytes

Data to unserialize.

required

Keyword Args **kwargs: Additional keyword only arguments for the method.

Returns:

Type Description
typing.Any

Original data.

Source code in blake2signer/bases.py
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
@abstractmethod
def _loads(self, dumped_data: bytes, **kwargs: typing.Any) -> typing.Any:
    """Load serialized data to recover it.

    Implement this method with all the tasks necessary to unserialize data,
    such as decoding, decompression, etc.

    Args:
        dumped_data: Data to unserialize.

    Keyword Args
        **kwargs: Additional keyword only arguments for the method.

    Returns:
        Original data.
    """

_read(file)

Read data from a file.

Parameters:

Name Type Description Default
file typing.IO

file to read data from.

required

Returns:

Type Description
typing.AnyStr

File contents.

Raises:

Type Description
FileError

File can't be read.

Source code in blake2signer/bases.py
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
@staticmethod
def _read(file: typing.IO) -> typing.AnyStr:
    """Read data from a file.

    Args:
        file: file to read data from.

    Returns:
        File contents.

    Raises:
        FileError: File can't be read.
    """
    try:
        return file.read()
    except OSError as exc:
        raise FileError('file can not be read') from exc

_write(file, data)

Write data to file.

Parameters:

Name Type Description Default
file typing.IO

file to write data to.

required
data str

data to write to the file.

required
Notes

The file can be either in text or binary mode, therefore given data is properly converted before writing.

Raises:

Type Description
FileError

File can't be written.

ConversionError

Data can't be converted to bytes (can happen when file is in binary mode).

Source code in blake2signer/bases.py
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
def _write(self, file: typing.IO, data: str) -> None:
    """Write data to file.

    Args:
        file: file to write data to.
        data: data to write to the file.

    Notes:
        The file can be either in text or binary mode, therefore given data
        is properly converted before writing.

    Raises:
        FileError: File can't be written.
        ConversionError: Data can't be converted to bytes (can happen when
            file is in binary mode).
    """
    data_ = data if file_mode_is_text(file) else self._force_bytes(data)

    try:
        file.write(data_)
    except OSError as exc:
        raise FileError('file can not be written') from exc

Signatures

blake2signer.bases.Blake2Signature

Bases: typing.NamedTuple

Signature container.

Source code in blake2signer/bases.py
51
52
53
54
55
class Blake2Signature(typing.NamedTuple):
    """Signature container."""

    signature: bytes  # Composite signature
    data: bytes

blake2signer.bases.Blake2SignatureDump

Bases: typing.NamedTuple

Signature container.

Source code in blake2signer/bases.py
44
45
46
47
48
class Blake2SignatureDump(typing.NamedTuple):
    """Signature container."""

    signature: str  # Composite signature
    data: str
Back to top