Comparison¶
Comparison against other similar libraries in terms of performance and capabilities.
It's Dangerous¶
Signing interface¶
Both itsdangerous.Signer and itsdangerous.TimestampSigner appends the signature to the given string, whereas blake2signer.Blake2Signer and blake2signer.Blake2TimestampSigner prepends it (with salt). Other than that, this package can be a drop-in replacement and vice versa.
from itsdangerous import SignatureExpired
from itsdangerous import Signer
from itsdangerous import TimestampSigner
# Signer
signer = Signer('secret-key')
print(signer.sign('my string'))
print(signer.unsign(b'my string.wh6tMHxLgJqB6oY1uT73iMlyrOA')) # b'my string'
# Timestamp signer
signer = TimestampSigner('secret-key')
print(signer.sign('foo'))
try:
print(signer.unsign(b'foo.YIZMkg.FQdIC8gFmkwy4w8KC05IQfzU-Gc', max_age=3600)) # b'foo'
except SignatureExpired as exc:
print(repr(exc)) # SignatureExpired: Signature age ... > 3600 seconds
from blake2signer import Blake2Signer
from blake2signer import Blake2TimestampSigner
from blake2signer.errors import ExpiredSignatureError
# Signer
signer = Blake2Signer('super-secret-key') # 16 bytes min size enforced
print(signer.sign('my string'))
print(signer.unsign(b'tUt_Nu7h86uvsUyQDRIm6d3T-cKlSUlt--JR_w.my string')) # b'my string'
# Timestamp signer
signer = Blake2TimestampSigner('super-secret-key')
print(signer.sign('foo'))
try:
print(signer.unsign(b'tUY3jX-uu4Luqaya0Yr0WgvHIaSboPiP-YwBAw.YIbw6w.foo', max_age=3600)) # b'foo'
except ExpiredSignatureError as exc:
print(repr(exc)) # ExpiredSignatureError('signature has expired, age ... > 3600.0 seconds')
Main differences
- itsdangerous uses sha-1 by default as hashing backend, this package uses blake2b.
- itsdangerous creates deterministic signatures by default, this package doesn't, but can.
- itsdangerous appends signature (and timestamp) to data, this package prepends it.
Serializing interface¶
For serializing and signing objects, itsdangerous provides four classes: Serializer, URLSafeSerializer, TimedSerializer and URLSafeTimedSerializer. This package provides similar capabilities in a single class: Blake2SerializerSigner.
The interfaces are similar and can be used as drop-in replacements.
from itsdangerous import SignatureExpired
from itsdangerous.serializer import Serializer
from itsdangerous.timed import TimedSerializer
from itsdangerous.url_safe import URLSafeSerializer
from itsdangerous.url_safe import URLSafeTimedSerializer
# Serializer signer
signer = Serializer('secret-key')
print(signer.dumps([1, 2, 3, 4]))
print(signer.loads('[1, 2, 3, 4].r7R9RhGgDPvvWl3iNzLuIIfELmo')) # [1, 2, 3, 4]
# Timestamp serializer signer
signer = TimedSerializer('secret-key')
print(signer.dumps([1, 2, 3, 4]))
try:
print(signer.loads('[1, 2, 3, 4].YIZQ-w.0wubnZ3HhtaKvWuo-Kpks7F9Pfs', max_age=3600)) # [1, 2, 3, 4]
except SignatureExpired as exc:
print(repr(exc)) # SignatureExpired: Signature age ... > 3600 seconds
# Encoder serializer signer
signer = URLSafeSerializer('secret-key')
print(signer.dumps([1, 2, 3, 4]))
print(signer.loads('WzEsMiwzLDRd.wSPHqC0gR7VUqivlSukJ0IeTDgo')) # [1, 2, 3, 4]
# Timestamp encoder serializer signer
signer = URLSafeTimedSerializer('secret-key')
print(signer.dumps([1, 2, 3, 4]))
try:
print(signer.loads('WzEsMiwzLDRd.YIZRkQ.1Q_nKsgoxD_JtddAyWxBBnmR87M', max_age=3600)) # [1, 2, 3, 4]
except SignatureExpired as exc:
print(repr(exc)) # SignatureExpired: Signature age ... > 3600 seconds
from blake2signer import Blake2SerializerSigner
from blake2signer.errors import ExpiredSignatureError
# Encoder compressor serializer signer
signer = Blake2SerializerSigner('super-secret-key') # 16 bytes min size enforced
print(signer.dumps([1, 2, 3, 4]))
print(signer.loads('0i5Gvawg0724DBwXAG8rsUegCgCL-VLhjfOQ2g.WzEsMiwzLDRd')) # [1, 2, 3, 4]
# Timestamp encoder compressor serializer signer
signer = Blake2SerializerSigner('super-secret-key', max_age=3600) # With timestamp
print(signer.dumps([1, 2, 3, 4]))
try:
print(signer.loads('_zhFEz1fpIaTiUd6lk8M1u_5Q0ChEx671Mux1Q.YIcL8w.WzEsMiwzLDRd')) # [1, 2, 3, 4]
except ExpiredSignatureError as exc:
print(repr(exc)) # ExpiredSignatureError('signature has expired, age ... > 3600.0 seconds')
Main differences
- itsdangerous provides four different serializers to handle different cases, this package provides just one.
- itsdangerous can serialize without encoding data, this package can't (not directly, see the example on how to serialize without encoding).
- itsdangerous can't easily change the encoder, compressor and/or serializer, this package can.
- itsdangerous provides an interface to load data unsafely, this package doesn't (and probably never will).
- itsdangerous appends signature (and timestamp) to data, this package prepends it.
Django's signer¶
Signing interface¶
The django.core.signing.Signer appends the signature to the given string, whereas blake2signer.Blake2Signer prepends it (with salt). Other than that, this package can be a drop-in replacement and vice versa.
from django.conf import settings
from django.core.signing import SignatureExpired
from django.core.signing import Signer
from django.core.signing import TimestampSigner
settings.configure() # Initialize Django
# Signer
signer = Signer('secret-key')
print(signer.sign('My string'))
print(signer.unsign('My string:ZMytWkz1GTS_Nk71RrVV19aB0pjYncBU3hgXOh78xk8')) # My string
# Timestamp signer
signer = TimestampSigner('secret-key')
print(signer.sign('My string'))
try:
print(signer.unsign('My string:1lb46Q:WXYzPtY3ICSPVSU4qqGXfJ2_UiuiOfQV4S47-q6eT70', max_age=3600)) # My string
except SignatureExpired as exc:
print(repr(exc)) # SignatureExpired: Signature age ... > 3600 seconds
from blake2signer import Blake2Signer
from blake2signer import Blake2TimestampSigner
from blake2signer.errors import ExpiredSignatureError
# Signer
signer = Blake2Signer('super-secret-key') # 16 bytes min size enforced
print(signer.sign('My string'))
print(signer.unsign(b'RjlBYraVdIvRuH8uYpVO3kBp4qfkt93r2EaqMQ.My string')) # b'my string'
# Timestamp signer
signer = Blake2TimestampSigner('super-secret-key')
print(signer.sign('My string'))
try:
print(signer.unsign(b'O8W1aRlq6_RqkDsB3FYKNXu0Tzu9ziNKWr3xDA.YI8YXQ.My string', max_age=3600)) # b'foo'
except ExpiredSignatureError as exc:
print(repr(exc)) # ExpiredSignatureError('signature has expired, age ... > 3600.0 seconds')
Main differences
- django uses sha-256 by default as hashing backend, this package uses blake2b.
- django creates deterministic signatures by default, this package doesn't, but can.
- django appends signature (and timestamp) to data, this package prepends it.
- django uses the character
:as separator by default, this package uses..
Serializing interface¶
For serializing and signing objects, Django provides two functions: dumps and loads. This package provides similar capabilities in a single class: Blake2SerializerSigner.
The interfaces are similar and can be used as drop-in replacements.
from django.conf import settings
from django.core import signing
from django.core.signing import SignatureExpired
settings.configure() # Initialize Django
# Encoder compressor serializer signer
print(signing.dumps({'foo': 'bar'}, key='secret-key'))
print(signing.loads('eyJmb28iOiJiYXIifQ:1lb4Iy:GvulJZFUKKn60lWG8WoAYfs4SY-Ctdm-PVkApi44nlE', key='secret-key')) # {'foo': 'bar'}
# Timestamp encoder compressor serializer signer
try:
print(signing.loads('eyJmb28iOiJiYXIifQ:1lb4Iy:GvulJZFUKKn60lWG8WoAYfs4SY-Ctdm-PVkApi44nlE', key='secret-key', max_age=3600)) # {'foo': 'bar'}
except SignatureExpired as exc:
print(repr(exc)) # SignatureExpired: Signature age ... > 3600 seconds
from blake2signer import Blake2SerializerSigner
from blake2signer.errors import ExpiredSignatureError
# Encoder compressor serializer signer
signer = Blake2SerializerSigner('super-secret-key') # 16 bytes min size enforced
print(signer.dumps({'foo': 'bar'}))
print(signer.loads('Is7yyhMWydBGzqvIpynb_sEqudc6AcAYnItCow.eyJmb28iOiJiYXIifQ')) # {'foo': 'bar'}
# Timestamp encoder compressor serializer signer
signer = Blake2SerializerSigner('super-secret-key', max_age=3600) # With timestamp
print(signer.dumps({'foo': 'bar'}))
try:
print(signer.loads('7nKxO-QcE60ciWiwu5OHrQz3ftKgIhh92B3pgQ.YJYbrA.eyJmb28iOiJiYXIifQ')) # {'foo': 'bar'}
except ExpiredSignatureError as exc:
print(repr(exc)) # ExpiredSignatureError('signature has expired, age ... > 3600.0 seconds')
Main differences
- Django provides two functions to handle different cases, this package provides just one class.
- Django can't easily change the encoder, compressor and/or serializer, this package can.
- Django doesn't compress by default, this package does (and smartly: it won't compress if it turns out detrimental).
- django appends signature (and timestamp) to data, this package prepends it.
- django uses the character
:as separator by default, this package uses..
PyJWT¶
Serializing interface¶
PyJWT implements RFC 7519 for JSON Web Token which is quite complex, not needed in most cases and can easily turn into a footgun; this package is uncomplicated and can be used instead in most situations. On the other hand, for a similar utility but without the many design deficits that plague JWT, see PASETO.
import jwt
print(jwt.encode({'some': 'payload'}, 'secret', algorithm='HS256'))
print(jwt.decode(
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzb21lIjoicGF5bG9hZCJ9.Joh1R2dYzkRvDkqv3sygm5YyK8Gi4ShZqbhK2gxcs2U',
'secret',
algorithms=['HS256'],
)) # {'some': 'payload'}
from blake2signer import Blake2SerializerSigner
signer = Blake2SerializerSigner('super-secret-key') # 16 bytes min size enforced
print(signer.dumps({'some': 'payload'})
print(signer.loads('xJigCzDl2Naq3pt8dHNEZDY7ZIYLZ24U-No83g.eyJzb21lIjoicGF5bG9hZCJ9')) # {'some': 'payload'}
Main differences
- PyJWT can handle different algorithms dynamically - using a data header -, this package can't and never will.
- PyJWT can't change the encoder or serializer, this package can.
- PyJWT can't compress data, this package does (and smartly: it won't compress if it turns out detrimental).
Performance comparison¶
The following code is for the performance comparison of this lib against It's Dangerous, Django and PyJWT. Generally speaking, it is faster than the other libs, but you should choose one or the other based on usability and fitting-your-needs rather than performance.
Regarding itsdangerous (v2.2), I found this lib to be faster when compared to it using BLAKE2.
Regarding django (v5.2), I found this lib to be quite faster when compared to it using BLAKE2. Note that its Signer doesn't handle arbitrary bytes well (it breaks raising BadSignature error).
Regarding pyjwt (2.3.0), I found this lib to be faster when compared to it using BLAKE2.
Comparing Blake2Signer
"""Blake2Signer Timing Comparison.
This script helps compare the performance of this lib vs It's Dangerous, Django,
and PyJWT.
Requirements:
python3 -m pip install \
'blake2signer[blake3]' \
'itsdangerous~=2.2' \
'pyjwt~=2.10' \
'django~=5.2'
Usage:
python3 comparison.py
NUMBER=10 REPEAT=2 pypy3 comparison.py
This will take a while to execute, go grab a mate, coffee, tea, or something else :).
"""
import json
import os
import statistics
import timeit
import typing as t
from collections.abc import Callable
from copy import deepcopy
from datetime import datetime
from datetime import timezone
from enum import Enum
from functools import partial
from hashlib import blake2b
from hashlib import blake2s
from hashlib import sha1
from hashlib import sha256
from hashlib import sha384
from hashlib import sha512
from hmac import compare_digest
from math import isclose
from secrets import token_bytes
from unittest.mock import patch
import django.core.signing as django
import itsdangerous
import jwt
from django.conf import settings as django_settings
import blake2signer
NUMBER: t.Optional[int] = int(os.getenv('NUMBER', '0')) if os.getenv('NUMBER') else None
"""Set this value if you prefer to have a fixed number of rounds for the benchmark."""
REPEAT: int = int(os.getenv('REPEAT', '10'))
"""How many times should each iterations cycle repeat."""
COLUMNS = (40, 13, 7, 29)
"""Sizes of each table columns."""
class Benchmark(t.TypedDict):
"""Benchmark result."""
all_runs: list[float]
best: float
average: float
stdev: float
loops: int
repeat: int
class TermFormat(str, Enum):
"""Terminal formatting."""
PURPLE = '\033[95m'
CYAN = '\033[96m'
DARKCYAN = '\033[36m'
BLUE = '\033[94m'
GREEN = '\033[92m'
YELLOW = '\033[93m'
RED = '\033[91m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
END = '\033[0m'
def __str__(self) -> str:
"""String representation."""
return self.value
P = t.ParamSpec('P')
def benchmark(
func: Callable[P, t.Any],
*args: P.args,
**kwargs: P.kwargs,
) -> Benchmark:
"""Run a benchmark on the given function, similar to IPython's %timeit."""
wrapped = partial(func, *args, **kwargs)
timer = timeit.Timer(wrapped)
repeat = REPEAT
if NUMBER is None:
number, _ = timer.autorange()
number *= 2
else:
number = NUMBER
results = timer.repeat(repeat=repeat, number=number)
per_loop = [timing / number for timing in results]
best = min(per_loop)
avg = statistics.mean(per_loop)
st_dev = statistics.stdev(per_loop) if len(per_loop) > 1 else 0.0
print(
format_time(avg),
'±',
format_time(st_dev),
'per loop (mean ± std. dev. of',
repeat,
'runs',
number,
'loops each)',
)
return Benchmark(
all_runs=results,
best=best,
average=avg,
stdev=st_dev,
loops=number,
repeat=repeat,
)
def format_time(
dt: float,
*,
unit: t.Optional[str] = None,
precision: int = 3,
) -> str:
"""Format time (copied from timeit lib)."""
units = { # This map needs to be sorted from larger to smaller
's': 1.0,
'ms': 1e-3,
'us': 1e-6,
'ns': 1e-9,
}
if unit:
scale = units[unit]
else:
for unit, scale in units.items(): # noqa: B007 # pylint: disable=R1704
if dt >= scale:
break
else:
unit = 'ns'
scale = units[unit]
return f'{dt / scale:.{precision}g} {unit}'
def print_row(
*,
name: str,
value: float,
ok: bool,
baseline: float,
columns_sizes: tuple[int, int, int, int],
) -> None:
"""Print a comparison table row."""
x_times = baseline / value
if isclose(value, baseline):
detail = ''
elif isclose(x_times, 1.0, rel_tol=0.1):
detail = '(close to baseline)'
elif x_times < 1:
detail = '(slower than baseline)'
else:
detail = '(faster than baseline)'
print(
name.ljust(columns_sizes[0]),
'|',
format_time(value).rjust(columns_sizes[1]),
'|',
('√' if ok else '⚠').center(columns_sizes[2]),
'|',
'baseline' if isclose(value, baseline) else f'{x_times:4.2f}x'.rjust(4),
detail,
)
def is_timing_ok(timing: Benchmark, /) -> bool:
"""Return True if the timing measurement appears to be correct."""
return (timing['best'] / timing['stdev']) > 60
def print_table(
*,
title: str,
timings: dict[str, Benchmark],
baseline: str,
columns_sizes: tuple[int, int, int, int],
) -> None:
"""Print comparison table."""
print()
print(title.ljust(columns_sizes[0]), '| Best Abs Time | Measure | Comparison')
print(
'-' * columns_sizes[0],
'|',
'-' * columns_sizes[1],
'|',
'-' * columns_sizes[2],
'|',
'-' * columns_sizes[3],
)
baseline_value = timings[baseline]['best']
for name, timing in timings.items():
ok = is_timing_ok(timing)
print_row(
name=name,
value=timing['best'],
ok=ok,
baseline=baseline_value,
columns_sizes=columns_sizes,
)
print()
def print_line() -> None:
"""Print a line."""
print()
print(TermFormat.YELLOW, '-' * (sum(COLUMNS) + len(COLUMNS) * 2 + 1), TermFormat.END, sep='')
print()
# Hack to get PyJWT with blake2b
jws_b2 = jwt.PyJWS()
# noinspection PyProtectedMember,PyUnresolvedReferences
jws_b2._algorithms.update({ # pylint: disable=W0212
'HSB2B': jwt.algorithms.HMACAlgorithm(blake2b),
'HSB2S': jwt.algorithms.HMACAlgorithm(blake2s),
})
def blake2b_sign(value: bytes, key: bytes) -> bytes:
"""A basic "signing" function using BLAKE2b to be used as sentinel.
Warning: this is just for testing purposes in this script, as it makes too many assumptions,
and uses no encoding!
Returns:
A value signed with BLAKE2b.
"""
salt = token_bytes(blake2b.SALT_SIZE) # pylint: disable=E1101
sig = blake2b(value, key=key, salt=salt, digest_size=16).digest()
return salt + sig + b'.' + value
def blake2b_unsign(signed_data: bytes, key: bytes) -> bytes:
"""A basic "unsigning" function using BLAKE2b to be used as sentinel.
Warning: this is just for testing purposes in this script, as it makes too many assumptions,
and uses no decoding!
Returns:
The original value that was signed with BLAKE2b.
Raises:
ValueError: the signature is incorrect.
"""
salt = signed_data[:blake2b.SALT_SIZE] # pylint: disable=E1101
sig = signed_data[blake2b.SALT_SIZE:blake2b.SALT_SIZE + 16] # pylint: disable=E1101
value = signed_data[blake2b.SALT_SIZE + 16 + 1:] # pylint: disable=E1101
good_sig = blake2b(value, key=key, salt=salt, digest_size=16).digest()
if compare_digest(good_sig, sig):
return value
raise ValueError('signature mismatch')
def blake2s_sign(value: bytes, key: bytes) -> bytes:
"""A basic "signing" function using BLAKE2s to be used as sentinel.
Warning: this is just for testing purposes in this script, as it makes too many assumptions,
and uses no encoding!
Returns:
A value signed with BLAKE2s.
"""
salt = token_bytes(blake2s.SALT_SIZE) # pylint: disable=E1101
sig = blake2s(value, key=key, salt=salt, digest_size=16).digest()
return salt + sig + b'.' + value
def blake2s_unsign(signed_data: bytes, key: bytes) -> bytes:
"""A basic "unsigning" function using BLAKE2s to be used as sentinel.
Warning: this is just for testing purposes in this script, as it makes too many assumptions,
and uses no decoding!
Returns:
The original value that was signed with BLAKE2s.
Raises:
ValueError: the signature is incorrect.
"""
salt = signed_data[:blake2s.SALT_SIZE] # pylint: disable=E1101
sig = signed_data[blake2s.SALT_SIZE:blake2s.SALT_SIZE + 16] # pylint: disable=E1101
value = signed_data[blake2s.SALT_SIZE + 16 + 1:] # pylint: disable=E1101
good_sig = blake2s(value, key=key, salt=salt, digest_size=16).digest()
if compare_digest(good_sig, sig):
return value
raise ValueError('signature mismatch')
def blake2b_dumps(value: t.Any, key: bytes) -> bytes:
"""A basic "serializing" function using BLAKE2b to be used as sentinel.
Warning: this is just for testing purposes in this script, as it makes too many assumptions,
and uses no encoding!
Returns:
A value signed with BLAKE2b.
"""
salt = token_bytes(blake2b.SALT_SIZE) # pylint: disable=E1101
serialized = json.dumps(value).encode()
sig = blake2b(serialized, key=key, salt=salt, digest_size=16).digest()
return salt + sig + b'.' + serialized
def blake2b_loads(signed_data: bytes, key: bytes) -> t.Any:
"""A basic "unserializing" function using BLAKE2b to be used as sentinel.
Warning: this is just for testing purposes in this script, as it makes too many assumptions,
and uses no decoding!
Returns:
The original value that was signed with BLAKE2b.
Raises:
ValueError: the signature is incorrect.
"""
salt = signed_data[:blake2b.SALT_SIZE] # pylint: disable=E1101
sig = signed_data[blake2b.SALT_SIZE:blake2b.SALT_SIZE + 16] # pylint: disable=E1101
value = signed_data[blake2b.SALT_SIZE + 16 + 1:] # pylint: disable=E1101
good_sig = blake2b(value, key=key, salt=salt, digest_size=16).digest()
if compare_digest(good_sig, sig):
return json.loads(value)
raise ValueError('signature mismatch')
def blake2s_dumps(value: t.Any, key: bytes) -> bytes:
"""A basic "serializing" function using BLAKE2s to be used as sentinel.
Warning: this is just for testing purposes in this script, as it makes too many assumptions,
and uses no encoding!
Returns:
A value signed with BLAKE2b.
"""
salt = token_bytes(blake2s.SALT_SIZE) # pylint: disable=E1101
serialized = json.dumps(value).encode()
sig = blake2s(serialized, key=key, salt=salt, digest_size=16).digest()
return salt + sig + b'.' + serialized
def blake2s_loads(signed_data: bytes, key: bytes) -> t.Any:
"""A basic "unserializing" function using BLAKE2s to be used as sentinel.
Warning: this is just for testing purposes in this script, as it makes too many assumptions,
and uses no decoding!
Returns:
The original value that was signed with BLAKE2b.
Raises:
ValueError: the signature is incorrect.
"""
salt = signed_data[:blake2s.SALT_SIZE] # pylint: disable=E1101
sig = signed_data[blake2s.SALT_SIZE:blake2s.SALT_SIZE + 16] # pylint: disable=E1101
value = signed_data[blake2s.SALT_SIZE + 16 + 1:] # pylint: disable=E1101
good_sig = blake2s(value, key=key, salt=salt, digest_size=16).digest()
if compare_digest(good_sig, sig):
return json.loads(value)
raise ValueError('signature mismatch')
def main() -> None: # noqa: C901,R701 # pylint: disable=R0914,R0915 # type: ignore[misc]
"""Run comparisons."""
secret = b'1' * 16
b2s = blake2signer.Blake2Signer(secret)
b2s_d = blake2signer.Blake2Signer(secret, deterministic=True)
b2s_2s = blake2signer.Blake2Signer(secret, hasher='blake2s')
b2s_2s_d = blake2signer.Blake2Signer(secret, hasher='blake2s', deterministic=True)
b2ss = blake2signer.Blake2SerializerSigner(secret)
b2ss_d = blake2signer.Blake2SerializerSigner(secret, deterministic=True)
b2ss_2s = blake2signer.Blake2SerializerSigner(secret, hasher='blake2s')
b2ss_2s_d = blake2signer.Blake2SerializerSigner(secret, hasher='blake2s', deterministic=True)
itd_2b = itsdangerous.Signer(secret, digest_method=blake2b)
itd_2s = itsdangerous.Signer(secret, digest_method=blake2s)
itd_s1 = itsdangerous.Signer(secret, digest_method=sha1)
itd_s256 = itsdangerous.Signer(secret, digest_method=sha256)
itd_s384 = itsdangerous.Signer(secret, digest_method=sha384)
itd_s512 = itsdangerous.Signer(secret, digest_method=sha512)
itds_2b = itsdangerous.URLSafeSerializer(
secret,
signer_kwargs={
'digest_method': blake2b,
},
)
itds_2s = itsdangerous.URLSafeSerializer(
secret,
signer_kwargs={
'digest_method': blake2s,
},
)
django_settings.configure() # Initialize Django
djs_2b = django.Signer(key=secret, algorithm='blake2b')
djs_2s = django.Signer(key=secret, algorithm='blake2s')
djs_s1 = django.Signer(key=secret, algorithm='sha1')
djs_s256 = django.Signer(key=secret, algorithm='sha256')
djs_s384 = django.Signer(key=secret, algorithm='sha384')
djs_s512 = django.Signer(key=secret, algorithm='sha512')
try:
b2s_3 = blake2signer.Blake2Signer(secret, hasher='blake3')
except blake2signer.errors.MissingDependencyError:
has_blake3 = False
b2s_3 = b2s_3_d = b2ss_3 = b2ss_3_d = None # type: ignore[assignment]
print(
'Note: `blake3` (https://pypi.org/project/blake3) is not installed, '
+ 'BLAKE3 tests disabled', # noqa: W503
)
print()
else:
has_blake3 = True
b2s_3_d = blake2signer.Blake2Signer(secret, hasher='blake3', deterministic=True)
b2ss_3 = blake2signer.Blake2SerializerSigner(secret, hasher='blake3')
b2ss_3_d = blake2signer.Blake2SerializerSigner(secret, hasher='blake3', deterministic=True)
# Example payload
regular_payload: dict[str, t.Any] = {
'user': {
'id': 1337,
'name': 'HacKan CuBa',
'username': 'hackan_cuba',
'email': 'hackan@email.com',
},
'_meta': {
'iss': 'blake2signer',
'nbf': datetime.now(timezone.utc).isoformat(),
},
}
payloads: dict[str, dict[str, t.Any]] = {
'regular': regular_payload,
'large': {
'payload': [regular_payload] * 100, # This is just a representation
},
}
results: dict[str, dict[str, dict[str, Benchmark]]] = {} # noqa: TAE002
for payload, data in payloads.items():
sentinel_payload = deepcopy(data)
data_s = json.dumps(data)
data_b = data_s.encode()
print()
print(
TermFormat.GREEN,
'Payload size ~: ',
len(data_b),
' bytes (',
payload,
')',
TermFormat.END,
sep='',
)
signers: dict[str, Benchmark] = {}
serializers: dict[str, Benchmark] = {}
print(
TermFormat.CYAN,
'Computing, please wait (this will take a while)...',
TermFormat.END,
sep='',
)
print()
print('Blake2Signer(blake2b, determ)')
signers['Blake2Signer(blake2b, determ)'] = benchmark(
lambda d: b2s_d.unsign(b2s_d.sign(d)),
data_b,
)
print()
print('Blake2Signer(blake2b)')
signers['Blake2Signer(blake2b)'] = benchmark(
lambda d: b2s.unsign(b2s.sign(d)),
data_b,
)
print()
print('Blake2Signer(blake2s, determ)')
signers['Blake2Signer(blake2s, determ)'] = benchmark(
lambda d: b2s_2s_d.unsign(b2s_2s_d.sign(d)),
data_b,
)
print()
print('Blake2Signer(blake2s)')
signers['Blake2Signer(blake2s)'] = benchmark(
lambda d: b2s_2s.unsign(b2s_2s.sign(d)),
data_b,
)
print()
if has_blake3:
print('Blake2Signer(blake3, determ)')
signers['Blake2Signer(blake3, determ)'] = benchmark(
lambda d: b2s_3_d.unsign(b2s_3_d.sign(d)), # type: ignore[union-attr]
data_b,
)
print()
print('Blake2Signer(blake3)')
signers['Blake2Signer(blake3)'] = benchmark(
lambda d: b2s_3.unsign(b2s_3.sign(d)),
data_b,
)
print()
print('Sentinel(blake2b)')
signers['Sentinel(blake2b)'] = benchmark(
lambda d: blake2b_unsign(blake2b_sign(d, secret), secret),
data_b,
)
print()
print('Sentinel(blake2s)')
signers['Sentinel(blake2s)'] = benchmark(
lambda d: blake2s_unsign(blake2s_sign(d, secret), secret),
data_b,
)
print()
print('ItsDangerousSigner(blake2b)')
signers['ItsDangerousSigner(blake2b)'] = benchmark(
lambda d: itd_2b.unsign(itd_2b.sign(d)),
data_b,
)
print()
print('ItsDangerousSigner(blake2s)')
signers['ItsDangerousSigner(blake2s)'] = benchmark(
lambda d: itd_2s.unsign(itd_2s.sign(d)),
data_b,
)
print()
print('ItsDangerousSigner(sha1)')
signers['ItsDangerousSigner(sha1)'] = benchmark(
lambda d: itd_s1.unsign(itd_s1.sign(d)),
data_b,
)
print()
print('ItsDangerousSigner(sha256)')
signers['ItsDangerousSigner(sha256)'] = benchmark(
lambda d: itd_s256.unsign(itd_s256.sign(d)),
data_b,
)
print()
print('ItsDangerousSigner(sha384)')
signers['ItsDangerousSigner(sha384)'] = benchmark(
lambda d: itd_s384.unsign(itd_s384.sign(d)),
data_b,
)
print()
print('ItsDangerousSigner(sha512)')
signers['ItsDangerousSigner(sha512)'] = benchmark(
lambda d: itd_s512.unsign(itd_s512.sign(d)),
data_b,
)
print()
print('DjangoSigner(blake2b)')
signers['DjangoSigner(blake2b)'] = benchmark(
lambda d: djs_2b.unsign(djs_2b.sign(d)),
data_s,
)
print()
print('DjangoSigner(blake2s)')
signers['DjangoSigner(blake2s)'] = benchmark(
lambda d: djs_2s.unsign(djs_2s.sign(d)),
data_s,
)
print()
print('DjangoSigner(sha1)')
signers['DjangoSigner(sha1)'] = benchmark(
lambda d: djs_s1.unsign(djs_s1.sign(d)),
data_s,
)
print()
print('DjangoSigner(sha256)')
signers['DjangoSigner(sha256)'] = benchmark(
lambda d: djs_s256.unsign(djs_s256.sign(d)),
data_s,
)
print()
print('DjangoSigner(sha384)')
signers['DjangoSigner(sha384)'] = benchmark(
lambda d: djs_s384.unsign(djs_s384.sign(d)),
data_s,
)
print()
print('DjangoSigner(sha512)')
signers['DjangoSigner(sha512)'] = benchmark(
lambda d: djs_s512.unsign(djs_s512.sign(d)),
data_s,
)
print()
print('Blake2SerializerSigner(blake2b, determ)')
serializers['Blake2SerializerSigner(blake2b, determ)'] = benchmark(
lambda d: b2ss_d.loads(b2ss_d.dumps(d)),
data,
)
print()
assert data == sentinel_payload
print('Blake2SerializerSigner(blake2b)')
serializers['Blake2SerializerSigner(blake2b)'] = benchmark(
lambda d: b2ss.loads(b2ss.dumps(d)),
data,
)
print()
assert data == sentinel_payload
print('Blake2SerializerSigner(blake2s, determ)')
serializers['Blake2SerializerSigner(blake2s, determ)'] = benchmark(
lambda d: b2ss_2s_d.loads(b2ss_2s_d.dumps(d)),
data,
)
print()
assert data == sentinel_payload
print('Blake2SerializerSigner(blake2s)')
serializers['Blake2SerializerSigner(blake2s)'] = benchmark(
lambda d: b2ss_2s.loads(b2ss_2s.dumps(d)),
data,
)
print()
assert data == sentinel_payload
if has_blake3:
print('Blake2SerializerSigner(blake3, determ)')
serializers['Blake2SerializerSigner(blake3, determ)'] = benchmark(
lambda d: b2ss_3_d.loads(b2ss_3_d.dumps(d)), # type: ignore[union-attr]
data,
)
print()
assert data == sentinel_payload
print('Blake2SerializerSigner(blake3)')
serializers['Blake2SerializerSigner(blake3)'] = benchmark(
lambda d: b2ss_3.loads(b2ss_3.dumps(d)), # type: ignore[union-attr]
data,
)
print()
assert data == sentinel_payload
print('Sentinel(blake2b)')
serializers['Sentinel(blake2b)'] = benchmark(
lambda d: blake2b_loads(blake2b_dumps(d, secret), secret),
data,
)
print()
assert data == sentinel_payload
print('Sentinel(blake2s)')
serializers['Sentinel(blake2s)'] = benchmark(
lambda d: blake2s_loads(blake2s_dumps(d, secret), secret),
data,
)
print()
assert data == sentinel_payload
print('ItsDangerousURLSafeSerializer(blake2b)')
serializers['ItsDangerousURLSafeSerializer(blake2b)'] = benchmark(
lambda d: itds_2b.loads(itds_2b.dumps(d)),
data,
)
print()
assert data == sentinel_payload
print('ItsDangerousURLSafeSerializer(blake2s)')
serializers['ItsDangerousURLSafeSerializer(blake2s)'] = benchmark(
lambda d: itds_2s.loads(itds_2s.dumps(d)),
data,
)
print()
assert data == sentinel_payload
print('DjangoSerializer(sha256)')
serializers['DjangoSerializer(sha256)'] = benchmark(
lambda d: django.loads(django.dumps(d, key=secret, compress=True), key=secret),
data,
)
print()
assert data == sentinel_payload
print('PyJWTSerializer(sha256)')
serializers['PyJWTSerializer(sha256)'] = benchmark(
lambda d: jwt.decode(
jwt.encode(d, secret, algorithm='HS256'),
secret,
algorithms=['HS256'],
),
data,
)
print()
assert data == sentinel_payload
print('PyJWTSerializer(blake2b)')
with (
patch.object(
jwt.api_jws,
jwt.api_jws.decode_complete.__name__,
new=jws_b2.decode_complete,
),
patch.object(
jwt.api_jws,
jwt.api_jws.encode.__name__,
new=jws_b2.encode,
),
):
serializers['PyJWTSerializer(blake2b)'] = benchmark(
lambda d: jwt.decode(jwt.encode(d, secret, 'HSB2B'), secret, algorithms=['HSB2B']),
data,
)
print()
assert data == sentinel_payload
print('PyJWTSerializer(blake2s)')
with (
patch.object(
jwt.api_jws,
jwt.api_jws.decode_complete.__name__,
new=jws_b2.decode_complete,
),
patch.object(
jwt.api_jws,
jwt.api_jws.encode.__name__,
new=jws_b2.encode,
),
):
serializers['PyJWTSerializer(blake2s)'] = benchmark(
lambda d: jwt.decode(jwt.encode(d, secret, 'HSB2S'), secret, algorithms=['HSB2S']),
data,
)
print()
assert data == sentinel_payload
print(
TermFormat.CYAN,
'Results for a ',
TermFormat.BOLD,
payload,
TermFormat.END,
TermFormat.CYAN,
' payload:',
TermFormat.END,
sep='',
)
print_table(
title='Signer',
timings=signers,
baseline='Blake2Signer(blake2b, determ)',
columns_sizes=COLUMNS,
)
print_table(
title='Serializer',
timings=serializers,
baseline='Blake2SerializerSigner(blake2b, determ)',
columns_sizes=COLUMNS,
)
results[payload] = {
'signers': {
signer: result.copy()
for signer, result in signers.items()
if 'blake2' in signer
},
'serializers': {
serializer: result.copy()
for serializer, result in serializers.items()
if 'blake2' in serializer
},
}
print_line()
for payload, result in results.items():
print()
print(
TermFormat.CYAN,
'Results for BLAKE2 only, for a ',
TermFormat.BOLD,
payload,
TermFormat.END,
TermFormat.CYAN,
' payload:',
TermFormat.END,
sep='',
)
print_table(
title='Signer',
timings=result['signers'],
baseline='Blake2Signer(blake2b, determ)',
columns_sizes=COLUMNS,
)
print_table(
title='Serializer',
timings=result['serializers'],
baseline='Blake2SerializerSigner(blake2b, determ)',
columns_sizes=COLUMNS,
)
if __name__ == '__main__':
main()
Payload size ~: 178 bytes (regular)
Computing, please wait (this will take a while)...
Blake2Signer(blake2b, determ)
6.61 us ± 77.9 ns per loop (mean ± std. dev. of 10 runs 100000 loops each)
Blake2Signer(blake2b)
8.96 us ± 83.7 ns per loop (mean ± std. dev. of 10 runs 100000 loops each)
Blake2Signer(blake2s, determ)
6.45 us ± 48.9 ns per loop (mean ± std. dev. of 10 runs 100000 loops each)
Blake2Signer(blake2s)
8.71 us ± 40.9 ns per loop (mean ± std. dev. of 10 runs 100000 loops each)
Blake2Signer(blake3, determ)
7.34 us ± 40 ns per loop (mean ± std. dev. of 10 runs 100000 loops each)
Blake2Signer(blake3)
9.91 us ± 77.8 ns per loop (mean ± std. dev. of 10 runs 100000 loops each)
Sentinel(blake2b)
3.9 us ± 14 ns per loop (mean ± std. dev. of 10 runs 200000 loops each)
Sentinel(blake2s)
3.96 us ± 128 ns per loop (mean ± std. dev. of 10 runs 200000 loops each)
ItsDangerousSigner(blake2b)
11.4 us ± 72 ns per loop (mean ± std. dev. of 10 runs 40000 loops each)
ItsDangerousSigner(blake2s)
10.4 us ± 48.9 ns per loop (mean ± std. dev. of 10 runs 40000 loops each)
ItsDangerousSigner(sha1)
12.1 us ± 513 ns per loop (mean ± std. dev. of 10 runs 40000 loops each)
ItsDangerousSigner(sha256)
13.2 us ± 256 ns per loop (mean ± std. dev. of 10 runs 40000 loops each)
ItsDangerousSigner(sha384)
14.3 us ± 605 ns per loop (mean ± std. dev. of 10 runs 40000 loops each)
ItsDangerousSigner(sha512)
14.3 us ± 114 ns per loop (mean ± std. dev. of 10 runs 40000 loops each)
DjangoSigner(blake2b)
12.3 us ± 369 ns per loop (mean ± std. dev. of 10 runs 40000 loops each)
DjangoSigner(blake2s)
11 us ± 261 ns per loop (mean ± std. dev. of 10 runs 40000 loops each)
DjangoSigner(sha1)
12.4 us ± 607 ns per loop (mean ± std. dev. of 10 runs 40000 loops each)
DjangoSigner(sha256)
13.4 us ± 94.4 ns per loop (mean ± std. dev. of 10 runs 40000 loops each)
DjangoSigner(sha384)
14.5 us ± 135 ns per loop (mean ± std. dev. of 10 runs 40000 loops each)
DjangoSigner(sha512)
14.5 us ± 79 ns per loop (mean ± std. dev. of 10 runs 40000 loops each)
Blake2SerializerSigner(blake2b, determ)
48.4 us ± 192 ns per loop (mean ± std. dev. of 10 runs 10000 loops each)
Blake2SerializerSigner(blake2b)
51.6 us ± 348 ns per loop (mean ± std. dev. of 10 runs 10000 loops each)
Blake2SerializerSigner(blake2s, determ)
48.1 us ± 214 ns per loop (mean ± std. dev. of 10 runs 10000 loops each)
Blake2SerializerSigner(blake2s)
51.8 us ± 318 ns per loop (mean ± std. dev. of 10 runs 10000 loops each)
Blake2SerializerSigner(blake3, determ)
51.4 us ± 671 ns per loop (mean ± std. dev. of 10 runs 10000 loops each)
Blake2SerializerSigner(blake3)
54.4 us ± 274 ns per loop (mean ± std. dev. of 10 runs 10000 loops each)
Sentinel(blake2b)
15.6 us ± 484 ns per loop (mean ± std. dev. of 10 runs 40000 loops each)
Sentinel(blake2s)
15.4 us ± 236 ns per loop (mean ± std. dev. of 10 runs 40000 loops each)
ItsDangerousURLSafeSerializer(blake2b)
60.6 us ± 252 ns per loop (mean ± std. dev. of 10 runs 10000 loops each)
ItsDangerousURLSafeSerializer(blake2s)
59.5 us ± 187 ns per loop (mean ± std. dev. of 10 runs 10000 loops each)
DjangoSerializer(sha256)
74.9 us ± 294 ns per loop (mean ± std. dev. of 10 runs 10000 loops each)
PyJWTSerializer(sha256)
53.2 us ± 400 ns per loop (mean ± std. dev. of 10 runs 10000 loops each)
PyJWTSerializer(blake2b)
50.9 us ± 143 ns per loop (mean ± std. dev. of 10 runs 10000 loops each)
PyJWTSerializer(blake2s)
50.5 us ± 723 ns per loop (mean ± std. dev. of 10 runs 10000 loops each)
Results for a regular payload:
Signer | Best Abs Time | Measure | Comparison
---------------------------------------- | ------------- | ------- | -----------------------------
Blake2Signer(blake2b, determ) | 6.51 us | √ | baseline
Blake2Signer(blake2b) | 8.87 us | √ | 0.73x (slower than baseline)
Blake2Signer(blake2s, determ) | 6.39 us | √ | 1.0x (close to baseline)
Blake2Signer(blake2s) | 8.66 us | √ | 0.75x (slower than baseline)
Blake2Signer(blake3, determ) | 7.25 us | √ | 0.9x (slower than baseline)
Blake2Signer(blake3) | 9.8 us | √ | 0.66x (slower than baseline)
Sentinel(blake2b) | 3.89 us | √ | 1.7x (faster than baseline)
Sentinel(blake2s) | 3.87 us | ⚠ | 1.7x (faster than baseline)
ItsDangerousSigner(blake2b) | 11.3 us | √ | 0.58x (slower than baseline)
ItsDangerousSigner(blake2s) | 10.4 us | √ | 0.63x (slower than baseline)
ItsDangerousSigner(sha1) | 11.7 us | ⚠ | 0.56x (slower than baseline)
ItsDangerousSigner(sha256) | 12.9 us | ⚠ | 0.5x (slower than baseline)
ItsDangerousSigner(sha384) | 14 us | ⚠ | 0.46x (slower than baseline)
ItsDangerousSigner(sha512) | 14.2 us | √ | 0.46x (slower than baseline)
DjangoSigner(blake2b) | 11.8 us | ⚠ | 0.55x (slower than baseline)
DjangoSigner(blake2s) | 10.7 us | ⚠ | 0.61x (slower than baseline)
DjangoSigner(sha1) | 12 us | ⚠ | 0.54x (slower than baseline)
DjangoSigner(sha256) | 13.3 us | √ | 0.49x (slower than baseline)
DjangoSigner(sha384) | 14.4 us | √ | 0.45x (slower than baseline)
DjangoSigner(sha512) | 14.3 us | √ | 0.46x (slower than baseline)
Serializer | Best Abs Time | Measure | Comparison
---------------------------------------- | ------------- | ------- | -----------------------------
Blake2SerializerSigner(blake2b, determ) | 48.2 us | √ | baseline
Blake2SerializerSigner(blake2b) | 51.2 us | √ | 0.94x (slower than baseline)
Blake2SerializerSigner(blake2s, determ) | 47.8 us | √ | 1.0x (close to baseline)
Blake2SerializerSigner(blake2s) | 51.4 us | √ | 0.94x (slower than baseline)
Blake2SerializerSigner(blake3, determ) | 51 us | √ | 0.94x (slower than baseline)
Blake2SerializerSigner(blake3) | 54.1 us | √ | 0.89x (slower than baseline)
Sentinel(blake2b) | 15.1 us | ⚠ | 3.2x (faster than baseline)
Sentinel(blake2s) | 14.9 us | √ | 3.2x (faster than baseline)
ItsDangerousURLSafeSerializer(blake2b) | 60.4 us | √ | 0.8x (slower than baseline)
ItsDangerousURLSafeSerializer(blake2s) | 59.2 us | √ | 0.81x (slower than baseline)
DjangoSerializer(sha256) | 74.5 us | √ | 0.65x (slower than baseline)
PyJWTSerializer(sha256) | 52.8 us | √ | 0.91x (slower than baseline)
PyJWTSerializer(blake2b) | 50.7 us | √ | 0.95x (slower than baseline)
PyJWTSerializer(blake2s) | 49.9 us | √ | 0.97x (slower than baseline)
--------------------------------------------------------------------------------------------------
Payload size ~: 18013 bytes (large)
Computing, please wait (this will take a while)...
Blake2Signer(blake2b, determ)
55.6 us ± 1.29 us per loop (mean ± std. dev. of 10 runs 10000 loops each)
Blake2Signer(blake2b)
58.7 us ± 1.3 us per loop (mean ± std. dev. of 10 runs 10000 loops each)
Blake2Signer(blake2s, determ)
80 us ± 337 ns per loop (mean ± std. dev. of 10 runs 10000 loops each)
Blake2Signer(blake2s)
82 us ± 329 ns per loop (mean ± std. dev. of 10 runs 10000 loops each)
Blake2Signer(blake3, determ)
22.8 us ± 119 ns per loop (mean ± std. dev. of 10 runs 20000 loops each)
Blake2Signer(blake3)
25.4 us ± 66.1 ns per loop (mean ± std. dev. of 10 runs 20000 loops each)
Sentinel(blake2b)
50.2 us ± 296 ns per loop (mean ± std. dev. of 10 runs 10000 loops each)
Sentinel(blake2s)
76.6 us ± 214 ns per loop (mean ± std. dev. of 10 runs 10000 loops each)
ItsDangerousSigner(blake2b)
60 us ± 404 ns per loop (mean ± std. dev. of 10 runs 10000 loops each)
ItsDangerousSigner(blake2s)
85.2 us ± 350 ns per loop (mean ± std. dev. of 10 runs 10000 loops each)
ItsDangerousSigner(sha1)
50.3 us ± 184 ns per loop (mean ± std. dev. of 10 runs 10000 loops each)
ItsDangerousSigner(sha256)
95.4 us ± 2.01 us per loop (mean ± std. dev. of 10 runs 10000 loops each)
ItsDangerousSigner(sha384)
69.9 us ± 1.03 us per loop (mean ± std. dev. of 10 runs 10000 loops each)
ItsDangerousSigner(sha512)
71.6 us ± 1.96 us per loop (mean ± std. dev. of 10 runs 10000 loops each)
DjangoSigner(blake2b)
60.7 us ± 172 ns per loop (mean ± std. dev. of 10 runs 10000 loops each)
DjangoSigner(blake2s)
85.8 us ± 338 ns per loop (mean ± std. dev. of 10 runs 10000 loops each)
DjangoSigner(sha1)
51.5 us ± 182 ns per loop (mean ± std. dev. of 10 runs 10000 loops each)
DjangoSigner(sha256)
95.4 us ± 506 ns per loop (mean ± std. dev. of 10 runs 10000 loops each)
DjangoSigner(sha384)
70.2 us ± 245 ns per loop (mean ± std. dev. of 10 runs 10000 loops each)
DjangoSigner(sha512)
71.4 us ± 895 ns per loop (mean ± std. dev. of 10 runs 10000 loops each)
Blake2SerializerSigner(blake2b, determ)
459 us ± 2.57 us per loop (mean ± std. dev. of 10 runs 1000 loops each)
Blake2SerializerSigner(blake2b)
465 us ± 3.33 us per loop (mean ± std. dev. of 10 runs 1000 loops each)
Blake2SerializerSigner(blake2s, determ)
454 us ± 4.07 us per loop (mean ± std. dev. of 10 runs 1000 loops each)
Blake2SerializerSigner(blake2s)
460 us ± 994 ns per loop (mean ± std. dev. of 10 runs 1000 loops each)
Blake2SerializerSigner(blake3, determ)
463 us ± 6.06 us per loop (mean ± std. dev. of 10 runs 1000 loops each)
Blake2SerializerSigner(blake3)
473 us ± 5.8 us per loop (mean ± std. dev. of 10 runs 1000 loops each)
Sentinel(blake2b)
410 us ± 6.87 us per loop (mean ± std. dev. of 10 runs 1000 loops each)
Sentinel(blake2s)
440 us ± 16.3 us per loop (mean ± std. dev. of 10 runs 1000 loops each)
ItsDangerousURLSafeSerializer(blake2b)
484 us ± 11.8 us per loop (mean ± std. dev. of 10 runs 1000 loops each)
ItsDangerousURLSafeSerializer(blake2s)
477 us ± 4.53 us per loop (mean ± std. dev. of 10 runs 1000 loops each)
DjangoSerializer(sha256)
489 us ± 2.31 us per loop (mean ± std. dev. of 10 runs 1000 loops each)
PyJWTSerializer(sha256)
620 us ± 2.33 us per loop (mean ± std. dev. of 10 runs 1000 loops each)
PyJWTSerializer(blake2b)
577 us ± 2.32 us per loop (mean ± std. dev. of 10 runs 1000 loops each)
PyJWTSerializer(blake2s)
625 us ± 15.9 us per loop (mean ± std. dev. of 10 runs 1000 loops each)
Results for a large payload:
Signer | Best Abs Time | Measure | Comparison
---------------------------------------- | ------------- | ------- | -----------------------------
Blake2Signer(blake2b, determ) | 53.5 us | ⚠ | baseline
Blake2Signer(blake2b) | 55.8 us | ⚠ | 0.96x (slower than baseline)
Blake2Signer(blake2s, determ) | 79.7 us | √ | 0.67x (slower than baseline)
Blake2Signer(blake2s) | 81.6 us | √ | 0.66x (slower than baseline)
Blake2Signer(blake3, determ) | 22.6 us | √ | 2.4x (faster than baseline)
Blake2Signer(blake3) | 25.3 us | √ | 2.1x (faster than baseline)
Sentinel(blake2b) | 50 us | √ | 1.1x (faster than baseline)
Sentinel(blake2s) | 76.4 us | √ | 0.7x (slower than baseline)
ItsDangerousSigner(blake2b) | 59.5 us | √ | 0.9x (slower than baseline)
ItsDangerousSigner(blake2s) | 84.9 us | √ | 0.63x (slower than baseline)
ItsDangerousSigner(sha1) | 50.2 us | √ | 1.1x (faster than baseline)
ItsDangerousSigner(sha256) | 94 us | ⚠ | 0.57x (slower than baseline)
ItsDangerousSigner(sha384) | 69.1 us | √ | 0.77x (slower than baseline)
ItsDangerousSigner(sha512) | 69.1 us | ⚠ | 0.77x (slower than baseline)
DjangoSigner(blake2b) | 60.5 us | √ | 0.88x (slower than baseline)
DjangoSigner(blake2s) | 85.4 us | √ | 0.63x (slower than baseline)
DjangoSigner(sha1) | 51.2 us | √ | 1.0x (close to baseline)
DjangoSigner(sha256) | 94.7 us | √ | 0.56x (slower than baseline)
DjangoSigner(sha384) | 70 us | √ | 0.76x (slower than baseline)
DjangoSigner(sha512) | 70.2 us | √ | 0.76x (slower than baseline)
Serializer | Best Abs Time | Measure | Comparison
---------------------------------------- | ------------- | ------- | -----------------------------
Blake2SerializerSigner(blake2b, determ) | 456 us | √ | baseline
Blake2SerializerSigner(blake2b) | 460 us | √ | 0.99x (close to baseline)
Blake2SerializerSigner(blake2s, determ) | 452 us | √ | 1.0x (close to baseline)
Blake2SerializerSigner(blake2s) | 459 us | √ | 0.99x (close to baseline)
Blake2SerializerSigner(blake3, determ) | 456 us | √ | 1.0x (close to baseline)
Blake2SerializerSigner(blake3) | 466 us | √ | 0.98x (slower than baseline)
Sentinel(blake2b) | 403 us | ⚠ | 1.1x (faster than baseline)
Sentinel(blake2s) | 429 us | ⚠ | 1.1x (close to baseline)
ItsDangerousURLSafeSerializer(blake2b) | 474 us | ⚠ | 0.96x (slower than baseline)
ItsDangerousURLSafeSerializer(blake2s) | 473 us | √ | 0.97x (slower than baseline)
DjangoSerializer(sha256) | 486 us | √ | 0.94x (slower than baseline)
PyJWTSerializer(sha256) | 618 us | √ | 0.74x (slower than baseline)
PyJWTSerializer(blake2b) | 575 us | √ | 0.79x (slower than baseline)
PyJWTSerializer(blake2s) | 609 us | ⚠ | 0.75x (slower than baseline)
--------------------------------------------------------------------------------------------------
Results for BLAKE2 only, for a regular payload:
Signer | Best Abs Time | Measure | Comparison
---------------------------------------- | ------------- | ------- | -----------------------------
Blake2Signer(blake2b, determ) | 6.51 us | √ | baseline
Blake2Signer(blake2b) | 8.87 us | √ | 0.73x (slower than baseline)
Blake2Signer(blake2s, determ) | 6.39 us | √ | 1.0x (close to baseline)
Blake2Signer(blake2s) | 8.66 us | √ | 0.75x (slower than baseline)
Sentinel(blake2b) | 3.89 us | √ | 1.7x (faster than baseline)
Sentinel(blake2s) | 3.87 us | ⚠ | 1.7x (faster than baseline)
ItsDangerousSigner(blake2b) | 11.3 us | √ | 0.58x (slower than baseline)
ItsDangerousSigner(blake2s) | 10.4 us | √ | 0.63x (slower than baseline)
DjangoSigner(blake2b) | 11.8 us | ⚠ | 0.55x (slower than baseline)
DjangoSigner(blake2s) | 10.7 us | ⚠ | 0.61x (slower than baseline)
Serializer | Best Abs Time | Measure | Comparison
---------------------------------------- | ------------- | ------- | -----------------------------
Blake2SerializerSigner(blake2b, determ) | 48.2 us | √ | baseline
Blake2SerializerSigner(blake2b) | 51.2 us | √ | 0.94x (slower than baseline)
Blake2SerializerSigner(blake2s, determ) | 47.8 us | √ | 1.0x (close to baseline)
Blake2SerializerSigner(blake2s) | 51.4 us | √ | 0.94x (slower than baseline)
Sentinel(blake2b) | 15.1 us | ⚠ | 3.2x (faster than baseline)
Sentinel(blake2s) | 14.9 us | √ | 3.2x (faster than baseline)
ItsDangerousURLSafeSerializer(blake2b) | 60.4 us | √ | 0.8x (slower than baseline)
ItsDangerousURLSafeSerializer(blake2s) | 59.2 us | √ | 0.81x (slower than baseline)
PyJWTSerializer(blake2b) | 50.7 us | √ | 0.95x (slower than baseline)
PyJWTSerializer(blake2s) | 49.9 us | √ | 0.97x (slower than baseline)
Results for BLAKE2 only, for a large payload:
Signer | Best Abs Time | Measure | Comparison
---------------------------------------- | ------------- | ------- | -----------------------------
Blake2Signer(blake2b, determ) | 53.5 us | ⚠ | baseline
Blake2Signer(blake2b) | 55.8 us | ⚠ | 0.96x (slower than baseline)
Blake2Signer(blake2s, determ) | 79.7 us | √ | 0.67x (slower than baseline)
Blake2Signer(blake2s) | 81.6 us | √ | 0.66x (slower than baseline)
Sentinel(blake2b) | 50 us | √ | 1.1x (faster than baseline)
Sentinel(blake2s) | 76.4 us | √ | 0.7x (slower than baseline)
ItsDangerousSigner(blake2b) | 59.5 us | √ | 0.9x (slower than baseline)
ItsDangerousSigner(blake2s) | 84.9 us | √ | 0.63x (slower than baseline)
DjangoSigner(blake2b) | 60.5 us | √ | 0.88x (slower than baseline)
DjangoSigner(blake2s) | 85.4 us | √ | 0.63x (slower than baseline)
Serializer | Best Abs Time | Measure | Comparison
---------------------------------------- | ------------- | ------- | -----------------------------
Blake2SerializerSigner(blake2b, determ) | 456 us | √ | baseline
Blake2SerializerSigner(blake2b) | 460 us | √ | 0.99x (close to baseline)
Blake2SerializerSigner(blake2s, determ) | 452 us | √ | 1.0x (close to baseline)
Blake2SerializerSigner(blake2s) | 459 us | √ | 0.99x (close to baseline)
Sentinel(blake2b) | 403 us | ⚠ | 1.1x (faster than baseline)
Sentinel(blake2s) | 429 us | ⚠ | 1.1x (close to baseline)
ItsDangerousURLSafeSerializer(blake2b) | 474 us | ⚠ | 0.96x (slower than baseline)
ItsDangerousURLSafeSerializer(blake2s) | 473 us | √ | 0.97x (slower than baseline)
PyJWTSerializer(blake2b) | 575 us | √ | 0.79x (slower than baseline)
PyJWTSerializer(blake2s) | 609 us | ⚠ | 0.75x (slower than baseline)
The fastest?
BLAKE2s is generally the fastest algorithm for small payloads, while BLAKE3 performs best for large payloads (see BLAKE version's performace comparison). From the SHA family, SHA1 may be comparable to BLAKE2 on large payloads, but slower than BLAKE3. SHA2 is always slower.
Info
You should take into account that the only significant performance comparison exists when BLAKE2 is used as the hashing algorithm; otherwise, the algorithm performance may outweigh the implementation. Other algorithms are noted here to be taken into consideration against the BLAKE2 algorithm, and not against this library in particular.
A reference function that uses BLAKE2 directly is used as a sentinel to compare against it: this lib can't be faster than that.
Note
The standard deviation presented on each evaluation should be around two orders of magnitude lower than the statistics mean for appropriate results. As a simple reference, if the mean is in ms, then the std dev should be in us.
Using other than CPython
This comparison has been written with CPython in mind.
Be careful running it under PyPy, or others as-is! My machine froze because the memory was exhausted. Try first with a few rounds like NUMBER=10.
