Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ Changelog
* Added
:attr:`~cryptography.x509.CertificateRevocationList.signature_algorithm_oid`
support to :class:`~cryptography.x509.CertificateRevocationList`.
* Added support for :class:`~cryptography.hazmat.primitives.kdf.scrypt.Scrypt`
when using OpenSSL 1.1.0.


1.5 - 2016-08-26
~~~~~~~~~~~~~~~~
Expand Down
30 changes: 30 additions & 0 deletions docs/hazmat/backends/interfaces.rst
Original file line number Diff line number Diff line change
Expand Up @@ -652,3 +652,33 @@ A specific ``backend`` may provide one or more of these interfaces.

:returns: ``True`` if the given values of ``p`` and ``g`` are supported
by this backend, otherwise ``False``.


.. class:: ScryptBackend

.. versionadded:: 1.6

A backend with methods for using Scrypt.

The following backends implement this interface:

* :doc:`/hazmat/backends/openssl`

.. method:: derive_scrypt(self, key_material, salt, length, n, r, p)

:param bytes key_material: The key material to use as a basis for
the derived key. This is typically a password.

:param bytes salt: A salt.

:param int length: The desired length of the derived key.

:param int n: CPU/Memory cost parameter. It must be larger than 1 and be a
power of 2.

:param int r: Block size parameter.

:param int p: Parallelization parameter.

:return bytes: Derived key.

5 changes: 5 additions & 0 deletions docs/hazmat/backends/openssl.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ greater.
* :class:`~cryptography.hazmat.backends.interfaces.PEMSerializationBackend`
* :class:`~cryptography.hazmat.backends.interfaces.X509Backend`

It also implements the following interface for OpenSSL versions ``1.1.0``
and above.

* :class:`~cryptography.hazmat.backends.interfaces.ScryptBackend`

It also exposes the following:

.. attribute:: name
Expand Down
107 changes: 107 additions & 0 deletions docs/hazmat/primitives/key-derivation-functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,111 @@ Different KDFs are suitable for different tasks such as:
The counter iteration variable will be concatenated after
the fixed input data.

.. currentmodule:: cryptography.hazmat.primitives.kdf.scrypt

.. class:: Scrypt(salt, length, n, r, p, backend)

.. versionadded:: 1.6

Scrypt is a KDF designed for password storage by Colin Percival to be
resistant against hardware-assisted attackers by having a tunable memory
cost. It is described in :rfc:`7914`.

This class conforms to the
:class:`~cryptography.hazmat.primitives.kdf.KeyDerivationFunction`
interface.

.. code-block:: python

>>> import os
>>> from cryptography.hazmat.primitives import hashes
>>> from cryptography.hazmat.primitives.kdf.scrypt import Scrypt
>>> from cryptography.hazmat.backends import default_backend
>>> backend = default_backend()
>>> salt = os.urandom(16)
>>> # derive
>>> kdf = Scrypt(
... salt=salt,
... length=64,
... n=1024,
... r=8,
... p=16,
... backend=backend
... )
>>> key = kdf.derive(b"my great password")
>>> # verify
>>> kdf = Scrypt(
... salt=salt,
... length=64,
... n=1024,
... r=8,
... p=16,
... backend=backend
... )
>>> kdf.verify(b"my great password", key)

:param bytes salt: A salt.
:param int length: The desired length of the derived key.
:param int n: CPU/Memory cost parameter. It must be larger than 1 and be a
power of 2.
:param int r: Block size parameter.
:param int p: Parallelization parameter.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These params need better descriptions, there isn't enough info here for someone to intelligently use them


The computational and memory cost of Scrypt can be adjusted by manipulating
the 3 parameters: n, r and p. In general, the memory cost of Scrypt is
affected by the values of both n and r while n also determines the number
of iterations performed. p increases the computational cost without
affecting memory usage. A more in-depth explanation of the 3 parameters can
be found `here`_.

:rfc:`7914` `recommends`_ values of r=8 and p=1 while scaling n to the
number appropriate for your system.

:param backend: An instance of
:class:`~cryptography.hazmat.backends.interfaces.ScryptBackend`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't documented, that's why the tests are failing.


:raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if the
provided ``backend`` does not implement
:class:`~cryptography.hazmat.backends.interfaces.ScryptBackend`

:raises TypeError: This exception is raised if ``salt`` is not ``bytes``.

.. method:: derive(key_material)

:param bytes key_material: The input key material.
:return bytes: the derived key.
:raises TypeError: This exception is raised if ``key_material`` is not
``bytes``.
:raises cryptography.exceptions.AlreadyFinalized: This is raised when
:meth:`derive` or
:meth:`verify` is
called more than
once.

This generates and returns a new key from the supplied password.

.. method:: verify(key_material, expected_key)

:param bytes key_material: The input key material. This is the same as
``key_material`` in :meth:`derive`.
:param bytes expected_key: The expected result of deriving a new key,
this is the same as the return value of
:meth:`derive`.
:raises cryptography.exceptions.InvalidKey: This is raised when the
derived key does not match
the expected key.
:raises cryptography.exceptions.AlreadyFinalized: This is raised when
:meth:`derive` or
:meth:`verify` is
called more than
once.

This checks whether deriving a new key from the supplied
``key_material`` generates the same key as the ``expected_key``, and
raises an exception if they do not match. This can be used for
checking whether the password a user provides matches the stored derived
key.

Interface
~~~~~~~~~

Expand Down Expand Up @@ -795,3 +900,5 @@ Interface
.. _`key stretching`: https://en.wikipedia.org/wiki/Key_stretching
.. _`HKDF`: https://en.wikipedia.org/wiki/HKDF
.. _`HKDF paper`: https://eprint.iacr.org/2010/264
.. _`here`: https://stackoverflow.com/a/30308723/1170681
.. _`recommends`: https://tools.ietf.org/html/rfc7914#section-2
2 changes: 2 additions & 0 deletions docs/spelling_wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ Nonces
nonces
online
paddings
Parallelization
pickleable
plaintext
pre
Expand All @@ -73,6 +74,7 @@ SHA
Solaris
Tanja
testability
tunable
Ubuntu
unencrypted
unpadded
Expand Down
9 changes: 9 additions & 0 deletions src/cryptography/hazmat/backends/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,3 +357,12 @@ def dh_parameters_supported(self, p, g):
"""
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.
"""
8 changes: 7 additions & 1 deletion src/cryptography/hazmat/backends/multibackend.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from cryptography.hazmat.backends.interfaces import (
CMACBackend, CipherBackend, DERSerializationBackend, DSABackend,
EllipticCurveBackend, HMACBackend, HashBackend, PBKDF2HMACBackend,
PEMSerializationBackend, RSABackend, X509Backend
PEMSerializationBackend, RSABackend, ScryptBackend, X509Backend
)


Expand All @@ -24,6 +24,7 @@
@utils.register_interface(EllipticCurveBackend)
@utils.register_interface(PEMSerializationBackend)
@utils.register_interface(X509Backend)
@utils.register_interface(ScryptBackend)
class MultiBackend(object):
name = "multibackend"

Expand Down Expand Up @@ -409,3 +410,8 @@ def create_x509_revoked_certificate(self, builder):
"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.")
14 changes: 13 additions & 1 deletion src/cryptography/hazmat/backends/openssl/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import calendar
import collections
import itertools
import sys
from contextlib import contextmanager

import six
Expand All @@ -17,7 +18,7 @@
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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -1691,6 +1695,14 @@ def _openssh_public_key_bytes(self, key):
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):
Expand Down
49 changes: 49 additions & 0 deletions src/cryptography/hazmat/primitives/kdf/scrypt.py
Original file line number Diff line number Diff line change
@@ -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.")
16 changes: 15 additions & 1 deletion tests/hazmat/backends/test_multibackend.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
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.multibackend import MultiBackend
from cryptography.hazmat.primitives import cmac, hashes, hmac
Expand Down Expand Up @@ -231,6 +231,12 @@ def create_x509_revoked_certificate(self, builder):
pass


@utils.register_interface(ScryptBackend)
class DummyScryptBackend(object):
def derive_scrypt(self, key_material, salt, length, n, r, p):
pass


class TestMultiBackend(object):
def test_raises_error_with_empty_list(self):
with pytest.raises(ValueError):
Expand Down Expand Up @@ -558,3 +564,11 @@ def test_x509_backend(self):
)
with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509):
backend.create_x509_revoked_certificate(object())

def test_scrypt(self):
backend = MultiBackend([DummyScryptBackend()])
backend.derive_scrypt(b"key", b"salt", 1, 1, 1, 1)

backend = MultiBackend([DummyBackend])
with pytest.raises(UnsupportedAlgorithm):
backend.derive_scrypt(b"key", b"salt", 1, 1, 1, 1)
Loading