Skip to content

Commit

Permalink
Issue #18582: provide a faster C implementation of pbkdf2_hmac that w…
Browse files Browse the repository at this point in the history
…orks with OpenSSL < 1.0
  • Loading branch information
tiran committed Oct 19, 2013
1 parent fcd8de2 commit e723622
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 11 deletions.
8 changes: 3 additions & 5 deletions Doc/library/hashlib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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::
Expand Down
100 changes: 94 additions & 6 deletions Modules/_hashopenssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

/* EVP is the preferred interface to hashing in OpenSSL */
#include <openssl/evp.h>
#include <openssl/hmac.h>
/* We use the object interface to discover what hashes OpenSSL supports. */
#include <openssl/objects.h>
#include "openssl/err.h"
Expand Down Expand Up @@ -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\
Expand Down Expand Up @@ -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) {
Expand Down

0 comments on commit e723622

Please sign in to comment.