From e723622775172a2516f43721d998aae95f32e59d Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Sat, 19 Oct 2013 14:24:44 +0200 Subject: Issue #18582: provide a faster C implementation of pbkdf2_hmac that works with OpenSSL < 1.0 --- Doc/library/hashlib.rst | 8 ++-- Modules/_hashopenssl.c | 100 +++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 97 insertions(+), 11 deletions(-) diff --git a/Doc/library/hashlib.rst b/Doc/library/hashlib.rst index 677d530..b1daba1 100644 --- a/Doc/library/hashlib.rst +++ b/Doc/library/hashlib.rst @@ -212,11 +212,9 @@ slow and include a salt. .. versionadded:: 3.4 - .. note:: A fast implementation of *pbkdf2_hmac* is only available with - OpenSSL 1.0 and newer. The Python implementation uses an inline - version of :mod:`hmac` and is about three times slower. Contrary to - OpenSSL's current code the length of the password has only a minimal - impact on the runtime of the Python implementation. + .. note:: A fast implementation of *pbkdf2_hmac* is available with OpenSSL. + The Python implementation uses an inline version of :mod:`hmac`. It is + about three times slower and doesn't release the GIL. .. seealso:: diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index bec84ce..9dba5c7 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -20,6 +20,7 @@ /* EVP is the preferred interface to hashing in OpenSSL */ #include +#include /* We use the object interface to discover what hashes OpenSSL supports. */ #include #include "openssl/err.h" @@ -495,10 +496,97 @@ EVP_new(PyObject *self, PyObject *args, PyObject *kwdict) return ret_obj; } -#if (OPENSSL_VERSION_NUMBER >= 0x10000000 && !defined(OPENSSL_NO_HMAC) \ - && !defined(OPENSSL_NO_SHA)) + + +#if (!defined(OPENSSL_NO_HMAC) && !defined(OPENSSL_NO_SHA)) + #define PY_PBKDF2_HMAC 1 +/* Improved implementation of PKCS5_PBKDF2_HMAC() + * + * PKCS5_PBKDF2_HMAC_fast() hashes the password exactly one time instead of + * `iter` times. Today (2013) the iteration count is typically 100,000 or + * more. The improved algorithm is not subject to a Denial-of-Service + * vulnerability with overly large passwords. + * + * Also OpenSSL < 1.0 don't provide PKCS5_PBKDF2_HMAC(), only + * PKCS5_PBKDF2_SHA1. + */ +int PKCS5_PBKDF2_HMAC_fast(const char *pass, int passlen, + const unsigned char *salt, int saltlen, + int iter, const EVP_MD *digest, + int keylen, unsigned char *out) +{ + unsigned char digtmp[EVP_MAX_MD_SIZE], *p, itmp[4]; + int cplen, j, k, tkeylen, mdlen; + unsigned long i = 1; + HMAC_CTX hctx_tpl, hctx; + + mdlen = EVP_MD_size(digest); + if (mdlen < 0) + return 0; + + HMAC_CTX_init(&hctx_tpl); + HMAC_CTX_init(&hctx); + p = out; + tkeylen = keylen; + if (!pass) + passlen = 0; + else if(passlen == -1) + passlen = strlen(pass); + if (!HMAC_Init_ex(&hctx_tpl, pass, passlen, digest, NULL)) { + HMAC_CTX_cleanup(&hctx_tpl); + return 0; + } + while(tkeylen) { + if(tkeylen > mdlen) + cplen = mdlen; + else + cplen = tkeylen; + /* We are unlikely to ever use more than 256 blocks (5120 bits!) + * but just in case... + */ + itmp[0] = (unsigned char)((i >> 24) & 0xff); + itmp[1] = (unsigned char)((i >> 16) & 0xff); + itmp[2] = (unsigned char)((i >> 8) & 0xff); + itmp[3] = (unsigned char)(i & 0xff); + if (!HMAC_CTX_copy(&hctx, &hctx_tpl)) { + HMAC_CTX_cleanup(&hctx_tpl); + return 0; + } + if (!HMAC_Update(&hctx, salt, saltlen) + || !HMAC_Update(&hctx, itmp, 4) + || !HMAC_Final(&hctx, digtmp, NULL)) { + HMAC_CTX_cleanup(&hctx_tpl); + HMAC_CTX_cleanup(&hctx); + return 0; + } + memcpy(p, digtmp, cplen); + for (j = 1; j < iter; j++) { + if (!HMAC_CTX_copy(&hctx, &hctx_tpl)) { + HMAC_CTX_cleanup(&hctx_tpl); + return 0; + } + if (!HMAC_Update(&hctx, digtmp, mdlen) + || !HMAC_Final(&hctx, digtmp, NULL)) { + HMAC_CTX_cleanup(&hctx_tpl); + HMAC_CTX_cleanup(&hctx); + return 0; + } + HMAC_CTX_cleanup(&hctx); + for (k = 0; k < cplen; k++) { + p[k] ^= digtmp[k]; + } + } + tkeylen-= cplen; + i++; + p+= cplen; + } + HMAC_CTX_cleanup(&hctx_tpl); + return 1; +} + + PyDoc_STRVAR(pbkdf2_hmac__doc__, "pbkdf2_hmac(hash_name, password, salt, iterations, dklen=None) -> key\n\ \n\ @@ -579,10 +667,10 @@ pbkdf2_hmac(PyObject *self, PyObject *args, PyObject *kwdict) key = PyBytes_AS_STRING(key_obj); Py_BEGIN_ALLOW_THREADS - retval = PKCS5_PBKDF2_HMAC((char*)password.buf, password.len, - (unsigned char *)salt.buf, salt.len, - iterations, digest, dklen, - (unsigned char *)key); + retval = PKCS5_PBKDF2_HMAC_fast((char*)password.buf, password.len, + (unsigned char *)salt.buf, salt.len, + iterations, digest, dklen, + (unsigned char *)key); Py_END_ALLOW_THREADS if (!retval) { -- cgit v0.12