Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 39093e9

Browse files
committed
Issue #27928: Add scrypt (password-based key derivation function) to hashlib module (requires OpenSSL 1.1.0).
1 parent ac041c0 commit 39093e9

6 files changed

Lines changed: 262 additions & 0 deletions

File tree

Doc/library/hashlib.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,23 @@ include a `salt <https://en.wikipedia.org/wiki/Salt_%28cryptography%29>`_.
225225
Python implementation uses an inline version of :mod:`hmac`. It is about
226226
three times slower and doesn't release the GIL.
227227

228+
.. function:: scrypt(password, *, salt, n, r, p, maxmem=0, dklen=64)
229+
230+
The function provides scrypt password-based key derivation function as
231+
defined in :rfc:`7914`.
232+
233+
*password* and *salt* must be bytes-like objects. Applications and
234+
libraries should limit *password* to a sensible length (e.g. 1024). *salt*
235+
should be about 16 or more bytes from a proper source, e.g. :func:`os.urandom`.
236+
237+
*n* is the CPU/Memory cost factor, *r* the block size, *p* parallelization
238+
factor and *maxmem* limits memory (OpenSSL 1.1.0 defaults to 32 MB).
239+
*dklen* is the length of the derived key.
240+
241+
Availability: OpenSSL 1.1+
242+
243+
.. versionadded:: 3.6
244+
228245

229246
.. seealso::
230247

Lib/hashlib.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,12 @@ def prf(msg, inner=inner, outer=outer):
202202

203203
return dkey[:dklen]
204204

205+
try:
206+
# OpenSSL's scrypt requires OpenSSL 1.1+
207+
from _hashlib import scrypt
208+
except ImportError:
209+
pass
210+
205211

206212
for __func_name in __always_supported:
207213
# try them all, some may not work due to the OpenSSL

Lib/test/test_hashlib.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#
88

99
import array
10+
from binascii import unhexlify
1011
import hashlib
1112
import itertools
1213
import os
@@ -447,6 +448,12 @@ class KDFTests(unittest.TestCase):
447448
(b'pass\0word', b'sa\0lt', 4096, 16),
448449
]
449450

451+
scrypt_test_vectors = [
452+
(b'', b'', 16, 1, 1, unhexlify('77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906')),
453+
(b'password', b'NaCl', 1024, 8, 16, unhexlify('fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b3731622eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640')),
454+
(b'pleaseletmein', b'SodiumChloride', 16384, 8, 1, unhexlify('7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887')),
455+
]
456+
450457
pbkdf2_results = {
451458
"sha1": [
452459
# official test vectors from RFC 6070
@@ -526,5 +533,45 @@ def test_pbkdf2_hmac_c(self):
526533
self._test_pbkdf2_hmac(c_hashlib.pbkdf2_hmac)
527534

528535

536+
@unittest.skipUnless(hasattr(c_hashlib, 'scrypt'),
537+
' test requires OpenSSL > 1.1')
538+
def test_scrypt(self):
539+
for password, salt, n, r, p, expected in self.scrypt_test_vectors:
540+
result = hashlib.scrypt(password, salt=salt, n=n, r=r, p=p)
541+
self.assertEqual(result, expected)
542+
543+
# this values should work
544+
hashlib.scrypt(b'password', salt=b'salt', n=2, r=8, p=1)
545+
# password and salt must be bytes-like
546+
with self.assertRaises(TypeError):
547+
hashlib.scrypt('password', salt=b'salt', n=2, r=8, p=1)
548+
with self.assertRaises(TypeError):
549+
hashlib.scrypt(b'password', salt='salt', n=2, r=8, p=1)
550+
# require keyword args
551+
with self.assertRaises(TypeError):
552+
hashlib.scrypt(b'password')
553+
with self.assertRaises(TypeError):
554+
hashlib.scrypt(b'password', b'salt')
555+
with self.assertRaises(TypeError):
556+
hashlib.scrypt(b'password', 2, 8, 1, salt=b'salt')
557+
for n in [-1, 0, 1, None]:
558+
with self.assertRaises((ValueError, OverflowError, TypeError)):
559+
hashlib.scrypt(b'password', salt=b'salt', n=n, r=8, p=1)
560+
for r in [-1, 0, None]:
561+
with self.assertRaises((ValueError, OverflowError, TypeError)):
562+
hashlib.scrypt(b'password', salt=b'salt', n=2, r=r, p=1)
563+
for p in [-1, 0, None]:
564+
with self.assertRaises((ValueError, OverflowError, TypeError)):
565+
hashlib.scrypt(b'password', salt=b'salt', n=2, r=8, p=p)
566+
for maxmem in [-1, None]:
567+
with self.assertRaises((ValueError, OverflowError, TypeError)):
568+
hashlib.scrypt(b'password', salt=b'salt', n=2, r=8, p=1,
569+
maxmem=maxmem)
570+
for dklen in [-1, None]:
571+
with self.assertRaises((ValueError, OverflowError, TypeError)):
572+
hashlib.scrypt(b'password', salt=b'salt', n=2, r=8, p=1,
573+
dklen=dklen)
574+
575+
529576
if __name__ == "__main__":
530577
unittest.main()

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ Core and Builtins
8585
Library
8686
-------
8787

88+
- Issue #27928: Add scrypt (password-based key derivation function) to
89+
hashlib module (requires OpenSSL 1.1.0).
90+
8891
- Issue #27850: Remove 3DES from ssl module's default cipher list to counter
8992
measure sweet32 attack (CVE-2016-2183).
9093

Modules/_hashopenssl.c

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@
2525
#include <openssl/objects.h>
2626
#include "openssl/err.h"
2727

28+
#include "clinic/_hashopenssl.c.h"
29+
/*[clinic input]
30+
module _hashlib
31+
[clinic start generated code]*/
32+
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=c2b4ff081bac4be1]*/
33+
2834
#define MUNCH_SIZE INT_MAX
2935

3036
#ifndef HASH_OBJ_CONSTRUCTOR
@@ -713,6 +719,128 @@ pbkdf2_hmac(PyObject *self, PyObject *args, PyObject *kwdict)
713719

714720
#endif
715721

722+
#if OPENSSL_VERSION_NUMBER > 0x10100000L && !defined(OPENSSL_NO_SCRYPT) && !defined(LIBRESSL_VERSION_NUMBER)
723+
#define PY_SCRYPT 1
724+
725+
/*[clinic input]
726+
_hashlib.scrypt
727+
728+
password: Py_buffer
729+
*
730+
salt: Py_buffer = None
731+
n as n_obj: object(subclass_of='&PyLong_Type') = None
732+
r as r_obj: object(subclass_of='&PyLong_Type') = None
733+
p as p_obj: object(subclass_of='&PyLong_Type') = None
734+
maxmem: long = 0
735+
dklen: long = 64
736+
737+
738+
scrypt password-based key derivation function.
739+
[clinic start generated code]*/
740+
741+
static PyObject *
742+
_hashlib_scrypt_impl(PyObject *module, Py_buffer *password, Py_buffer *salt,
743+
PyObject *n_obj, PyObject *r_obj, PyObject *p_obj,
744+
long maxmem, long dklen)
745+
/*[clinic end generated code: output=14849e2aa2b7b46c input=48a7d63bf3f75c42]*/
746+
{
747+
PyObject *key_obj = NULL;
748+
char *key;
749+
int retval;
750+
unsigned long n, r, p;
751+
752+
if (password->len > INT_MAX) {
753+
PyErr_SetString(PyExc_OverflowError,
754+
"password is too long.");
755+
return NULL;
756+
}
757+
758+
if (salt->buf == NULL) {
759+
PyErr_SetString(PyExc_TypeError,
760+
"salt is required");
761+
return NULL;
762+
}
763+
if (salt->len > INT_MAX) {
764+
PyErr_SetString(PyExc_OverflowError,
765+
"salt is too long.");
766+
return NULL;
767+
}
768+
769+
n = PyLong_AsUnsignedLong(n_obj);
770+
if (n == (unsigned long) -1 && PyErr_Occurred()) {
771+
PyErr_SetString(PyExc_TypeError,
772+
"n is required and must be an unsigned int");
773+
return NULL;
774+
}
775+
if (n < 2 || n & (n - 1)) {
776+
PyErr_SetString(PyExc_ValueError,
777+
"n must be a power of 2.");
778+
return NULL;
779+
}
780+
781+
r = PyLong_AsUnsignedLong(r_obj);
782+
if (r == (unsigned long) -1 && PyErr_Occurred()) {
783+
PyErr_SetString(PyExc_TypeError,
784+
"r is required and must be an unsigned int");
785+
return NULL;
786+
}
787+
788+
p = PyLong_AsUnsignedLong(p_obj);
789+
if (p == (unsigned long) -1 && PyErr_Occurred()) {
790+
PyErr_SetString(PyExc_TypeError,
791+
"p is required and must be an unsigned int");
792+
return NULL;
793+
}
794+
795+
if (maxmem < 0 || maxmem > INT_MAX) {
796+
/* OpenSSL 1.1.0 restricts maxmem to 32MB. It may change in the
797+
future. The maxmem constant is private to OpenSSL. */
798+
PyErr_Format(PyExc_ValueError,
799+
"maxmem must be positive and smaller than %d",
800+
INT_MAX);
801+
return NULL;
802+
}
803+
804+
if (dklen < 1 || dklen > INT_MAX) {
805+
PyErr_Format(PyExc_ValueError,
806+
"dklen must be greater than 0 and smaller than %d",
807+
INT_MAX);
808+
return NULL;
809+
}
810+
811+
/* let OpenSSL validate the rest */
812+
retval = EVP_PBE_scrypt(NULL, 0, NULL, 0, n, r, p, maxmem, NULL, 0);
813+
if (!retval) {
814+
/* sorry, can't do much better */
815+
PyErr_SetString(PyExc_ValueError,
816+
"Invalid paramemter combination for n, r, p, maxmem.");
817+
return NULL;
818+
}
819+
820+
key_obj = PyBytes_FromStringAndSize(NULL, dklen);
821+
if (key_obj == NULL) {
822+
return NULL;
823+
}
824+
key = PyBytes_AS_STRING(key_obj);
825+
826+
Py_BEGIN_ALLOW_THREADS
827+
retval = EVP_PBE_scrypt(
828+
(const char*)password->buf, (size_t)password->len,
829+
(const unsigned char *)salt->buf, (size_t)salt->len,
830+
n, r, p, maxmem,
831+
(unsigned char *)key, (size_t)dklen
832+
);
833+
Py_END_ALLOW_THREADS
834+
835+
if (!retval) {
836+
Py_CLEAR(key_obj);
837+
_setException(PyExc_ValueError);
838+
return NULL;
839+
}
840+
return key_obj;
841+
}
842+
#endif
843+
716844
/* State for our callback function so that it can accumulate a result. */
717845
typedef struct _internal_name_mapper_state {
718846
PyObject *set;
@@ -836,6 +964,7 @@ static struct PyMethodDef EVP_functions[] = {
836964
{"pbkdf2_hmac", (PyCFunction)pbkdf2_hmac, METH_VARARGS|METH_KEYWORDS,
837965
pbkdf2_hmac__doc__},
838966
#endif
967+
_HASHLIB_SCRYPT_METHODDEF
839968
CONSTRUCTOR_METH_DEF(md5),
840969
CONSTRUCTOR_METH_DEF(sha1),
841970
CONSTRUCTOR_METH_DEF(sha224),

Modules/clinic/_hashopenssl.c.h

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*[clinic input]
2+
preserve
3+
[clinic start generated code]*/
4+
5+
#if (OPENSSL_VERSION_NUMBER > 0x10100000L && !defined(OPENSSL_NO_SCRYPT) && !defined(LIBRESSL_VERSION_NUMBER))
6+
7+
PyDoc_STRVAR(_hashlib_scrypt__doc__,
8+
"scrypt($module, /, password, *, salt=None, n=None, r=None, p=None,\n"
9+
" maxmem=0, dklen=64)\n"
10+
"--\n"
11+
"\n"
12+
"scrypt password-based key derivation function.");
13+
14+
#define _HASHLIB_SCRYPT_METHODDEF \
15+
{"scrypt", (PyCFunction)_hashlib_scrypt, METH_VARARGS|METH_KEYWORDS, _hashlib_scrypt__doc__},
16+
17+
static PyObject *
18+
_hashlib_scrypt_impl(PyObject *module, Py_buffer *password, Py_buffer *salt,
19+
PyObject *n_obj, PyObject *r_obj, PyObject *p_obj,
20+
long maxmem, long dklen);
21+
22+
static PyObject *
23+
_hashlib_scrypt(PyObject *module, PyObject *args, PyObject *kwargs)
24+
{
25+
PyObject *return_value = NULL;
26+
static const char * const _keywords[] = {"password", "salt", "n", "r", "p", "maxmem", "dklen", NULL};
27+
static _PyArg_Parser _parser = {"y*|$y*O!O!O!ll:scrypt", _keywords, 0};
28+
Py_buffer password = {NULL, NULL};
29+
Py_buffer salt = {NULL, NULL};
30+
PyObject *n_obj = Py_None;
31+
PyObject *r_obj = Py_None;
32+
PyObject *p_obj = Py_None;
33+
long maxmem = 0;
34+
long dklen = 64;
35+
36+
if (!_PyArg_ParseTupleAndKeywordsFast(args, kwargs, &_parser,
37+
&password, &salt, &PyLong_Type, &n_obj, &PyLong_Type, &r_obj, &PyLong_Type, &p_obj, &maxmem, &dklen)) {
38+
goto exit;
39+
}
40+
return_value = _hashlib_scrypt_impl(module, &password, &salt, n_obj, r_obj, p_obj, maxmem, dklen);
41+
42+
exit:
43+
/* Cleanup for password */
44+
if (password.obj) {
45+
PyBuffer_Release(&password);
46+
}
47+
/* Cleanup for salt */
48+
if (salt.obj) {
49+
PyBuffer_Release(&salt);
50+
}
51+
52+
return return_value;
53+
}
54+
55+
#endif /* (OPENSSL_VERSION_NUMBER > 0x10100000L && !defined(OPENSSL_NO_SCRYPT) && !defined(LIBRESSL_VERSION_NUMBER)) */
56+
57+
#ifndef _HASHLIB_SCRYPT_METHODDEF
58+
#define _HASHLIB_SCRYPT_METHODDEF
59+
#endif /* !defined(_HASHLIB_SCRYPT_METHODDEF) */
60+
/*[clinic end generated code: output=8c5386789f77430a input=a9049054013a1b77]*/

0 commit comments

Comments
 (0)