cpython: d926fa1a833c (original) (raw)
Mercurial > cpython
changeset 103127:d926fa1a833c
Issue #27928: Add scrypt (password-based key derivation function) to hashlib module (requires OpenSSL 1.1.0). [#27928]
Christian Heimes christian@python.org | |
---|---|
date | Tue, 06 Sep 2016 20:22:28 +0200 |
parents | f586742e56cb |
children | ae03163b6378 |
files | Doc/library/hashlib.rst Lib/hashlib.py Lib/test/test_hashlib.py Misc/NEWS Modules/_hashopenssl.c Modules/clinic/_hashopenssl.c.h |
diffstat | 6 files changed, 262 insertions(+), 0 deletions(-)[+] [-] Doc/library/hashlib.rst 17 Lib/hashlib.py 6 Lib/test/test_hashlib.py 47 Misc/NEWS 3 Modules/_hashopenssl.c 129 Modules/clinic/_hashopenssl.c.h 60 |
line wrap: on
line diff
--- a/Doc/library/hashlib.rst
+++ b/Doc/library/hashlib.rst
@@ -225,6 +225,23 @@ include a salt <https://en.wikipedia.or[](#l1.3) 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 +
--- a/Lib/hashlib.py +++ b/Lib/hashlib.py @@ -202,6 +202,12 @@ except ImportError: return dkey[:dklen] +try:
+ for __func_name in __always_supported: # try them all, some may not work due to the OpenSSL
--- a/Lib/test/test_hashlib.py +++ b/Lib/test/test_hashlib.py @@ -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')),[](#l3.16)
(b'password', b'NaCl', 1024, 8, 16, unhexlify('fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b3731622eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640')),[](#l3.17)
(b'pleaseletmein', b'SodiumChloride', 16384, 8, 1, unhexlify('7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887')),[](#l3.18)
- ] + 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')[](#l3.29)
- def test_scrypt(self):
for password, salt, n, r, p, expected in self.scrypt_test_vectors:[](#l3.31)
result = hashlib.scrypt(password, salt=salt, n=n, r=r, p=p)[](#l3.32)
self.assertEqual(result, expected)[](#l3.33)
# this values should work[](#l3.35)
hashlib.scrypt(b'password', salt=b'salt', n=2, r=8, p=1)[](#l3.36)
# password and salt must be bytes-like[](#l3.37)
with self.assertRaises(TypeError):[](#l3.38)
hashlib.scrypt('password', salt=b'salt', n=2, r=8, p=1)[](#l3.39)
with self.assertRaises(TypeError):[](#l3.40)
hashlib.scrypt(b'password', salt='salt', n=2, r=8, p=1)[](#l3.41)
# require keyword args[](#l3.42)
with self.assertRaises(TypeError):[](#l3.43)
hashlib.scrypt(b'password')[](#l3.44)
with self.assertRaises(TypeError):[](#l3.45)
hashlib.scrypt(b'password', b'salt')[](#l3.46)
with self.assertRaises(TypeError):[](#l3.47)
hashlib.scrypt(b'password', 2, 8, 1, salt=b'salt')[](#l3.48)
for n in [-1, 0, 1, None]:[](#l3.49)
with self.assertRaises((ValueError, OverflowError, TypeError)):[](#l3.50)
hashlib.scrypt(b'password', salt=b'salt', n=n, r=8, p=1)[](#l3.51)
for r in [-1, 0, None]:[](#l3.52)
with self.assertRaises((ValueError, OverflowError, TypeError)):[](#l3.53)
hashlib.scrypt(b'password', salt=b'salt', n=2, r=r, p=1)[](#l3.54)
for p in [-1, 0, None]:[](#l3.55)
with self.assertRaises((ValueError, OverflowError, TypeError)):[](#l3.56)
hashlib.scrypt(b'password', salt=b'salt', n=2, r=8, p=p)[](#l3.57)
for maxmem in [-1, None]:[](#l3.58)
with self.assertRaises((ValueError, OverflowError, TypeError)):[](#l3.59)
hashlib.scrypt(b'password', salt=b'salt', n=2, r=8, p=1,[](#l3.60)
maxmem=maxmem)[](#l3.61)
for dklen in [-1, None]:[](#l3.62)
with self.assertRaises((ValueError, OverflowError, TypeError)):[](#l3.63)
hashlib.scrypt(b'password', salt=b'salt', n=2, r=8, p=1,[](#l3.64)
dklen=dklen)[](#l3.65)
+ + if name == "main": unittest.main()
--- a/Misc/NEWS +++ b/Misc/NEWS @@ -85,6 +85,9 @@ Core and Builtins Library ------- +- Issue #27928: Add scrypt (password-based key derivation function) to
- Issue #27850: Remove 3DES from ssl module's default cipher list to counter measure sweet32 attack (CVE-2016-2183).
--- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -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 ar #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,[](#l5.41)
long maxmem, long dklen)[](#l5.42)
+/[clinic end generated code: output=14849e2aa2b7b46c input=48a7d63bf3f75c42]/ +{
- if (password->len > INT_MAX) {
PyErr_SetString(PyExc_OverflowError,[](#l5.51)
"password is too long.");[](#l5.52)
return NULL;[](#l5.53)
- }
- if (salt->buf == NULL) {
PyErr_SetString(PyExc_TypeError,[](#l5.57)
"salt is required");[](#l5.58)
return NULL;[](#l5.59)
- }
- if (salt->len > INT_MAX) {
PyErr_SetString(PyExc_OverflowError,[](#l5.62)
"salt is too long.");[](#l5.63)
return NULL;[](#l5.64)
- }
- n = PyLong_AsUnsignedLong(n_obj);
- if (n == (unsigned long) -1 && PyErr_Occurred()) {
PyErr_SetString(PyExc_TypeError,[](#l5.69)
"n is required and must be an unsigned int");[](#l5.70)
return NULL;[](#l5.71)
- }
- if (n < 2 || n & (n - 1)) {
PyErr_SetString(PyExc_ValueError,[](#l5.74)
"n must be a power of 2.");[](#l5.75)
return NULL;[](#l5.76)
- }
- r = PyLong_AsUnsignedLong(r_obj);
- if (r == (unsigned long) -1 && PyErr_Occurred()) {
PyErr_SetString(PyExc_TypeError,[](#l5.81)
"r is required and must be an unsigned int");[](#l5.82)
return NULL;[](#l5.83)
- }
- p = PyLong_AsUnsignedLong(p_obj);
- if (p == (unsigned long) -1 && PyErr_Occurred()) {
PyErr_SetString(PyExc_TypeError,[](#l5.88)
"p is required and must be an unsigned int");[](#l5.89)
return NULL;[](#l5.90)
- }
- if (maxmem < 0 || maxmem > INT_MAX) {
/* OpenSSL 1.1.0 restricts maxmem to 32MB. It may change in the[](#l5.94)
future. The maxmem constant is private to OpenSSL. */[](#l5.95)
PyErr_Format(PyExc_ValueError,[](#l5.96)
"maxmem must be positive and smaller than %d",[](#l5.97)
INT_MAX);[](#l5.98)
return NULL;[](#l5.99)
- }
- if (dklen < 1 || dklen > INT_MAX) {
PyErr_Format(PyExc_ValueError,[](#l5.103)
"dklen must be greater than 0 and smaller than %d",[](#l5.104)
INT_MAX);[](#l5.105)
return NULL;[](#l5.106)
- }
- /* 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 */[](#l5.112)
PyErr_SetString(PyExc_ValueError,[](#l5.113)
"Invalid paramemter combination for n, r, p, maxmem.");[](#l5.114)
return NULL;[](#l5.115)
- } +
- key_obj = PyBytes_FromStringAndSize(NULL, dklen);
- if (key_obj == NULL) {
return NULL;[](#l5.120)
- }
- key = PyBytes_AS_STRING(key_obj);
- Py_BEGIN_ALLOW_THREADS
- retval = EVP_PBE_scrypt(
(const char*)password->buf, (size_t)password->len,[](#l5.126)
(const unsigned char *)salt->buf, (size_t)salt->len,[](#l5.127)
n, r, p, maxmem,[](#l5.128)
(unsigned char *)key, (size_t)dklen[](#l5.129)
- );
- Py_END_ALLOW_THREADS
- if (!retval) {
Py_CLEAR(key_obj);[](#l5.134)
_setException(PyExc_ValueError);[](#l5.135)
return NULL;[](#l5.136)
- }
- 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),
new file mode 100644 --- /dev/null +++ b/Modules/clinic/hashopenssl.c.h @@ -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 [](#l6.18)
+ +static PyObject * +_hashlib_scrypt_impl(PyObject *module, Py_buffer *password, Py_buffer *salt,
PyObject *n_obj, PyObject *r_obj, PyObject *p_obj,[](#l6.23)
long maxmem, long dklen);[](#l6.24)
+ +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)) {[](#l6.41)
goto exit;[](#l6.42)
- }
- return_value = _hashlib_scrypt_impl(module, &password, &salt, n_obj, r_obj, p_obj, maxmem, dklen);
- /* Cleanup for password */
- if (password.obj) {
PyBuffer_Release(&password);[](#l6.49)
- }
- /* Cleanup for salt */
- if (salt.obj) {
PyBuffer_Release(&salt);[](#l6.53)
- }
+} + +#endif /* (OPENSSL_VERSION_NUMBER > 0x10100000L && !defined(OPENSSL_NO_SCRYPT) && !defined(LIBRESSL_VERSION_NUMBER)) */ + +#ifndef _HASHLIB_SCRYPT_METHODDEF
+#endif /* !defined(_HASHLIB_SCRYPT_METHODDEF) / +/[clinic end generated code: output=8c5386789f77430a input=a9049054013a1b77]*/