From d8a27df32b1ae35f165b00a644bd2432f6e44280 Mon Sep 17 00:00:00 2001 From: Terry Chia Date: Thu, 1 Sep 2016 23:39:57 +0800 Subject: Scrypt Implementation (#3117) * Scrypt implementation. * Docs stuff. * Make example just an example and not a doctest. * Add changelog entry. * Docs cleanup. * Add more tests. * Add multibackend tests. * PEP8. * Add docs about Scrypt parameters. * Docs cleanup. * Add AlreadyFinalized. --- src/cryptography/hazmat/backends/interfaces.py | 9 ++++ src/cryptography/hazmat/backends/multibackend.py | 8 +++- .../hazmat/backends/openssl/backend.py | 14 ++++++- src/cryptography/hazmat/primitives/kdf/scrypt.py | 49 ++++++++++++++++++++++ 4 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 src/cryptography/hazmat/primitives/kdf/scrypt.py (limited to 'src') diff --git a/src/cryptography/hazmat/backends/interfaces.py b/src/cryptography/hazmat/backends/interfaces.py index 5b9e6f38..9a1d704a 100644 --- a/src/cryptography/hazmat/backends/interfaces.py +++ b/src/cryptography/hazmat/backends/interfaces.py @@ -357,3 +357,12 @@ class DHBackend(object): """ Returns whether the backend supports DH with these parameter values. """ + + +@six.add_metaclass(abc.ABCMeta) +class ScryptBackend(object): + @abc.abstractmethod + def derive_scrypt(self, key_material, salt, length, n, r, p): + """ + Return bytes derived from provided Scrypt parameters. + """ diff --git a/src/cryptography/hazmat/backends/multibackend.py b/src/cryptography/hazmat/backends/multibackend.py index 48bc7d08..deca020e 100644 --- a/src/cryptography/hazmat/backends/multibackend.py +++ b/src/cryptography/hazmat/backends/multibackend.py @@ -9,7 +9,7 @@ from cryptography.exceptions import UnsupportedAlgorithm, _Reasons from cryptography.hazmat.backends.interfaces import ( CMACBackend, CipherBackend, DERSerializationBackend, DSABackend, EllipticCurveBackend, HMACBackend, HashBackend, PBKDF2HMACBackend, - PEMSerializationBackend, RSABackend, X509Backend + PEMSerializationBackend, RSABackend, ScryptBackend, X509Backend ) @@ -24,6 +24,7 @@ from cryptography.hazmat.backends.interfaces import ( @utils.register_interface(EllipticCurveBackend) @utils.register_interface(PEMSerializationBackend) @utils.register_interface(X509Backend) +@utils.register_interface(ScryptBackend) class MultiBackend(object): name = "multibackend" @@ -409,3 +410,8 @@ class MultiBackend(object): "This backend does not support X.509.", _Reasons.UNSUPPORTED_X509 ) + + def derive_scrypt(self, key_material, salt, length, n, r, p): + for b in self._filtered_backends(ScryptBackend): + return b.derive_scrypt(key_material, salt, length, n, r, p) + raise UnsupportedAlgorithm("This backend does not support scrypt.") diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 7d16e05e..955b1977 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -8,6 +8,7 @@ import base64 import calendar import collections import itertools +import sys from contextlib import contextmanager import six @@ -17,7 +18,7 @@ from cryptography.exceptions import UnsupportedAlgorithm, _Reasons from cryptography.hazmat.backends.interfaces import ( CMACBackend, CipherBackend, DERSerializationBackend, DSABackend, EllipticCurveBackend, HMACBackend, HashBackend, PBKDF2HMACBackend, - PEMSerializationBackend, RSABackend, X509Backend + PEMSerializationBackend, RSABackend, ScryptBackend, X509Backend ) from cryptography.hazmat.backends.openssl.ciphers import ( _AESCTRCipherContext, _CipherContext @@ -114,6 +115,9 @@ def _pem_password_cb(buf, size, writing, userdata_handle): @utils.register_interface(RSABackend) @utils.register_interface(PEMSerializationBackend) @utils.register_interface(X509Backend) +@utils.register_interface_if( + binding.Binding().lib.Cryptography_HAS_SCRYPT, ScryptBackend +) class Backend(object): """ OpenSSL API binding interfaces. @@ -1691,6 +1695,14 @@ class Backend(object): serialization._ssh_write_string(public_numbers.encode_point()) ) + def derive_scrypt(self, key_material, salt, length, n, r, p): + buf = self._ffi.new("unsigned char[]", length) + res = self._lib.EVP_PBE_scrypt(key_material, len(key_material), salt, + len(salt), n, r, p, sys.maxsize // 2, + buf, length) + self.openssl_assert(res == 1) + return self._ffi.buffer(buf)[:] + class GetCipherByName(object): def __init__(self, fmt): diff --git a/src/cryptography/hazmat/primitives/kdf/scrypt.py b/src/cryptography/hazmat/primitives/kdf/scrypt.py new file mode 100644 index 00000000..09181d97 --- /dev/null +++ b/src/cryptography/hazmat/primitives/kdf/scrypt.py @@ -0,0 +1,49 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from cryptography import utils +from cryptography.exceptions import ( + AlreadyFinalized, InvalidKey, UnsupportedAlgorithm, _Reasons +) +from cryptography.hazmat.backends.interfaces import ScryptBackend +from cryptography.hazmat.primitives import constant_time +from cryptography.hazmat.primitives.kdf import KeyDerivationFunction + + +@utils.register_interface(KeyDerivationFunction) +class Scrypt(object): + def __init__(self, salt, length, n, r, p, backend): + if not isinstance(backend, ScryptBackend): + raise UnsupportedAlgorithm( + "Backend object does not implement ScryptBackend.", + _Reasons.BACKEND_MISSING_INTERFACE + ) + + self._length = length + if not isinstance(salt, bytes): + raise TypeError("salt must be bytes.") + self._used = False + self._salt = salt + self._n = n + self._r = r + self._p = p + self._backend = backend + + def derive(self, key_material): + if self._used: + raise AlreadyFinalized("Scrypt instances can only be used once.") + self._used = True + + if not isinstance(key_material, bytes): + raise TypeError("key_material must be bytes.") + return self._backend.derive_scrypt( + key_material, self._salt, self._length, self._n, self._r, self._p + ) + + def verify(self, key_material, expected_key): + derived_key = self.derive(key_material) + if not constant_time.bytes_eq(derived_key, expected_key): + raise InvalidKey("Keys do not match.") -- cgit v1.2.3