From 4bc0ab39a6445f471e39389b549acacea900cfd3 Mon Sep 17 00:00:00 2001 From: shanyu Date: Sat, 3 Oct 2026 04:51:42 +0800 Subject: [PATCH] Reject BLAKE2b digest lengths that abort the process. libsodium treats an output length of 0 or above 64 as misuse. A directly constructed state reached that call. --- src/nacl/bindings/crypto_generichash.py | 39 +++++++++++++++++-------- tests/test_generichash.py | 19 ++++++++++++ 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/src/nacl/bindings/crypto_generichash.py b/src/nacl/bindings/crypto_generichash.py index 521873cc..38f7967d 100644 --- a/src/nacl/bindings/crypto_generichash.py +++ b/src/nacl/bindings/crypto_generichash.py @@ -40,12 +40,38 @@ _OVERLONG = "{0} length greater than {1} bytes" _TOOBIG = "{0} greater than {1}" +_TOOSMALL = "{0} smaller than {1}" + + +def _check_digest_size(digest_size: int) -> None: + """Reject a digest length that libsodium treats as misuse. + + ``crypto_generichash_blake2b_final`` calls ``sodium_misuse`` when the + output length is 0 or greater than 64, which aborts the process. + Lengths from 1 through ``crypto_generichash_BYTES_MAX`` stay valid. + """ + ensure( + isinstance(digest_size, int), + "Digest size must be an integer number", + raising=exc.TypeError, + ) + ensure( + digest_size >= 1, + _TOOSMALL.format("Digest_size", 1), + raising=exc.ValueError, + ) + ensure( + digest_size <= crypto_generichash_BYTES_MAX, + _TOOBIG.format("Digest_size", crypto_generichash_BYTES_MAX), + raising=exc.ValueError, + ) def _checkparams( digest_size: int, key: bytes, salt: bytes, person: bytes ) -> None: """Check hash parameters""" + _check_digest_size(digest_size) ensure( isinstance(key, bytes), "Key must be a bytes sequence", @@ -64,18 +90,6 @@ def _checkparams( raising=exc.TypeError, ) - ensure( - isinstance(digest_size, int), - "Digest size must be an integer number", - raising=exc.TypeError, - ) - - ensure( - digest_size <= crypto_generichash_BYTES_MAX, - _TOOBIG.format("Digest_size", crypto_generichash_BYTES_MAX), - raising=exc.ValueError, - ) - ensure( len(key) <= crypto_generichash_KEYBYTES_MAX, _OVERLONG.format("Key", crypto_generichash_KEYBYTES_MAX), @@ -270,6 +284,7 @@ def generichash_blake2b_final(state: Blake2State) -> bytes: "State must be a Blake2State object", raising=exc.TypeError, ) + _check_digest_size(state.digest_size) _digest = ffi.new("unsigned char[]", crypto_generichash_BYTES_MAX) rc = lib.crypto_generichash_blake2b_final( diff --git a/tests/test_generichash.py b/tests/test_generichash.py index 7aee1e4e..d4fddf00 100644 --- a/tests/test_generichash.py +++ b/tests/test_generichash.py @@ -245,3 +245,22 @@ def test_blake2_digest_size_descriptor_coherence(): assert h.name == "blake2b" assert h.block_size == 128 assert h.digest_size == 64 + + +def test_blake2b_rejects_digest_lengths_that_abort(): + # libsodium calls sodium_misuse for an output length of 0 or above 64. + # A directly constructed Blake2State used to reach that abort. + from nacl.bindings.crypto_generichash import ( + Blake2State, + generichash_blake2b_final, + generichash_blake2b_salt_personal, + ) + + one_byte = generichash_blake2b_salt_personal(b"abc", digest_size=1) + assert len(one_byte) == 1 + + for size in (0, -1, 65): + with pytest.raises(exc.ValueError): + generichash_blake2b_salt_personal(b"abc", digest_size=size) + with pytest.raises(exc.ValueError): + generichash_blake2b_final(Blake2State(size))