diff options
-rw-r--r-- | CHANGELOG.rst | 8 | ||||
-rw-r--r-- | cryptography/__about__.py | 2 | ||||
-rw-r--r-- | cryptography/hazmat/backends/commoncrypto/backend.py | 6 | ||||
-rw-r--r-- | cryptography/hazmat/backends/commoncrypto/ciphers.py | 16 | ||||
-rw-r--r-- | cryptography/hazmat/bindings/commoncrypto/seckey.py | 1 | ||||
-rw-r--r-- | cryptography/hazmat/primitives/ciphers/modes.py | 9 | ||||
-rw-r--r-- | cryptography/hazmat/primitives/constant_time.py | 16 | ||||
-rw-r--r-- | docs/fernet.rst | 2 | ||||
-rw-r--r-- | docs/hazmat/primitives/asymmetric/ec.rst | 91 | ||||
-rw-r--r-- | docs/hazmat/primitives/symmetric-encryption.rst | 23 | ||||
-rw-r--r-- | docs/spelling_wordlist.txt | 2 | ||||
-rw-r--r-- | tasks.py | 1 | ||||
-rw-r--r-- | tests/hazmat/backends/test_commoncrypto.py | 6 | ||||
-rw-r--r-- | tests/hazmat/primitives/test_aes.py | 4 | ||||
-rw-r--r-- | tests/hazmat/primitives/utils.py | 13 |
15 files changed, 126 insertions, 74 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e057b636..a49f3f46 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,11 @@ Changelog .. note:: This version is not yet released and is under active development. +* **BACKWARDS INCOMPATIBLE:** + :class:`~cryptography.hazmat.primitives.ciphers.modes.GCM` no longer allows + truncation of tags by default. Previous versions of ``cryptography`` allowed + tags to be truncated by default, applications wishing to preserve this + behavior (not recommended) can pass the ``min_tag_length`` argument. * Added :class:`~cryptography.hazmat.primitives.kdf.hkdf.HKDFExpand`. * Added :class:`~cryptography.hazmat.primitives.ciphers.modes.CFB8` support for :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES` and @@ -18,7 +23,8 @@ Changelog and :class:`~cryptography.hazmat.backends.interfaces.TraditionalOpenSSLSerializationBackend` support to the :doc:`/hazmat/backends/openssl`. -* Added :class:`~cryptography.hazmat.backends.interfaces.EllipticCurveBackend`. +* Added :doc:`/hazmat/primitives/asymmetric/ec` and + :class:`~cryptography.hazmat.backends.interfaces.EllipticCurveBackend`. * Added :class:`~cryptography.hazmat.primitives.ciphers.modes.ECB` support for :class:`~cryptography.hazmat.primitives.ciphers.algorithms.TripleDES` on :doc:`/hazmat/backends/commoncrypto` and :doc:`/hazmat/backends/openssl`. diff --git a/cryptography/__about__.py b/cryptography/__about__.py index ee53902b..ccbcdfe8 100644 --- a/cryptography/__about__.py +++ b/cryptography/__about__.py @@ -28,4 +28,4 @@ __author__ = "The cryptography developers" __email__ = "cryptography-dev@python.org" __license__ = "Apache License, Version 2.0" -__copyright__ = "Copyright 2013-2014 %s" % __author__ +__copyright__ = "Copyright 2013-2014 {0}".format(__author__) diff --git a/cryptography/hazmat/backends/commoncrypto/backend.py b/cryptography/hazmat/backends/commoncrypto/backend.py index f78370c4..7bab979f 100644 --- a/cryptography/hazmat/backends/commoncrypto/backend.py +++ b/cryptography/hazmat/backends/commoncrypto/backend.py @@ -149,7 +149,7 @@ class Backend(object): buf, length ) - self._check_response(res) + self._check_cipher_response(res) return self._ffi.buffer(buf)[:] @@ -223,7 +223,7 @@ class Backend(object): self._lib.kCCModeRC4 ) - def _check_response(self, response): + def _check_cipher_response(self, response): if response == self._lib.kCCSuccess: return elif response == self._lib.kCCAlignmentError: @@ -246,7 +246,7 @@ class Backend(object): """ if ctx[0] != self._ffi.NULL: res = self._lib.CCCryptorRelease(ctx[0]) - self._check_response(res) + self._check_cipher_response(res) ctx[0] = self._ffi.NULL diff --git a/cryptography/hazmat/backends/commoncrypto/ciphers.py b/cryptography/hazmat/backends/commoncrypto/ciphers.py index 2820fd0e..525500c8 100644 --- a/cryptography/hazmat/backends/commoncrypto/ciphers.py +++ b/cryptography/hazmat/backends/commoncrypto/ciphers.py @@ -78,7 +78,7 @@ class _CipherContext(object): self._backend._lib.ccNoPadding, iv_nonce, cipher.key, len(cipher.key), self._backend._ffi.NULL, 0, 0, mode_option, ctx) - self._backend._check_response(res) + self._backend._check_cipher_response(res) self._ctx = ctx @@ -91,7 +91,7 @@ class _CipherContext(object): res = self._backend._lib.CCCryptorUpdate( self._ctx[0], data, len(data), buf, len(data) + self._byte_block_size - 1, outlen) - self._backend._check_response(res) + self._backend._check_cipher_response(res) return self._backend._ffi.buffer(buf)[:outlen[0]] def finalize(self): @@ -105,7 +105,7 @@ class _CipherContext(object): outlen = self._backend._ffi.new("size_t *") res = self._backend._lib.CCCryptorFinal( self._ctx[0], buf, len(buf), outlen) - self._backend._check_response(res) + self._backend._check_cipher_response(res) self._backend._release_cipher_ctx(self._ctx) return self._backend._ffi.buffer(buf)[:outlen[0]] @@ -143,14 +143,14 @@ class _GCMCipherContext(object): self._backend._ffi.NULL, cipher.key, len(cipher.key), self._backend._ffi.NULL, 0, 0, 0, self._ctx) - self._backend._check_response(res) + self._backend._check_cipher_response(res) res = self._backend._lib.CCCryptorGCMAddIV( self._ctx[0], mode.initialization_vector, len(mode.initialization_vector) ) - self._backend._check_response(res) + self._backend._check_cipher_response(res) def update(self, data): buf = self._backend._ffi.new("unsigned char[]", len(data)) @@ -160,7 +160,7 @@ class _GCMCipherContext(object): else: res = self._backend._lib.CCCryptorGCMDecrypt(*args) - self._backend._check_response(res) + self._backend._check_cipher_response(res) return self._backend._ffi.buffer(buf)[:] def finalize(self): @@ -170,7 +170,7 @@ class _GCMCipherContext(object): res = self._backend._lib.CCCryptorGCMFinal( self._ctx[0], tag_buf, tag_len ) - self._backend._check_response(res) + self._backend._check_cipher_response(res) self._backend._release_cipher_ctx(self._ctx) self._tag = self._backend._ffi.buffer(tag_buf)[:] if (self._operation == self._backend._lib.kCCDecrypt and @@ -184,7 +184,7 @@ class _GCMCipherContext(object): res = self._backend._lib.CCCryptorGCMAddAAD( self._ctx[0], data, len(data) ) - self._backend._check_response(res) + self._backend._check_cipher_response(res) @property def tag(self): diff --git a/cryptography/hazmat/bindings/commoncrypto/seckey.py b/cryptography/hazmat/bindings/commoncrypto/seckey.py index 38aaece8..5e4b6dac 100644 --- a/cryptography/hazmat/bindings/commoncrypto/seckey.py +++ b/cryptography/hazmat/bindings/commoncrypto/seckey.py @@ -23,6 +23,7 @@ typedef ... *SecKeyRef; FUNCTIONS = """ OSStatus SecKeyGeneratePair(CFDictionaryRef, SecKeyRef *, SecKeyRef *); +size_t SecKeyGetBlockSize(SecKeyRef); """ MACROS = """ diff --git a/cryptography/hazmat/primitives/ciphers/modes.py b/cryptography/hazmat/primitives/ciphers/modes.py index e70a9db5..509b4de2 100644 --- a/cryptography/hazmat/primitives/ciphers/modes.py +++ b/cryptography/hazmat/primitives/ciphers/modes.py @@ -97,13 +97,16 @@ class CTR(object): class GCM(object): name = "GCM" - def __init__(self, initialization_vector, tag=None): + def __init__(self, initialization_vector, tag=None, min_tag_length=16): # len(initialization_vector) must in [1, 2 ** 64), but it's impossible # to actually construct a bytes object that large, so we don't check # for it - if tag is not None and len(tag) < 4: + if min_tag_length < 4: + raise ValueError("min_tag_length must be >= 4") + if tag is not None and len(tag) < min_tag_length: raise ValueError( - "Authentication tag must be 4 bytes or longer." + "Authentication tag must be {0} bytes or longer.".format( + min_tag_length) ) self.initialization_vector = initialization_vector diff --git a/cryptography/hazmat/primitives/constant_time.py b/cryptography/hazmat/primitives/constant_time.py index 4547da13..9789851a 100644 --- a/cryptography/hazmat/primitives/constant_time.py +++ b/cryptography/hazmat/primitives/constant_time.py @@ -13,6 +13,7 @@ from __future__ import absolute_import, division, print_function +import hmac import sys import cffi @@ -53,9 +54,18 @@ _lib = _ffi.verify( ext_package="cryptography", ) +if hasattr(hmac, "compare_digest"): + def bytes_eq(a, b): + if not isinstance(a, bytes) or not isinstance(b, bytes): + raise TypeError("a and b must be bytes.") + + return hmac.compare_digest(a, b) -def bytes_eq(a, b): - if not isinstance(a, bytes) or not isinstance(b, bytes): +else: + def bytes_eq(a, b): + if not isinstance(a, bytes) or not isinstance(b, bytes): raise TypeError("a and b must be bytes.") - return _lib.Cryptography_constant_time_bytes_eq(a, len(a), b, len(b)) == 1 + return _lib.Cryptography_constant_time_bytes_eq( + a, len(a), b, len(b) + ) == 1 diff --git a/docs/fernet.rst b/docs/fernet.rst index 1c4918ad..4b713a54 100644 --- a/docs/fernet.rst +++ b/docs/fernet.rst @@ -83,7 +83,7 @@ Specifically it uses: * :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES` in :class:`~cryptography.hazmat.primitives.ciphers.modes.CBC` mode with a 128-bit key for encryption; using - :class:`~cryptography.hazmat.primitives.ciphers.PKCS7` padding. + :class:`~cryptography.hazmat.primitives.padding.PKCS7` padding. * :class:`~cryptography.hazmat.primitives.hmac.HMAC` using :class:`~cryptography.hazmat.primitives.hashes.SHA256` for authentication. * Initialization vectors are generated using ``os.urandom()``. diff --git a/docs/hazmat/primitives/asymmetric/ec.rst b/docs/hazmat/primitives/asymmetric/ec.rst index 4b3c460e..0e19bb2e 100644 --- a/docs/hazmat/primitives/asymmetric/ec.rst +++ b/docs/hazmat/primitives/asymmetric/ec.rst @@ -1,6 +1,6 @@ .. hazmat:: -Elliptic Curve Cryptography +Elliptic curve cryptography =========================== .. currentmodule:: cryptography.hazmat.primitives.asymmetric.ec @@ -13,7 +13,7 @@ Elliptic Curve Cryptography Generate a new private key on ``curve`` for use with ``backend``. :param backend: A - :class:`~cryptography.hazmat.primtives.interfaces.EllipticCurve` + :class:`~cryptography.hazmat.primitives.interfaces.EllipticCurve` provider. :param backend: A @@ -21,10 +21,38 @@ Elliptic Curve Cryptography provider. :returns: A new instance of a - :class:`~cryptography.hazmat.primtivies.interfaces.EllipticCurvePrivateKey` + :class:`~cryptography.hazmat.primitives.interfaces.EllipticCurvePrivateKey` provider. +Elliptic Curve Signature Algorithms +----------------------------------- + +.. class:: ECDSA(algorithm) + + .. versionadded:: 0.5 + + The ECDSA signature algorithm first standardized in NIST publication + `FIPS 186-3`_, and later in `FIPS 186-4`_. + + :param algorithm: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` + provider. + + .. doctest:: + + >>> from cryptography.hazmat.backends import default_backend + >>> from cryptography.hazmat.primitives import hashes + >>> from cryptography.hazmat.primitives.asymmetric import ec + >>> private_key = ec.generate_private_key( + ... ec.SECP384R1(), default_backend() + ... ) + >>> signer = private_key.signer(ec.ECDSA(hashes.SHA256())) + >>> signer.update(b"this is some data I'd like") + >>> signer.update(b" to sign") + >>> signature = signer.finalize() + + .. class:: EllipticCurvePrivateNumbers(private_value, public_numbers) .. versionadded:: 0.5 @@ -33,7 +61,7 @@ Elliptic Curve Cryptography .. attribute:: public_numbers - :type: :class:`~cryptography.hazmat.primitives.ec.EllipticCurvePublicNumbers` + :type: :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicNumbers` The :class:`EllipticCurvePublicNumbers` which makes up the EC public key associated with this EC private key. @@ -54,7 +82,7 @@ Elliptic Curve Cryptography provider. :returns: A new instance of a - :class:`~cryptography.hazmat.primtivies.interfaces.EllipticCurvePrivateKey` + :class:`~cryptography.hazmat.primitives.interfaces.EllipticCurvePrivateKey` provider. @@ -92,48 +120,35 @@ Elliptic Curve Cryptography provider. :returns: A new instance of a - :class:`~cryptography.hazmat.primtivies.interfaces.EllipticCurvePublicKey` + :class:`~cryptography.hazmat.primitives.interfaces.EllipticCurvePublicKey` provider. +Elliptic Curves +--------------- -Elliptic Curve Signature Algorithms ------------------------------------ - -.. class:: ECDSA(algorithm) - - .. versionadded:: 0.5 - - The ECDSA signature algorithm first standardized in NIST publication - `FIPS 186-3`_, and later in `FIPS 186-4`_. +Elliptic curves provide equivalent security at much smaller key sizes than +asymmetric cryptography systems such as RSA or DSA. For some operations they +can also provide higher performance at every security level. According to NIST +they can have as much as a `64x lower computational cost than DH`_. - :param algorithm: An instance of a - :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` - provider. +.. note:: + Curves with a size of `less than 224 bits`_ should not be used. You should + strongly consider using curves of at least 224 bits. - .. doctest:: +Generally the NIST prime field ("P") curves are significantly faster than the +other types suggested by NIST at both signing and verifying with ECDSA. - >>> from cryptography.hazmat.backends import default_backend - >>> from cryptography.hazmat.primitives import hashes - >>> from cryptography.hazmat.primitives.asymmetric import ec - >>> private_key = ec.generate_private_key( - ... ec.SECP384R1(), default_backend() - ... ) - >>> signer = private_key.signer(ec.ECDSA(hashes.SHA256())) - >>> signer.update(b"this is some data I'd like") - >>> signer.update(b" to sign") - >>> signature = signer.finalize() +Prime fields also `minimize the number of security concerns for elliptic-curve +cryptography`_. However there is `some concern`_ that both the prime field and +binary field ("B") NIST curves may have been weakened during their generation. -Elliptic Curves ---------------- +Currently `cryptography` only supports NIST curves, none of which are +considered "safe" by the `SafeCurves`_ project run by Daniel J. Bernstein and +Tanja Lange. All named curves are providers of :class:`~cryptography.hazmat.primtives.interfaces.EllipticCurve`. -There is `some concern`_ that the non-Koblitz NIST curves (identified by names -that start with "B" or "P") may have been intentionally weakened by their -generation process. - - .. class:: SECT571K1 .. versionadded:: 0.5 @@ -243,3 +258,7 @@ generation process. .. _`FIPS 186-3`: http://csrc.nist.gov/publications/fips/fips186-3/fips_186-3.pdf .. _`FIPS 186-4`: http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf .. _`some concern`: https://crypto.stackexchange.com/questions/10263/should-we-trust-the-nist-recommended-ecc-parameters +.. _`less than 224 bits`: http://www.ecrypt.eu.org/documents/D.SPA.20.pdf +.. _`64x lower computational cost than DH`: http://www.nsa.gov/business/programs/elliptic_curve.shtml +.. _`minimize the number of security concerns for elliptic-curve cryptography`: http://cr.yp.to/ecdh/curve25519-20060209.pdf +.. _`SafeCurves`: http://safecurves.cr.yp.to/ diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst index abc2b076..586285b7 100644 --- a/docs/hazmat/primitives/symmetric-encryption.rst +++ b/docs/hazmat/primitives/symmetric-encryption.rst @@ -288,7 +288,7 @@ Modes Must be the same number of bytes as the ``block_size`` of the cipher. Do not reuse an ``initialization_vector`` with a given ``key``. -.. class:: GCM(initialization_vector, tag=None) +.. class:: GCM(initialization_vector, tag=None, min_tag_length=16) .. danger:: @@ -318,13 +318,23 @@ Modes You can shorten a tag by truncating it to the desired length but this is **not recommended** as it lowers the security margins of the authentication (`NIST SP-800-38D`_ recommends 96-bits or greater). - If you must shorten the tag the minimum allowed length is 4 bytes - (32-bits). Applications **must** verify the tag is the expected length - to guarantee the expected security margin. + Applications wishing to allow truncation must pass the + ``min_tag_length`` parameter. + + .. versionchanged:: 0.5 + + The ``min_tag_length`` parameter was added in ``0.5``, previously + truncation down to ``4`` bytes was always allowed. :param bytes tag: The tag bytes to verify during decryption. When encrypting this must be ``None``. + :param bytes min_tag_length: The minimum length ``tag`` must be. By default + this is ``16``, meaning tag truncation is not allowed. Allowing tag + truncation is strongly discouraged for most applications. + + :raises ValueError: This is raised if ``len(tag) < min_tag_length``. + .. testcode:: import os @@ -356,11 +366,6 @@ Modes return (iv, ciphertext, encryptor.tag) def decrypt(key, associated_data, iv, ciphertext, tag): - if len(tag) != 16: - raise ValueError( - "tag must be 16 bytes -- truncation not supported" - ) - # Construct a Cipher object, with the key, iv, and additionally the # GCM tag used for authenticating the message. decryptor = Cipher( diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index dc123493..d90547a8 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -29,6 +29,7 @@ introspectability invariants iOS Koblitz +Lange metadata namespace namespaces @@ -39,6 +40,7 @@ preprocessors pseudorandom Schneier scrypt +Tanja testability Ubuntu unencrypted @@ -10,6 +10,7 @@ # implied. # See the License for the specific language governing permissions and # limitations under the License. + from __future__ import absolute_import, division, print_function import getpass diff --git a/tests/hazmat/backends/test_commoncrypto.py b/tests/hazmat/backends/test_commoncrypto.py index 7c703f67..e2c6f4a0 100644 --- a/tests/hazmat/backends/test_commoncrypto.py +++ b/tests/hazmat/backends/test_commoncrypto.py @@ -51,13 +51,13 @@ class TestCommonCrypto(object): from cryptography.hazmat.backends.commoncrypto.backend import backend with pytest.raises(ValueError): - backend._check_response(backend._lib.kCCAlignmentError) + backend._check_cipher_response(backend._lib.kCCAlignmentError) with pytest.raises(InternalError): - backend._check_response(backend._lib.kCCMemoryFailure) + backend._check_cipher_response(backend._lib.kCCMemoryFailure) with pytest.raises(InternalError): - backend._check_response(backend._lib.kCCDecodeError) + backend._check_cipher_response(backend._lib.kCCDecodeError) def test_nonexistent_aead_cipher(self): from cryptography.hazmat.backends.commoncrypto.backend import Backend diff --git a/tests/hazmat/primitives/test_aes.py b/tests/hazmat/primitives/test_aes.py index 173075d6..5bde7d3c 100644 --- a/tests/hazmat/primitives/test_aes.py +++ b/tests/hazmat/primitives/test_aes.py @@ -225,6 +225,6 @@ class TestAESModeGCM(object): "gcmEncryptExtIV192.rsp", "gcmEncryptExtIV256.rsp", ], - lambda key: algorithms.AES(key), - lambda iv, tag: modes.GCM(iv, tag), + algorithms.AES, + modes.GCM, ) diff --git a/tests/hazmat/primitives/utils.py b/tests/hazmat/primitives/utils.py index 49b73f01..6428b03e 100644 --- a/tests/hazmat/primitives/utils.py +++ b/tests/hazmat/primitives/utils.py @@ -90,7 +90,8 @@ def aead_test(backend, cipher_factory, mode_factory, params): cipher = Cipher( cipher_factory(binascii.unhexlify(params["key"])), mode_factory(binascii.unhexlify(params["iv"]), - binascii.unhexlify(params["tag"])), + binascii.unhexlify(params["tag"]), + len(binascii.unhexlify(params["tag"]))), backend ) decryptor = cipher.decryptor() @@ -108,12 +109,13 @@ def aead_test(backend, cipher_factory, mode_factory, params): encryptor.authenticate_additional_data(binascii.unhexlify(aad)) actual_ciphertext = encryptor.update(binascii.unhexlify(plaintext)) actual_ciphertext += encryptor.finalize() - tag_len = len(params["tag"]) - assert binascii.hexlify(encryptor.tag)[:tag_len] == params["tag"] + tag_len = len(binascii.unhexlify(params["tag"])) + assert binascii.hexlify(encryptor.tag[:tag_len]) == params["tag"] cipher = Cipher( cipher_factory(binascii.unhexlify(params["key"])), mode_factory(binascii.unhexlify(params["iv"]), - binascii.unhexlify(params["tag"])), + binascii.unhexlify(params["tag"]), + min_tag_length=tag_len), backend ) decryptor = cipher.decryptor() @@ -309,6 +311,9 @@ def aead_tag_exception_test(backend, cipher_factory, mode_factory): with pytest.raises(ValueError): mode_factory(binascii.unhexlify(b"0" * 24), b"000") + with pytest.raises(ValueError): + mode_factory(binascii.unhexlify(b"0" * 24), b"000000", 2) + cipher = Cipher( cipher_factory(binascii.unhexlify(b"0" * 32)), mode_factory(binascii.unhexlify(b"0" * 24), b"0" * 16), |