Skip to content

bpo-39298: Add BLAKE3 bindings to hashlib. #31686

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions Lib/hashlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
than using new(name):

md5(), sha1(), sha224(), sha256(), sha384(), sha512(), blake2b(), blake2s(),
sha3_224, sha3_256, sha3_384, sha3_512, shake_128, and shake_256.
blake3(), sha3_224(), sha3_256(), sha3_384(), sha3_512(), shake_128(),
and shake_256().

More algorithms may be available on your platform but the above are guaranteed
to exist. See the algorithms_guaranteed and algorithms_available attributes
Expand Down Expand Up @@ -56,7 +57,7 @@
# This tuple and __get_builtin_constructor() must be modified if a new
# always available algorithm is added.
__always_supported = ('md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512',
'blake2b', 'blake2s',
'blake2b', 'blake2s', 'blake3',
'sha3_224', 'sha3_256', 'sha3_384', 'sha3_512',
'shake_128', 'shake_256')

Expand Down Expand Up @@ -103,6 +104,9 @@ def __get_builtin_constructor(name):
import _blake2
cache['blake2b'] = _blake2.blake2b
cache['blake2s'] = _blake2.blake2s
elif name in {'blake3'}:
import _blake3
cache['blake3'] = _blake3.blake3
elif name in {'sha3_224', 'sha3_256', 'sha3_384', 'sha3_512'}:
import _sha3
cache['sha3_224'] = _sha3.sha3_224
Expand Down
217 changes: 217 additions & 0 deletions Lib/test/blake3.test_vectors.json

Large diffs are not rendered by default.

79 changes: 77 additions & 2 deletions Lib/test/test_hashlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import hashlib
import importlib
import itertools
import json
import os
import sys
import sysconfig
Expand All @@ -28,7 +29,7 @@
COMPILED_WITH_PYDEBUG = hasattr(sys, 'gettotalrefcount')

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

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

try:
import _blake3
except ImportError:
_blake3 = None

requires_blake3 = unittest.skipUnless(_blake3, 'requires _blake3')

# bpo-46913: Don't test the _sha3 extension on a Python UBSAN build
SKIP_SHA3 = support.check_sanitizer(ub=True)
requires_sha3 = unittest.skipUnless(not SKIP_SHA3, 'requires _sha3')
Expand Down Expand Up @@ -100,7 +108,7 @@ class HashLibTestCase(unittest.TestCase):
supported_hash_names = ( 'md5', 'MD5', 'sha1', 'SHA1',
'sha224', 'SHA224', 'sha256', 'SHA256',
'sha384', 'SHA384', 'sha512', 'SHA512',
'blake2b', 'blake2s',
'blake2b', 'blake2s', 'blake3',
'sha3_224', 'sha3_256', 'sha3_384', 'sha3_512',
'shake_128', 'shake_256')

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

_blake3 = self._conditional_import_module('_blake3')
if _blake3:
algorithms.add('blake3')

self.constructors_to_test = {}
for algorithm in algorithms:
if SKIP_SHA3 and algorithm.startswith('sha3_'):
Expand Down Expand Up @@ -182,6 +194,8 @@ def add_builtin_constructor(name):
if _blake2:
add_builtin_constructor('blake2s')
add_builtin_constructor('blake2b')
if _blake3:
add_builtin_constructor('blake3')

if not SKIP_SHA3:
_sha3 = self._conditional_import_module('_sha3')
Expand Down Expand Up @@ -390,6 +404,10 @@ def test_no_unicode_blake2(self):
self.check_no_unicode('blake2b')
self.check_no_unicode('blake2s')

@requires_blake3
def test_no_unicode_blake3(self):
self.check_no_unicode('blake3')

@requires_sha3
def test_no_unicode_sha3(self):
self.check_no_unicode('sha3_224')
Expand Down Expand Up @@ -461,6 +479,10 @@ def test_blocksize_name_blake2(self):
self.check_blocksize_name('blake2b', 128, 64)
self.check_blocksize_name('blake2s', 64, 32)

@requires_blake3
def test_blocksize_name_blake3(self):
self.check_blocksize_name('blake3', 64, 32)

def test_case_md5_0(self):
self.check(
'md5', b'', 'd41d8cd98f00b204e9800998ecf8427e',
Expand Down Expand Up @@ -787,6 +809,59 @@ def test_blake2s_vectors(self):
key = bytes.fromhex(key)
self.check('blake2s', msg, md, key=key)

@requires_blake3
def test_blake3_vectors(self):
base_data = b''.join([bytes((i,)) for i in range(0, 251)])
data = base_data * 408

path = os.path.dirname(__file__) + "/blake3.test_vectors.json"
with open(path, "rb") as f:
test_vectors = json.load(f)

blake3_key = test_vectors['key'].encode('ascii')
blake3_derive_key_context = test_vectors['context_string']

def test(case):
nonlocal data
length = case['input_len']
truncated = data[:length]

styles = ['stock', 'initialized stock', 'key', 'derived_key']
for style in styles:
update = True
if style == 'stock':
b = hashlib.blake3()
expected = case['hash']
elif style == 'initialized stock':
b = hashlib.blake3(truncated)
expected = case['hash']
update = False
elif style == 'key':
b = hashlib.blake3(b'', key=blake3_key)
expected = case['keyed_hash']
elif style == 'derived_key':
b = hashlib.blake3(derive_key_context=blake3_derive_key_context)
expected = case['derive_key']

if update:
b.update(truncated)

copy = b.copy()

for hasher in [b, copy]:
got = hasher.hexdigest(length=len(expected) // 2)
self.assertEqual(expected, got, f"\n\ndidn't get the right long hash!\n{ style=}\n{ length=}\n\n{expected=}\n\n{ got=}\n")

expected = expected[:b.output_length * 2]
got = hasher.hexdigest()
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")

got = hasher.digest().hex()
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")

for case in test_vectors['cases']:
test(case)

@requires_sha3
def test_case_sha3_224_0(self):
self.check('sha3_224', b"",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Add BLAKE3 bindings to :mod:`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.
11 changes: 11 additions & 0 deletions Modules/_blake3/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
The "Modules/\_blake3/impl" directory contains an unmodified copy of the "c"
directory from the BLAKE3 source code distribution (with the unnecessary-to-us
Rust bindings removed), along with a copy of BLAKE3's "LICENSE".
There's also a copy of BLAKE3's "test_vectors.json" in the "Lib/test" directory,
although it's been renamed to "blake3.test_vectors.json".

I got all of that from the official BLAKE3 repo:

https://github.com/BLAKE3-team/BLAKE3/

--larry 2022/03/03
Loading