Issue #27928: Add scrypt (password-based key derivation function) to hashlib module (requires OpenSSL 1.1.0).
This commit is contained in:
parent
ac041c0aa7
commit
39093e9e68
|
@ -225,6 +225,23 @@ include a `salt <https://en.wikipedia.org/wiki/Salt_%28cryptography%29>`_.
|
|||
Python implementation uses an inline version of :mod:`hmac`. It is about
|
||||
three times slower and doesn't release the GIL.
|
||||
|
||||
.. function:: scrypt(password, *, salt, n, r, p, maxmem=0, dklen=64)
|
||||
|
||||
The function provides scrypt password-based key derivation function as
|
||||
defined in :rfc:`7914`.
|
||||
|
||||
*password* and *salt* must be bytes-like objects. Applications and
|
||||
libraries should limit *password* to a sensible length (e.g. 1024). *salt*
|
||||
should be about 16 or more bytes from a proper source, e.g. :func:`os.urandom`.
|
||||
|
||||
*n* is the CPU/Memory cost factor, *r* the block size, *p* parallelization
|
||||
factor and *maxmem* limits memory (OpenSSL 1.1.0 defaults to 32 MB).
|
||||
*dklen* is the length of the derived key.
|
||||
|
||||
Availability: OpenSSL 1.1+
|
||||
|
||||
.. versionadded:: 3.6
|
||||
|
||||
|
||||
.. seealso::
|
||||
|
||||
|
|
|
@ -202,6 +202,12 @@ except ImportError:
|
|||
|
||||
return dkey[:dklen]
|
||||
|
||||
try:
|
||||
# OpenSSL's scrypt requires OpenSSL 1.1+
|
||||
from _hashlib import scrypt
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
for __func_name in __always_supported:
|
||||
# try them all, some may not work due to the OpenSSL
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#
|
||||
|
||||
import array
|
||||
from binascii import unhexlify
|
||||
import hashlib
|
||||
import itertools
|
||||
import os
|
||||
|
@ -447,6 +448,12 @@ class KDFTests(unittest.TestCase):
|
|||
(b'pass\0word', b'sa\0lt', 4096, 16),
|
||||
]
|
||||
|
||||
scrypt_test_vectors = [
|
||||
(b'', b'', 16, 1, 1, unhexlify('77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906')),
|
||||
(b'password', b'NaCl', 1024, 8, 16, unhexlify('fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b3731622eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640')),
|
||||
(b'pleaseletmein', b'SodiumChloride', 16384, 8, 1, unhexlify('7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887')),
|
||||
]
|
||||
|
||||
pbkdf2_results = {
|
||||
"sha1": [
|
||||
# official test vectors from RFC 6070
|
||||
|
@ -526,5 +533,45 @@ class KDFTests(unittest.TestCase):
|
|||
self._test_pbkdf2_hmac(c_hashlib.pbkdf2_hmac)
|
||||
|
||||
|
||||
@unittest.skipUnless(hasattr(c_hashlib, 'scrypt'),
|
||||
' test requires OpenSSL > 1.1')
|
||||
def test_scrypt(self):
|
||||
for password, salt, n, r, p, expected in self.scrypt_test_vectors:
|
||||
result = hashlib.scrypt(password, salt=salt, n=n, r=r, p=p)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
# this values should work
|
||||
hashlib.scrypt(b'password', salt=b'salt', n=2, r=8, p=1)
|
||||
# password and salt must be bytes-like
|
||||
with self.assertRaises(TypeError):
|
||||
hashlib.scrypt('password', salt=b'salt', n=2, r=8, p=1)
|
||||
with self.assertRaises(TypeError):
|
||||
hashlib.scrypt(b'password', salt='salt', n=2, r=8, p=1)
|
||||
# require keyword args
|
||||
with self.assertRaises(TypeError):
|
||||
hashlib.scrypt(b'password')
|
||||
with self.assertRaises(TypeError):
|
||||
hashlib.scrypt(b'password', b'salt')
|
||||
with self.assertRaises(TypeError):
|
||||
hashlib.scrypt(b'password', 2, 8, 1, salt=b'salt')
|
||||
for n in [-1, 0, 1, None]:
|
||||
with self.assertRaises((ValueError, OverflowError, TypeError)):
|
||||
hashlib.scrypt(b'password', salt=b'salt', n=n, r=8, p=1)
|
||||
for r in [-1, 0, None]:
|
||||
with self.assertRaises((ValueError, OverflowError, TypeError)):
|
||||
hashlib.scrypt(b'password', salt=b'salt', n=2, r=r, p=1)
|
||||
for p in [-1, 0, None]:
|
||||
with self.assertRaises((ValueError, OverflowError, TypeError)):
|
||||
hashlib.scrypt(b'password', salt=b'salt', n=2, r=8, p=p)
|
||||
for maxmem in [-1, None]:
|
||||
with self.assertRaises((ValueError, OverflowError, TypeError)):
|
||||
hashlib.scrypt(b'password', salt=b'salt', n=2, r=8, p=1,
|
||||
maxmem=maxmem)
|
||||
for dklen in [-1, None]:
|
||||
with self.assertRaises((ValueError, OverflowError, TypeError)):
|
||||
hashlib.scrypt(b'password', salt=b'salt', n=2, r=8, p=1,
|
||||
dklen=dklen)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
@ -85,6 +85,9 @@ Core and Builtins
|
|||
Library
|
||||
-------
|
||||
|
||||
- Issue #27928: Add scrypt (password-based key derivation function) to
|
||||
hashlib module (requires OpenSSL 1.1.0).
|
||||
|
||||
- Issue #27850: Remove 3DES from ssl module's default cipher list to counter
|
||||
measure sweet32 attack (CVE-2016-2183).
|
||||
|
||||
|
|
|
@ -25,6 +25,12 @@
|
|||
#include <openssl/objects.h>
|
||||
#include "openssl/err.h"
|
||||
|
||||
#include "clinic/_hashopenssl.c.h"
|
||||
/*[clinic input]
|
||||
module _hashlib
|
||||
[clinic start generated code]*/
|
||||
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=c2b4ff081bac4be1]*/
|
||||
|
||||
#define MUNCH_SIZE INT_MAX
|
||||
|
||||
#ifndef HASH_OBJ_CONSTRUCTOR
|
||||
|
@ -713,6 +719,128 @@ pbkdf2_hmac(PyObject *self, PyObject *args, PyObject *kwdict)
|
|||
|
||||
#endif
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER > 0x10100000L && !defined(OPENSSL_NO_SCRYPT) && !defined(LIBRESSL_VERSION_NUMBER)
|
||||
#define PY_SCRYPT 1
|
||||
|
||||
/*[clinic input]
|
||||
_hashlib.scrypt
|
||||
|
||||
password: Py_buffer
|
||||
*
|
||||
salt: Py_buffer = None
|
||||
n as n_obj: object(subclass_of='&PyLong_Type') = None
|
||||
r as r_obj: object(subclass_of='&PyLong_Type') = None
|
||||
p as p_obj: object(subclass_of='&PyLong_Type') = None
|
||||
maxmem: long = 0
|
||||
dklen: long = 64
|
||||
|
||||
|
||||
scrypt password-based key derivation function.
|
||||
[clinic start generated code]*/
|
||||
|
||||
static PyObject *
|
||||
_hashlib_scrypt_impl(PyObject *module, Py_buffer *password, Py_buffer *salt,
|
||||
PyObject *n_obj, PyObject *r_obj, PyObject *p_obj,
|
||||
long maxmem, long dklen)
|
||||
/*[clinic end generated code: output=14849e2aa2b7b46c input=48a7d63bf3f75c42]*/
|
||||
{
|
||||
PyObject *key_obj = NULL;
|
||||
char *key;
|
||||
int retval;
|
||||
unsigned long n, r, p;
|
||||
|
||||
if (password->len > INT_MAX) {
|
||||
PyErr_SetString(PyExc_OverflowError,
|
||||
"password is too long.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (salt->buf == NULL) {
|
||||
PyErr_SetString(PyExc_TypeError,
|
||||
"salt is required");
|
||||
return NULL;
|
||||
}
|
||||
if (salt->len > INT_MAX) {
|
||||
PyErr_SetString(PyExc_OverflowError,
|
||||
"salt is too long.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
n = PyLong_AsUnsignedLong(n_obj);
|
||||
if (n == (unsigned long) -1 && PyErr_Occurred()) {
|
||||
PyErr_SetString(PyExc_TypeError,
|
||||
"n is required and must be an unsigned int");
|
||||
return NULL;
|
||||
}
|
||||
if (n < 2 || n & (n - 1)) {
|
||||
PyErr_SetString(PyExc_ValueError,
|
||||
"n must be a power of 2.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
r = PyLong_AsUnsignedLong(r_obj);
|
||||
if (r == (unsigned long) -1 && PyErr_Occurred()) {
|
||||
PyErr_SetString(PyExc_TypeError,
|
||||
"r is required and must be an unsigned int");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
p = PyLong_AsUnsignedLong(p_obj);
|
||||
if (p == (unsigned long) -1 && PyErr_Occurred()) {
|
||||
PyErr_SetString(PyExc_TypeError,
|
||||
"p is required and must be an unsigned int");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (maxmem < 0 || maxmem > INT_MAX) {
|
||||
/* OpenSSL 1.1.0 restricts maxmem to 32MB. It may change in the
|
||||
future. The maxmem constant is private to OpenSSL. */
|
||||
PyErr_Format(PyExc_ValueError,
|
||||
"maxmem must be positive and smaller than %d",
|
||||
INT_MAX);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (dklen < 1 || dklen > INT_MAX) {
|
||||
PyErr_Format(PyExc_ValueError,
|
||||
"dklen must be greater than 0 and smaller than %d",
|
||||
INT_MAX);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* let OpenSSL validate the rest */
|
||||
retval = EVP_PBE_scrypt(NULL, 0, NULL, 0, n, r, p, maxmem, NULL, 0);
|
||||
if (!retval) {
|
||||
/* sorry, can't do much better */
|
||||
PyErr_SetString(PyExc_ValueError,
|
||||
"Invalid paramemter combination for n, r, p, maxmem.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
key_obj = PyBytes_FromStringAndSize(NULL, dklen);
|
||||
if (key_obj == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
key = PyBytes_AS_STRING(key_obj);
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
retval = EVP_PBE_scrypt(
|
||||
(const char*)password->buf, (size_t)password->len,
|
||||
(const unsigned char *)salt->buf, (size_t)salt->len,
|
||||
n, r, p, maxmem,
|
||||
(unsigned char *)key, (size_t)dklen
|
||||
);
|
||||
Py_END_ALLOW_THREADS
|
||||
|
||||
if (!retval) {
|
||||
Py_CLEAR(key_obj);
|
||||
_setException(PyExc_ValueError);
|
||||
return NULL;
|
||||
}
|
||||
return key_obj;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* State for our callback function so that it can accumulate a result. */
|
||||
typedef struct _internal_name_mapper_state {
|
||||
PyObject *set;
|
||||
|
@ -836,6 +964,7 @@ static struct PyMethodDef EVP_functions[] = {
|
|||
{"pbkdf2_hmac", (PyCFunction)pbkdf2_hmac, METH_VARARGS|METH_KEYWORDS,
|
||||
pbkdf2_hmac__doc__},
|
||||
#endif
|
||||
_HASHLIB_SCRYPT_METHODDEF
|
||||
CONSTRUCTOR_METH_DEF(md5),
|
||||
CONSTRUCTOR_METH_DEF(sha1),
|
||||
CONSTRUCTOR_METH_DEF(sha224),
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
/*[clinic input]
|
||||
preserve
|
||||
[clinic start generated code]*/
|
||||
|
||||
#if (OPENSSL_VERSION_NUMBER > 0x10100000L && !defined(OPENSSL_NO_SCRYPT) && !defined(LIBRESSL_VERSION_NUMBER))
|
||||
|
||||
PyDoc_STRVAR(_hashlib_scrypt__doc__,
|
||||
"scrypt($module, /, password, *, salt=None, n=None, r=None, p=None,\n"
|
||||
" maxmem=0, dklen=64)\n"
|
||||
"--\n"
|
||||
"\n"
|
||||
"scrypt password-based key derivation function.");
|
||||
|
||||
#define _HASHLIB_SCRYPT_METHODDEF \
|
||||
{"scrypt", (PyCFunction)_hashlib_scrypt, METH_VARARGS|METH_KEYWORDS, _hashlib_scrypt__doc__},
|
||||
|
||||
static PyObject *
|
||||
_hashlib_scrypt_impl(PyObject *module, Py_buffer *password, Py_buffer *salt,
|
||||
PyObject *n_obj, PyObject *r_obj, PyObject *p_obj,
|
||||
long maxmem, long dklen);
|
||||
|
||||
static PyObject *
|
||||
_hashlib_scrypt(PyObject *module, PyObject *args, PyObject *kwargs)
|
||||
{
|
||||
PyObject *return_value = NULL;
|
||||
static const char * const _keywords[] = {"password", "salt", "n", "r", "p", "maxmem", "dklen", NULL};
|
||||
static _PyArg_Parser _parser = {"y*|$y*O!O!O!ll:scrypt", _keywords, 0};
|
||||
Py_buffer password = {NULL, NULL};
|
||||
Py_buffer salt = {NULL, NULL};
|
||||
PyObject *n_obj = Py_None;
|
||||
PyObject *r_obj = Py_None;
|
||||
PyObject *p_obj = Py_None;
|
||||
long maxmem = 0;
|
||||
long dklen = 64;
|
||||
|
||||
if (!_PyArg_ParseTupleAndKeywordsFast(args, kwargs, &_parser,
|
||||
&password, &salt, &PyLong_Type, &n_obj, &PyLong_Type, &r_obj, &PyLong_Type, &p_obj, &maxmem, &dklen)) {
|
||||
goto exit;
|
||||
}
|
||||
return_value = _hashlib_scrypt_impl(module, &password, &salt, n_obj, r_obj, p_obj, maxmem, dklen);
|
||||
|
||||
exit:
|
||||
/* Cleanup for password */
|
||||
if (password.obj) {
|
||||
PyBuffer_Release(&password);
|
||||
}
|
||||
/* Cleanup for salt */
|
||||
if (salt.obj) {
|
||||
PyBuffer_Release(&salt);
|
||||
}
|
||||
|
||||
return return_value;
|
||||
}
|
||||
|
||||
#endif /* (OPENSSL_VERSION_NUMBER > 0x10100000L && !defined(OPENSSL_NO_SCRYPT) && !defined(LIBRESSL_VERSION_NUMBER)) */
|
||||
|
||||
#ifndef _HASHLIB_SCRYPT_METHODDEF
|
||||
#define _HASHLIB_SCRYPT_METHODDEF
|
||||
#endif /* !defined(_HASHLIB_SCRYPT_METHODDEF) */
|
||||
/*[clinic end generated code: output=8c5386789f77430a input=a9049054013a1b77]*/
|
Loading…
Reference in New Issue