# HG changeset patch # User Benjamin Peterson # Date 1409233289 14400 # Node ID 3f73c44b1fd1d442d6841493328e9756fb5e7ef5 # Parent 97081a80f487841d81aeed55d398a1dba1faca00 PEP 466: backport hashlib algorithm constants (closes #21307) diff --git a/Doc/library/hashlib.rst b/Doc/library/hashlib.rst --- a/Doc/library/hashlib.rst +++ b/Doc/library/hashlib.rst @@ -88,6 +88,24 @@ This module provides the following const .. versionadded:: 2.7 +.. data:: algorithms_guaranteed + + A set containing the names of the hash algorithms guaranteed to be supported + by this module on all platforms. + + .. versionadded:: 2.7.9 + +.. data:: algorithms_available + + A set containing the names of the hash algorithms that are available in the + running Python interpreter. These names will be recognized when passed to + :func:`new`. :attr:`algorithms_guaranteed` will always be a subset. The + same algorithm may appear multiple times in this set under different names + (thanks to OpenSSL). + + .. versionadded:: 2.7.9 + + The following values are provided as constant attributes of the hash objects returned by the constructors: diff -up Python-2.7.5/Lib/hashlib.py.hash Python-2.7.5/Lib/hashlib.py --- Python-2.7.5/Lib/hashlib.py.hash 2015-03-04 17:05:57.496598686 +0100 +++ Python-2.7.5/Lib/hashlib.py 2015-03-04 17:11:34.872739103 +0100 @@ -18,8 +18,9 @@ than using new(): md5(), sha1(), sha224(), sha256(), sha384(), and sha512() -More algorithms may be available on your platform but the above are -guaranteed to exist. +More algorithms may be available on your platform but the above are guaranteed +to exist. See the algorithms_guaranteed and algorithms_available attributes +to find out what algorithm names can be passed to new(). NOTE: If you want the adler32 or crc32 hash functions they are available in the zlib module. @@ -75,9 +76,14 @@ More condensed: # always available algorithm is added. __always_supported = ('md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512') +algorithms_guaranteed = set(__always_supported) +algorithms_available = set(__always_supported) + algorithms = __always_supported -__all__ = __always_supported + ('new', 'algorithms', 'pbkdf2_hmac') +__all__ = __always_supported + ('new', 'algorithms_guaranteed', + 'algorithms_available', 'algorithms', + 'pbkdf2_hmac') def __get_openssl_constructor(name): @@ -110,6 +116,8 @@ try: import _hashlib new = __hash_new __get_hash = __get_openssl_constructor + algorithms_available = algorithms_available.union( + _hashlib.openssl_md_meth_names) except ImportError: # We don't build the legacy modules raise diff -up Python-2.7.5/Modules/_hashopenssl.c.hash Python-2.7.5/Modules/_hashopenssl.c --- Python-2.7.5/Modules/_hashopenssl.c.hash 2015-03-04 17:06:18.246791837 +0100 +++ Python-2.7.5/Modules/_hashopenssl.c 2015-03-04 17:16:17.696369000 +0100 @@ -784,6 +784,61 @@ pbkdf2_hmac(PyObject *self, PyObject *ar #endif +/* State for our callback function so that it can accumulate a result. */ +typedef struct _internal_name_mapper_state { + PyObject *set; + int error; +} _InternalNameMapperState; + + +/* A callback function to pass to OpenSSL's OBJ_NAME_do_all(...) */ +static void +_openssl_hash_name_mapper(const OBJ_NAME *openssl_obj_name, void *arg) +{ + _InternalNameMapperState *state = (_InternalNameMapperState *)arg; + PyObject *py_name; + + assert(state != NULL); + if (openssl_obj_name == NULL) + return; + /* Ignore aliased names, they pollute the list and OpenSSL appears to + * have a its own definition of alias as the resulting list still + * contains duplicate and alternate names for several algorithms. */ + if (openssl_obj_name->alias) + return; + + py_name = PyString_FromString(openssl_obj_name->name); + if (py_name == NULL) { + state->error = 1; + } else { + if (PySet_Add(state->set, py_name) != 0) { + state->error = 1; + } + Py_DECREF(py_name); + } +} + + +/* Ask OpenSSL for a list of supported ciphers, filling in a Python set. */ +static PyObject* +generate_hash_name_list(void) +{ + _InternalNameMapperState state; + state.set = PyFrozenSet_New(NULL); + if (state.set == NULL) + return NULL; + state.error = 0; + + OBJ_NAME_do_all(OBJ_NAME_TYPE_MD_METH, &_openssl_hash_name_mapper, &state); + + if (state.error) { + Py_DECREF(state.set); + return NULL; + } + return state.set; +} + + /* * This macro and function generates a family of constructor function * definitions for specific hash algorithms. These constructors are much @@ -924,11 +979,11 @@ static struct PyMethodDef EVP_functions[ PyMODINIT_FUNC init_hashlib(void) { - PyObject *m; + PyObject *m, *openssl_md_meth_names; SSL_load_error_strings(); SSL_library_init(); - OpenSSL_add_all_digests(); + ERR_load_crypto_strings(); Py_TYPE(&EVPtype) = &PyType_Type; if (PyType_Ready(&EVPtype) < 0) @@ -938,6 +993,14 @@ init_hashlib(void) if (m == NULL) return; + openssl_md_meth_names = generate_hash_name_list(); + if (openssl_md_meth_names == NULL) { + return; + } + if (PyModule_AddObject(m, "openssl_md_meth_names", openssl_md_meth_names)) { + return; + } + #if HASH_OBJ_CONSTRUCTOR Py_INCREF(&EVPtype); PyModule_AddObject(m, "HASH", (PyObject *)&EVPtype); diff -up Python-2.7.5/Lib/test/test_hashlib.py.hash Python-2.7.5/Lib/test/test_hashlib.py --- Python-2.7.5/Lib/test/test_hashlib.py.hash 2015-03-04 18:04:57.823553474 +0100 +++ Python-2.7.5/Lib/test/test_hashlib.py 2015-03-04 18:06:39.395499123 +0100 @@ -107,6 +107,15 @@ class HashLibTestCase(unittest.TestCase) tuple([_algo for _algo in self.supported_hash_names if _algo.islower()])) + def test_algorithms_guaranteed(self): + self.assertEqual(hashlib.algorithms_guaranteed, + set(_algo for _algo in self.supported_hash_names + if _algo.islower())) + + def test_algorithms_available(self): + self.assertTrue(set(hashlib.algorithms_guaranteed). + issubset(hashlib.algorithms_available)) + def test_unknown_hash(self): self.assertRaises(ValueError, hashlib.new, 'spam spam spam spam spam') self.assertRaises(TypeError, hashlib.new, 1)