Skip to content

Commit 14d2b41

Browse files
committed
Add BLAKE3 bindings to hashlib.
BLAKE3 is a fast new cryptographic hash algorithm created by the team behind BLAKE2. Python's BLAKE3 support uses local SIMD extensions where available, making it Python's fastest built-in hash algorithm on newer Intel and AMD CPUs for non-trivial inputs.
1 parent 586b24d commit 14d2b41

39 files changed

+33216
-11
lines changed

Lib/hashlib.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
than using new(name):
1313
1414
md5(), sha1(), sha224(), sha256(), sha384(), sha512(), blake2b(), blake2s(),
15-
sha3_224, sha3_256, sha3_384, sha3_512, shake_128, and shake_256.
15+
blake3(), sha3_224(), sha3_256(), sha3_384(), sha3_512(), shake_128(),
16+
and shake_256().
1617
1718
More algorithms may be available on your platform but the above are guaranteed
1819
to exist. See the algorithms_guaranteed and algorithms_available attributes
@@ -56,7 +57,7 @@
5657
# This tuple and __get_builtin_constructor() must be modified if a new
5758
# always available algorithm is added.
5859
__always_supported = ('md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512',
59-
'blake2b', 'blake2s',
60+
'blake2b', 'blake2s', 'blake3',
6061
'sha3_224', 'sha3_256', 'sha3_384', 'sha3_512',
6162
'shake_128', 'shake_256')
6263

@@ -103,6 +104,9 @@ def __get_builtin_constructor(name):
103104
import _blake2
104105
cache['blake2b'] = _blake2.blake2b
105106
cache['blake2s'] = _blake2.blake2s
107+
elif name in {'blake3'}:
108+
import _blake3
109+
cache['blake3'] = _blake3.blake3
106110
elif name in {'sha3_224', 'sha3_256', 'sha3_384', 'sha3_512'}:
107111
import _sha3
108112
cache['sha3_224'] = _sha3.sha3_224

Lib/test/blake3.test_vectors.json

Lines changed: 217 additions & 0 deletions
Large diffs are not rendered by default.

Lib/test/test_hashlib.py

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import hashlib
1212
import importlib
1313
import itertools
14+
import json
1415
import os
1516
import sys
1617
import sysconfig
@@ -28,7 +29,7 @@
2829
COMPILED_WITH_PYDEBUG = hasattr(sys, 'gettotalrefcount')
2930

3031
# default builtin hash module
31-
default_builtin_hashes = {'md5', 'sha1', 'sha256', 'sha512', 'sha3', 'blake2'}
32+
default_builtin_hashes = {'md5', 'sha1', 'sha256', 'sha512', 'sha3', 'blake2', 'blake3'}
3233
# --with-builtin-hashlib-hashes override
3334
builtin_hashes = sysconfig.get_config_var("PY_BUILTIN_HASHLIB_HASHES")
3435
if builtin_hashes is None:
@@ -64,6 +65,13 @@ def get_fips_mode():
6465

6566
requires_blake2 = unittest.skipUnless(_blake2, 'requires _blake2')
6667

68+
try:
69+
import _blake3
70+
except ImportError:
71+
_blake3 = None
72+
73+
requires_blake3 = unittest.skipUnless(_blake3, 'requires _blake3')
74+
6775
# bpo-46913: Don't test the _sha3 extension on a Python UBSAN build
6876
SKIP_SHA3 = support.check_sanitizer(ub=True)
6977
requires_sha3 = unittest.skipUnless(not SKIP_SHA3, 'requires _sha3')
@@ -100,7 +108,7 @@ class HashLibTestCase(unittest.TestCase):
100108
supported_hash_names = ( 'md5', 'MD5', 'sha1', 'SHA1',
101109
'sha224', 'SHA224', 'sha256', 'SHA256',
102110
'sha384', 'SHA384', 'sha512', 'SHA512',
103-
'blake2b', 'blake2s',
111+
'blake2b', 'blake2s', 'blake3',
104112
'sha3_224', 'sha3_256', 'sha3_384', 'sha3_512',
105113
'shake_128', 'shake_256')
106114

@@ -127,6 +135,10 @@ def __init__(self, *args, **kwargs):
127135
if _blake2:
128136
algorithms.update({'blake2b', 'blake2s'})
129137

138+
_blake3 = self._conditional_import_module('_blake3')
139+
if _blake3:
140+
algorithms.add('blake3')
141+
130142
self.constructors_to_test = {}
131143
for algorithm in algorithms:
132144
if SKIP_SHA3 and algorithm.startswith('sha3_'):
@@ -182,6 +194,8 @@ def add_builtin_constructor(name):
182194
if _blake2:
183195
add_builtin_constructor('blake2s')
184196
add_builtin_constructor('blake2b')
197+
if _blake3:
198+
add_builtin_constructor('blake3')
185199

186200
if not SKIP_SHA3:
187201
_sha3 = self._conditional_import_module('_sha3')
@@ -390,6 +404,10 @@ def test_no_unicode_blake2(self):
390404
self.check_no_unicode('blake2b')
391405
self.check_no_unicode('blake2s')
392406

407+
@requires_blake3
408+
def test_no_unicode_blake3(self):
409+
self.check_no_unicode('blake3')
410+
393411
@requires_sha3
394412
def test_no_unicode_sha3(self):
395413
self.check_no_unicode('sha3_224')
@@ -461,6 +479,10 @@ def test_blocksize_name_blake2(self):
461479
self.check_blocksize_name('blake2b', 128, 64)
462480
self.check_blocksize_name('blake2s', 64, 32)
463481

482+
@requires_blake3
483+
def test_blocksize_name_blake3(self):
484+
self.check_blocksize_name('blake3', 64, 32)
485+
464486
def test_case_md5_0(self):
465487
self.check(
466488
'md5', b'', 'd41d8cd98f00b204e9800998ecf8427e',
@@ -787,6 +809,59 @@ def test_blake2s_vectors(self):
787809
key = bytes.fromhex(key)
788810
self.check('blake2s', msg, md, key=key)
789811

812+
@requires_blake3
813+
def test_blake3_vectors(self):
814+
base_data = b''.join([bytes((i,)) for i in range(0, 251)])
815+
data = base_data * 408
816+
817+
path = os.path.dirname(__file__) + "/blake3.test_vectors.json"
818+
with open(path, "rb") as f:
819+
test_vectors = json.load(f)
820+
821+
blake3_key = test_vectors['key'].encode('ascii')
822+
blake3_derive_key_context = test_vectors['context_string'].encode('ascii')
823+
824+
def test(case):
825+
nonlocal data
826+
length = case['input_len']
827+
truncated = data[:length]
828+
829+
styles = ['stock', 'initialized stock', 'key', 'derived_key']
830+
for style in styles:
831+
update = True
832+
if style == 'stock':
833+
b = hashlib.blake3()
834+
expected = case['hash']
835+
elif style == 'initialized stock':
836+
b = hashlib.blake3(truncated)
837+
expected = case['hash']
838+
update = False
839+
elif style == 'key':
840+
b = hashlib.blake3(b'', key=blake3_key)
841+
expected = case['keyed_hash']
842+
elif style == 'derived_key':
843+
b = hashlib.blake3(derive_key_context=blake3_derive_key_context)
844+
expected = case['derive_key']
845+
846+
if update:
847+
b.update(truncated)
848+
849+
copy = b.copy()
850+
851+
for hasher in [b, copy]:
852+
got = hasher.hexdigest(length=len(expected) // 2)
853+
self.assertEqual(expected, got, f"\n\ndidn't get the right long hash!\n{ style=}\n{ length=}\n\n{expected=}\n\n{ got=}\n")
854+
855+
expected = expected[:b.output_length * 2]
856+
got = hasher.hexdigest()
857+
self.assertEqual(expected, got, f"\n\ndidn't get the right default-length hash!\n{ style=}\n{ length=}\n\n{expected=}\n\n{ got=}\n")
858+
859+
got = hasher.digest().hex()
860+
self.assertEqual(expected, got, f"\n\ndidn't get the right default-length digest!\n{ style=}\n{ length=}\n\n{expected=}\n\n{ got=}\n")
861+
862+
for case in test_vectors['cases']:
863+
test(case)
864+
790865
@requires_sha3
791866
def test_case_sha3_224_0(self):
792867
self.check('sha3_224', b"",
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Add BLAKE3 bindings to :mod:`hashlib`. BLAKE3 is a fast new cryptographic
2+
hash algorithm created by the team behind BLAKE2. Python's BLAKE3 support
3+
uses local SIMD extensions where available, making it Python's fastest
4+
built-in hash algorithm on newer Intel and AMD CPUs for non-trivial inputs.

Modules/_blake3/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
The "Modules/\_blake3/impl" directory contains an unmodified copy of the "c"
2+
directory from the BLAKE3 source code distribution (with the unnecessary-to-us
3+
Rust bindings removed), along with a copy of BLAKE3's "LICENSE".
4+
There's also a copy of BLAKE3's "test_vectors.json" in the "Lib/test" directory,
5+
although it's been renamed to "blake3.test_vectors.json".
6+
7+
I got all of that from the official BLAKE3 repo:
8+
9+
https://github.com/BLAKE3-team/BLAKE3/
10+
11+
--larry 2022/03/03

0 commit comments

Comments
 (0)