From 689ae0519d4b6e0e58f9974d7e64b6856dc84883 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Thu, 22 Oct 2026 14:57:33 -0700 Subject: [PATCH 1/2] gh-160333: deprecate BLAKE-2 salt and personalization without a key Passing *salt* or *person* to `hashlib.blake2b` or `hashlib.blake2s` without a non-empty *key* now emits a `DeprecationWarning`, even if they are empty. Keyed hashing still accepts these parameters. Removal is scheduled for Python 3.21. --- Doc/deprecations/pending-removal-in-3.21.rst | 8 ++ Doc/library/hashlib.rst | 4 + Doc/whatsnew/3.16.rst | 8 ++ Lib/test/test_hashlib.py | 102 ++++++++++++++---- ...-10-22-13-31-43.gh-issue-160333.ZmzhDw.rst | 3 + Modules/blake2module.c | 42 ++++++++ 6 files changed, 149 insertions(+), 18 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-10-22-13-31-43.gh-issue-160333.ZmzhDw.rst diff --git a/Doc/deprecations/pending-removal-in-3.21.rst b/Doc/deprecations/pending-removal-in-3.21.rst index 9fd0cd15aac5476..487c60fd0fcc74d 100644 --- a/Doc/deprecations/pending-removal-in-3.21.rst +++ b/Doc/deprecations/pending-removal-in-3.21.rst @@ -27,6 +27,14 @@ Pending removal in Python 3.21 parameter or pass the dispatch type explicitly instead. (Contributed by Bartosz Sławecki in :gh:`143465`.) +* :mod:`hashlib`: + + * Passing *salt* or *person* to :func:`~hashlib.blake2b` and + :func:`~hashlib.blake2s` with an empty or omitted *key* is deprecated + since Python 3.16 and slated for removal in Python 3.21. Keyed hashing + still accepts these parameters. + (Contributed by Bénédikt Tran in :gh:`160333`.) + * :mod:`struct`: * Soft-deprecated since Python 3.15, using ``'F'`` and ``'D'`` type codes are now diff --git a/Doc/library/hashlib.rst b/Doc/library/hashlib.rst index 5b666c5adac6456..f18c3e52a07030b 100644 --- a/Doc/library/hashlib.rst +++ b/Doc/library/hashlib.rst @@ -450,6 +450,10 @@ BLAKE2s 32 32 8 8 example, ``b'salt'`` and ``b'salt\x00'`` is the same value. (This is not the case for *key*.) +.. deprecated-removed:: next 3.21 + Passing *salt* or *person* with an empty or omitted *key* is deprecated. + + These sizes are available as module `constants`_ described below. Constructor functions also accept the following tree hashing parameters: diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index 7995da012858245..d9290e3cb6bab8d 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -1154,6 +1154,14 @@ New deprecations instead. (Contributed by Bartosz Sławecki in :gh:`143465`.) +* :mod:`hashlib`: + + * Passing *salt* or *person* to :func:`~hashlib.blake2b` and + :func:`~hashlib.blake2s` with an empty or omitted *key* is deprecated + since Python 3.16 and slated for removal in Python 3.21. Keyed hashing + still accepts these parameters. + (Contributed by Bénédikt Tran in :gh:`160333`.) + * :mod:`struct`: * The ``'F'`` and ``'D'`` type codes, soft-deprecated since Python 3.15, diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py index a718be415b99055..670d483335bc4fd 100644 --- a/Lib/test/test_hashlib.py +++ b/Lib/test/test_hashlib.py @@ -26,6 +26,7 @@ from test.support.import_helper import import_fresh_module from test.support import requires_resource from test.support import threading_helper +from test.support import warnings_helper from http.client import HTTPException @@ -125,6 +126,14 @@ def read_vectors(hash_name): ) +def deprecated_blake2_unkeyed_parameter(name): + return re.escape( + f"the {name!r} keyword parameter is deprecated for unkeyed " + "hashing since Python 3.16 and slated for removal in Python 3.21; " + f"use a non-empty 'key' or omit {name!r}" + ) + + class HashLibTestCase(unittest.TestCase): supported_hash_names = ( 'md5', 'MD5', 'sha1', 'SHA1', 'sha224', 'SHA224', 'sha256', 'SHA256', @@ -839,16 +848,26 @@ def skip_if_blake2_not_builtin(self, name, skip_reason): def check_blake2(self, constructor, salt_size, person_size, key_size, digest_size, max_offset): self.assertEqual(constructor.SALT_SIZE, salt_size) + msg = deprecated_blake2_unkeyed_parameter('salt') for i in range(salt_size + 1): - constructor(salt=b'a' * i) + with self.assertWarnsRegex(DeprecationWarning, msg): + constructor(salt=b'a' * i) + constructor(key=b'k', salt=b'a' * i) salt = b'a' * (salt_size + 1) - self.assertRaises(ValueError, constructor, salt=salt) + with self.assertWarnsRegex(DeprecationWarning, msg): + self.assertRaises(ValueError, constructor, salt=salt) + self.assertRaises(ValueError, constructor, key=b'k', salt=salt) self.assertEqual(constructor.PERSON_SIZE, person_size) + msg = deprecated_blake2_unkeyed_parameter('person') for i in range(person_size+1): - constructor(person=b'a' * i) + with self.assertWarnsRegex(DeprecationWarning, msg): + constructor(person=b'a' * i) + constructor(key=b'k', person=b'a' * i) person = b'a' * (person_size + 1) - self.assertRaises(ValueError, constructor, person=person) + with self.assertWarnsRegex(DeprecationWarning, msg): + self.assertRaises(ValueError, constructor, person=person) + self.assertRaises(ValueError, constructor, key=b'k', person=person) self.assertEqual(constructor.MAX_DIGEST_SIZE, digest_size) for i in range(1, digest_size + 1): @@ -898,20 +917,26 @@ def check_blake2(self, constructor, salt_size, person_size, key_size, self.assertRaises(TypeError, constructor, '') - constructor( - b'', - key=b'', - salt=b'', - person=b'', - digest_size=17, - fanout=1, - depth=1, - leaf_size=256, - node_offset=512, - node_depth=1, - inner_size=7, - last_node=True - ) + salt_msg = deprecated_blake2_unkeyed_parameter('salt') + person_msg = deprecated_blake2_unkeyed_parameter('person') + with warnings_helper.check_warnings( + (salt_msg, DeprecationWarning), + (person_msg, DeprecationWarning), + ): + constructor( + b'', + key=b'', + salt=b'', + person=b'', + digest_size=17, + fanout=1, + depth=1, + leaf_size=256, + node_offset=512, + node_depth=1, + inner_size=7, + last_node=True + ) def blake2_rfc7693(self, constructor, md_len, in_len): def selftest_seq(length, seed): @@ -1023,6 +1048,47 @@ def test_blake2s_vectors(self): key = bytes.fromhex(key) self.check('blake2s', msg, md, key=key) + @requires_blake2 + @support.subTests("name", ["blake2b", "blake2s"]) + @support.subTests("parameter", ["salt", "person"]) + @support.subTests("value", [b'', b'x']) + @support.subTests("key", [None, b'']) + def test_blake2_unkeyed_parameter_deprecated(self, name, parameter, + value, key): + constructor = getattr(hashlib, name) + msg = deprecated_blake2_unkeyed_parameter(parameter) + kwargs = {parameter: value} + if key is not None: + kwargs['key'] = key + with self.assertWarnsRegex(DeprecationWarning, msg): + constructor(**kwargs) + with self.assertWarnsRegex(DeprecationWarning, msg): + hashlib.new(name, **kwargs) + + @requires_blake2 + @support.subTests("name", ["blake2b", "blake2s"]) + @support.subTests("parameter", ["salt", "person"]) + @support.subTests("value", [b'', b'x']) + def test_blake2_keyed_parameter_not_deprecated(self, name, parameter, + value): + constructor = getattr(hashlib, name) + kwargs = {'key': b'key', parameter: value} + with warnings_helper.check_no_warnings(self, + category=DeprecationWarning): + constructor(**kwargs) + hashlib.new(name, **kwargs) + + @requires_blake2 + @support.subTests("name", ["blake2b", "blake2s"]) + @support.subTests("key", [None, b'']) + def test_blake2_unkeyed_parameters_omitted(self, name, key): + constructor = getattr(hashlib, name) + kwargs = {} if key is None else {'key': key} + with warnings_helper.check_no_warnings(self, + category=DeprecationWarning): + constructor(b'data', **kwargs) + hashlib.new(name, b'data', **kwargs) + @requires_sha3 def test_case_sha3_224_0(self): self.check('sha3_224', b"", diff --git a/Misc/NEWS.d/next/Library/2026-10-22-13-31-43.gh-issue-160333.ZmzhDw.rst b/Misc/NEWS.d/next/Library/2026-10-22-13-31-43.gh-issue-160333.ZmzhDw.rst new file mode 100644 index 000000000000000..ef21306d1d9423f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-22-13-31-43.gh-issue-160333.ZmzhDw.rst @@ -0,0 +1,3 @@ +:mod:`hashlib`: Passing *salt* or *person* to :func:`~hashlib.blake2b` and +:func:`~hashlib.blake2s` with an empty or omitted *key* is now deprecated +and slated for removal in Python 3.21. Patch by Bénédikt Tran. \ No newline at end of file diff --git a/Modules/blake2module.c b/Modules/blake2module.c index 551ebd7cd052af9..93ecc6965141136 100644 --- a/Modules/blake2module.c +++ b/Modules/blake2module.c @@ -561,6 +561,44 @@ py_blake2_validate_params(Blake2Object *self, return -1; } +/* + * Emit a DeprecationWarning for a parameter given without a key. + * + * Return 0 on success; otherwise, set an exception and return -1. + */ +static int +blake2_warn_unkeyed_parameter(const char *name) +{ + return PyErr_WarnFormat(PyExc_DeprecationWarning, 1, + "the '%s' keyword parameter is deprecated for " + "unkeyed hashing since Python 3.16 and slated " + "for removal in Python 3.21; use a non-empty " + "'key' or omit '%s'", name, name); +} + +/* + * Check the salt and personalization parameters for unkeyed hashing. + * + * Omitting 'key' is not given or empty when using 'salt' or 'person' + * emits a deprecation warning. + * + * Return 0 on success; otherwise, set an exception and return -1. + */ +static int +blake2_check_unkeyed_parameters(Py_buffer *key, Py_buffer *salt, + Py_buffer *person) +{ + if (key->obj != NULL && key->len > 0) { + return 0; + } + if (salt->obj != NULL && blake2_warn_unkeyed_parameter("salt") < 0) { + return -1; + } + if (person->obj != NULL && blake2_warn_unkeyed_parameter("person") < 0) { + return -1; + } + return 0; +} static PyObject * py_blake2_new(PyTypeObject *type, PyObject *data, int digest_size, @@ -571,6 +609,10 @@ py_blake2_new(PyTypeObject *type, PyObject *data, int digest_size, { Blake2Object *self = NULL; + if (blake2_check_unkeyed_parameters(key, salt, person) < 0) { + return NULL; + } + self = new_Blake2Object(type); if (self == NULL) { goto error; From f19452a5c202eb18d63994b8b9dc124951ec3e07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Thu, 22 Oct 2026 15:26:04 -0700 Subject: [PATCH 2/2] Update 2026-10-22-13-31-43.gh-issue-160333.ZmzhDw.rst --- .../next/Library/2026-10-22-13-31-43.gh-issue-160333.ZmzhDw.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2026-10-22-13-31-43.gh-issue-160333.ZmzhDw.rst b/Misc/NEWS.d/next/Library/2026-10-22-13-31-43.gh-issue-160333.ZmzhDw.rst index ef21306d1d9423f..37592ec0165ac9c 100644 --- a/Misc/NEWS.d/next/Library/2026-10-22-13-31-43.gh-issue-160333.ZmzhDw.rst +++ b/Misc/NEWS.d/next/Library/2026-10-22-13-31-43.gh-issue-160333.ZmzhDw.rst @@ -1,3 +1,3 @@ :mod:`hashlib`: Passing *salt* or *person* to :func:`~hashlib.blake2b` and :func:`~hashlib.blake2s` with an empty or omitted *key* is now deprecated -and slated for removal in Python 3.21. Patch by Bénédikt Tran. \ No newline at end of file +and slated for removal in Python 3.21. Patch by Bénédikt Tran.