diff options
-rw-r--r-- | .travis.yml | 4 | ||||
-rw-r--r-- | CHANGELOG.rst | 1 | ||||
-rw-r--r-- | docs/glossary.rst | 6 | ||||
-rw-r--r-- | docs/hazmat/primitives/symmetric-encryption.rst | 69 | ||||
-rw-r--r-- | docs/installation.rst | 2 | ||||
-rw-r--r-- | pyproject.toml | 2 | ||||
-rw-r--r-- | setup.py | 6 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/openssl/ciphers.py | 21 | ||||
-rw-r--r-- | src/cryptography/hazmat/primitives/ciphers/algorithms.py | 4 | ||||
-rw-r--r-- | src/cryptography/hazmat/primitives/ciphers/modes.py | 14 | ||||
-rw-r--r-- | src/cryptography/utils.py | 7 | ||||
-rw-r--r-- | tests/hazmat/primitives/test_aes.py | 42 | ||||
-rw-r--r-- | tests/hazmat/primitives/test_chacha20.py | 12 |
13 files changed, 141 insertions, 49 deletions
diff --git a/.travis.yml b/.travis.yml index 2a636ad1..b8ecb7d6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,9 +31,9 @@ matrix: env: TOXENV=py37 - python: 3.7 env: TOXENV=py37-idna - - python: pypy-5.3 + - python: pypy-5.4 env: TOXENV=pypy-nocoverage - # PyPy 5.3 isn't available for xenial + # PyPy 5.4 isn't available for xenial dist: trusty - python: pypy2.7-5.10.0 env: TOXENV=pypy-nocoverage diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 25c7c8c3..54004b48 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,6 +12,7 @@ Changelog version 2.1, but this version removes the default ``idna`` dependency as well. If you still need this deprecated path please install cryptography with the ``idna`` extra: ``pip install cryptography[idna]``. +* **BACKWARDS INCOMPATIBLE:** The minimum supported PyPy version is now 5.4. * Added support for :class:`~cryptography.hazmat.primitives.hashes.SHA512_224` and :class:`~cryptography.hazmat.primitives.hashes.SHA512_256` when using OpenSSL 1.1.1. diff --git a/docs/glossary.rst b/docs/glossary.rst index ce08dbaa..95b893c8 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -94,6 +94,11 @@ Glossary bit key, you can calculate the number of bytes by dividing by 8. 128 divided by 8 is 16, so a 128 bit key is a 16 byte key. + bytes-like + A bytes-like object contains binary data and supports the + `buffer protocol`_. This includes ``bytes``, ``bytearray``, and + ``memoryview`` objects. + U-label The presentational unicode form of an internationalized domain name. U-labels use unicode characters outside the ASCII range and @@ -101,3 +106,4 @@ Glossary .. _`hardware security module`: https://en.wikipedia.org/wiki/Hardware_security_module .. _`idna`: https://pypi.org/project/idna/ +.. _`buffer protocol`: https://docs.python.org/3/c-api/buffer.html diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst index 31f240c4..4924ddd6 100644 --- a/docs/hazmat/primitives/symmetric-encryption.rst +++ b/docs/hazmat/primitives/symmetric-encryption.rst @@ -92,8 +92,9 @@ Algorithms AES is both fast, and cryptographically strong. It is a good default choice for encryption. - :param bytes key: The secret key. This must be kept secret. Either ``128``, + :param key: The secret key. This must be kept secret. Either ``128``, ``192``, or ``256`` :term:`bits` long. + :type key: :term:`bytes-like` .. class:: Camellia(key) @@ -101,8 +102,9 @@ Algorithms It is considered to have comparable security and performance to AES but is not as widely studied or deployed. - :param bytes key: The secret key. This must be kept secret. Either ``128``, + :param key: The secret key. This must be kept secret. Either ``128``, ``192``, or ``256`` :term:`bits` long. + :type key: :term:`bytes-like` .. class:: ChaCha20(key) @@ -120,15 +122,17 @@ Algorithms ChaCha20 is a stream cipher used in several IETF protocols. It is standardized in :rfc:`7539`. - :param bytes key: The secret key. This must be kept secret. ``256`` + :param key: The secret key. This must be kept secret. ``256`` :term:`bits` (32 bytes) in length. + :type key: :term:`bytes-like` - :param bytes nonce: Should be unique, a :term:`nonce`. It is + :param nonce: Should be unique, a :term:`nonce`. It is critical to never reuse a ``nonce`` with a given key. Any reuse of a nonce with the same key compromises the security of every message encrypted with that key. The nonce does not need to be kept secret and may be included with the ciphertext. This must be ``128`` :term:`bits` in length. + :type nonce: :term:`bytes-like` .. note:: @@ -161,12 +165,13 @@ Algorithms Nonetheless, Triple DES is not recommended for new applications because it is incredibly slow; old applications should consider moving away from it. - :param bytes key: The secret key. This must be kept secret. Either ``64``, + :param key: The secret key. This must be kept secret. Either ``64``, ``128``, or ``192`` :term:`bits` long. DES only uses ``56``, ``112``, or ``168`` bits of the key as there is a parity byte in each component of the key. Some writing refers to there being up to three separate keys that are each ``56`` bits long, they can simply be concatenated to produce the full key. + :type key: :term:`bytes-like` .. class:: CAST5(key) @@ -177,8 +182,9 @@ Algorithms a variable key length cipher and supports keys from 40-128 :term:`bits` in length. - :param bytes key: The secret key, This must be kept secret. 40 to 128 + :param key: The secret key, This must be kept secret. 40 to 128 :term:`bits` in length in increments of 8 bits. + :type key: :term:`bytes-like` .. class:: SEED(key) @@ -188,8 +194,9 @@ Algorithms (KISA). It is defined in :rfc:`4269` and is used broadly throughout South Korean industry, but rarely found elsewhere. - :param bytes key: The secret key. This must be kept secret. ``128`` + :param key: The secret key. This must be kept secret. ``128`` :term:`bits` in length. + :type key: :term:`bytes-like` Weak ciphers ------------ @@ -206,8 +213,9 @@ Weak ciphers susceptible to attacks when using weak keys. The author has recommended that users of Blowfish move to newer algorithms such as :class:`AES`. - :param bytes key: The secret key. This must be kept secret. 32 to 448 + :param key: The secret key. This must be kept secret. 32 to 448 :term:`bits` in length in increments of 8 bits. + :type key: :term:`bytes-like` .. class:: ARC4(key) @@ -215,9 +223,10 @@ Weak ciphers initial stream output. Its use is strongly discouraged. ARC4 does not use mode constructions. - :param bytes key: The secret key. This must be kept secret. Either ``40``, + :param key: The secret key. This must be kept secret. Either ``40``, ``56``, ``64``, ``80``, ``128``, ``192``, or ``256`` :term:`bits` in length. + :type key: :term:`bytes-like` .. doctest:: @@ -238,8 +247,9 @@ Weak ciphers is susceptible to attacks when using weak keys. It is recommended that you do not use this cipher for new applications. - :param bytes key: The secret key. This must be kept secret. ``128`` + :param key: The secret key. This must be kept secret. ``128`` :term:`bits` in length. + :type key: :term:`bytes-like` .. _symmetric-encryption-modes: @@ -256,13 +266,14 @@ Modes **Padding is required when using this mode.** - :param bytes initialization_vector: Must be :doc:`random bytes + :param initialization_vector: Must be :doc:`random bytes </random-numbers>`. They do not need to be kept secret and they can be included in a transmitted message. Must be the same number of bytes as the ``block_size`` of the cipher. Each time something is encrypted a new ``initialization_vector`` should be generated. Do not reuse an ``initialization_vector`` with a given ``key``, and particularly do not use a constant ``initialization_vector``. + :type initialization_vector: :term:`bytes-like` A good construction looks like: @@ -295,12 +306,13 @@ Modes **This mode does not require padding.** - :param bytes nonce: Should be unique, a :term:`nonce`. It is + :param nonce: Should be unique, a :term:`nonce`. It is critical to never reuse a ``nonce`` with a given key. Any reuse of a nonce with the same key compromises the security of every message encrypted with that key. Must be the same number of bytes as the ``block_size`` of the cipher with a given key. The nonce does not need to be kept secret and may be included with the ciphertext. + :type nonce: :term:`bytes-like` .. class:: OFB(initialization_vector) @@ -309,11 +321,12 @@ Modes **This mode does not require padding.** - :param bytes initialization_vector: Must be :doc:`random bytes + :param initialization_vector: Must be :doc:`random bytes </random-numbers>`. They do not need to be kept secret and they can be included in a transmitted message. Must be the same number of bytes as the ``block_size`` of the cipher. Do not reuse an ``initialization_vector`` with a given ``key``. + :type initialization_vector: :term:`bytes-like` .. class:: CFB(initialization_vector) @@ -322,11 +335,12 @@ Modes **This mode does not require padding.** - :param bytes initialization_vector: Must be :doc:`random bytes + :param initialization_vector: Must be :doc:`random bytes </random-numbers>`. They do not need to be kept secret and they can be included in a transmitted message. Must be the same number of bytes as the ``block_size`` of the cipher. Do not reuse an ``initialization_vector`` with a given ``key``. + :type initialization_vector: :term:`bytes-like` .. class:: CFB8(initialization_vector) @@ -336,11 +350,12 @@ Modes **This mode does not require padding.** - :param bytes initialization_vector: Must be :doc:`random bytes + :param initialization_vector: Must be :doc:`random bytes </random-numbers>`. They do not need to be kept secret and they can be included in a transmitted message. Must be the same number of bytes as the ``block_size`` of the cipher. Do not reuse an ``initialization_vector`` with a given ``key``. + :type initialization_vector: :term:`bytes-like` .. class:: GCM(initialization_vector, tag=None, min_tag_length=16) @@ -368,12 +383,13 @@ Modes **This mode does not require padding.** - :param bytes initialization_vector: Must be unique, a :term:`nonce`. + :param initialization_vector: Must be unique, a :term:`nonce`. They do not need to be kept secret and they can be included in a transmitted message. NIST `recommends a 96-bit IV length`_ for performance critical situations but it can be up to 2\ :sup:`64` - 1 :term:`bits`. Do not reuse an ``initialization_vector`` with a given ``key``. + :type initialization_vector: :term:`bytes-like` .. note:: @@ -495,11 +511,11 @@ Modes **This mode does not require padding.** - :param bytes tweak: The tweak is a 16 byte value typically derived from + :param tweak: The tweak is a 16 byte value typically derived from something like the disk sector number. A given ``(tweak, key)`` pair should not be reused, although doing so is less catastrophic than in CTR mode. - + :type tweak: :term:`bytes-like` Insecure modes -------------- @@ -544,7 +560,8 @@ Interfaces .. method:: update(data) - :param bytes data: The data you wish to pass into the context. + :param data: The data you wish to pass into the context. + :type data: :term:`bytes-like` :return bytes: Returns the data that was encrypted or decrypted. :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize` @@ -567,7 +584,8 @@ Interfaces requirement and you will be making many small calls to ``update_into``. - :param bytes data: The data you wish to pass into the context. + :param data: The data you wish to pass into the context. + :type data: :term:`bytes-like` :param buf: A writable Python buffer that the data will be written into. This buffer should be ``len(data) + n - 1`` bytes where ``n`` is the block size (in bytes) of the cipher and mode combination. @@ -629,7 +647,8 @@ Interfaces .. method:: authenticate_additional_data(data) - :param bytes data: Any data you wish to authenticate but not encrypt. + :param data: Any data you wish to authenticate but not encrypt. + :type data: :term:`bytes-like` :raises: :class:`~cryptography.exceptions.AlreadyFinalized` .. class:: AEADEncryptionContext @@ -747,7 +766,7 @@ Interfaces used by the symmetric cipher modes described in .. attribute:: initialization_vector - :type: bytes + :type: :term:`bytes-like` Exact requirements of the initialization are described by the documentation of individual modes. @@ -759,7 +778,7 @@ Interfaces used by the symmetric cipher modes described in .. attribute:: nonce - :type: bytes + :type: :term:`bytes-like` Exact requirements of the nonce are described by the documentation of individual modes. @@ -771,7 +790,7 @@ Interfaces used by the symmetric cipher modes described in .. attribute:: tag - :type: bytes + :type: :term:`bytes-like` Exact requirements of the tag are described by the documentation of individual modes. @@ -785,7 +804,7 @@ Interfaces used by the symmetric cipher modes described in .. attribute:: tweak - :type: bytes + :type: :term:`bytes-like` Exact requirements of the tweak are described by the documentation of individual modes. diff --git a/docs/installation.rst b/docs/installation.rst index 2d9db667..5b2854d9 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -11,7 +11,7 @@ Supported platforms ------------------- Currently we test ``cryptography`` on Python 2.7, 3.4+, and -PyPy 5.3+ on these operating systems. +PyPy 5.4+ on these operating systems. * x86-64 CentOS 7.x * macOS 10.12 Sierra, 10.11 El Capitan diff --git a/pyproject.toml b/pyproject.toml index 461675f0..7d64f993 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,5 +3,5 @@ requires = [ "setuptools>=18.5", "wheel", - "cffi>=1.7,!=1.11.3; python_implementation != 'PyPy'", + "cffi>=1.8,!=1.11.3; python_implementation != 'PyPy'", ] @@ -44,12 +44,12 @@ with open(os.path.join(src_dir, "cryptography", "__about__.py")) as f: VECTORS_DEPENDENCY = "cryptography_vectors=={0}".format(about['__version__']) # `setup_requirements` must be kept in sync with `pyproject.toml` -setup_requirements = ["cffi>=1.7,!=1.11.3"] +setup_requirements = ["cffi>=1.8,!=1.11.3"] if platform.python_implementation() == "PyPy": - if sys.pypy_version_info < (5, 3): + if sys.pypy_version_info < (5, 4): raise RuntimeError( - "cryptography is not compatible with PyPy < 5.3. Please upgrade " + "cryptography is not compatible with PyPy < 5.4. Please upgrade " "PyPy to use this library." ) diff --git a/src/cryptography/hazmat/backends/openssl/ciphers.py b/src/cryptography/hazmat/backends/openssl/ciphers.py index e0ee06ee..fe5715b2 100644 --- a/src/cryptography/hazmat/backends/openssl/ciphers.py +++ b/src/cryptography/hazmat/backends/openssl/ciphers.py @@ -56,13 +56,15 @@ class _CipherContext(object): ) if isinstance(mode, modes.ModeWithInitializationVector): - iv_nonce = mode.initialization_vector + iv_nonce = self._backend._ffi.from_buffer( + mode.initialization_vector + ) elif isinstance(mode, modes.ModeWithTweak): - iv_nonce = mode.tweak + iv_nonce = self._backend._ffi.from_buffer(mode.tweak) elif isinstance(mode, modes.ModeWithNonce): - iv_nonce = mode.nonce + iv_nonce = self._backend._ffi.from_buffer(mode.nonce) elif isinstance(cipher, modes.ModeWithNonce): - iv_nonce = cipher.nonce + iv_nonce = self._backend._ffi.from_buffer(cipher.nonce) else: iv_nonce = self._backend._ffi.NULL # begin init with cipher and operation type @@ -105,7 +107,7 @@ class _CipherContext(object): ctx, self._backend._ffi.NULL, self._backend._ffi.NULL, - cipher.key, + self._backend._ffi.from_buffer(cipher.key), iv_nonce, operation ) @@ -131,8 +133,10 @@ class _CipherContext(object): "unsigned char *", self._backend._ffi.from_buffer(buf) ) outlen = self._backend._ffi.new("int *") - res = self._backend._lib.EVP_CipherUpdate(self._ctx, buf, outlen, - data, len(data)) + res = self._backend._lib.EVP_CipherUpdate( + self._ctx, buf, outlen, + self._backend._ffi.from_buffer(data), len(data) + ) self._backend.openssl_assert(res != 0) return outlen[0] @@ -215,7 +219,8 @@ class _CipherContext(object): def authenticate_additional_data(self, data): outlen = self._backend._ffi.new("int *") res = self._backend._lib.EVP_CipherUpdate( - self._ctx, self._backend._ffi.NULL, outlen, data, len(data) + self._ctx, self._backend._ffi.NULL, outlen, + self._backend._ffi.from_buffer(data), len(data) ) self._backend.openssl_assert(res != 0) diff --git a/src/cryptography/hazmat/primitives/ciphers/algorithms.py b/src/cryptography/hazmat/primitives/ciphers/algorithms.py index 21d9ecf0..1f49fd9d 100644 --- a/src/cryptography/hazmat/primitives/ciphers/algorithms.py +++ b/src/cryptography/hazmat/primitives/ciphers/algorithms.py @@ -13,7 +13,7 @@ from cryptography.hazmat.primitives.ciphers.modes import ModeWithNonce def _verify_key_size(algorithm, key): # Verify that the key is instance of bytes - utils._check_bytes("key", key) + utils._check_byteslike("key", key) # Verify that the key size matches the expected key size if len(key) * 8 not in algorithm.key_sizes: @@ -153,7 +153,7 @@ class ChaCha20(object): def __init__(self, key, nonce): self.key = _verify_key_size(self, key) - utils._check_bytes("nonce", nonce) + utils._check_byteslike("nonce", nonce) if len(nonce) != 16: raise ValueError("nonce must be 128-bits (16 bytes)") diff --git a/src/cryptography/hazmat/primitives/ciphers/modes.py b/src/cryptography/hazmat/primitives/ciphers/modes.py index d2444580..ad91a6e1 100644 --- a/src/cryptography/hazmat/primitives/ciphers/modes.py +++ b/src/cryptography/hazmat/primitives/ciphers/modes.py @@ -88,7 +88,7 @@ class CBC(object): name = "CBC" def __init__(self, initialization_vector): - utils._check_bytes("initialization_vector", initialization_vector) + utils._check_byteslike("initialization_vector", initialization_vector) self._initialization_vector = initialization_vector initialization_vector = utils.read_only_property("_initialization_vector") @@ -101,7 +101,7 @@ class XTS(object): name = "XTS" def __init__(self, tweak): - utils._check_bytes("tweak", tweak) + utils._check_byteslike("tweak", tweak) if len(tweak) != 16: raise ValueError("tweak must be 128-bits (16 bytes)") @@ -131,7 +131,7 @@ class OFB(object): name = "OFB" def __init__(self, initialization_vector): - utils._check_bytes("initialization_vector", initialization_vector) + utils._check_byteslike("initialization_vector", initialization_vector) self._initialization_vector = initialization_vector initialization_vector = utils.read_only_property("_initialization_vector") @@ -144,7 +144,7 @@ class CFB(object): name = "CFB" def __init__(self, initialization_vector): - utils._check_bytes("initialization_vector", initialization_vector) + utils._check_byteslike("initialization_vector", initialization_vector) self._initialization_vector = initialization_vector initialization_vector = utils.read_only_property("_initialization_vector") @@ -157,7 +157,7 @@ class CFB8(object): name = "CFB8" def __init__(self, initialization_vector): - utils._check_bytes("initialization_vector", initialization_vector) + utils._check_byteslike("initialization_vector", initialization_vector) self._initialization_vector = initialization_vector initialization_vector = utils.read_only_property("_initialization_vector") @@ -170,7 +170,7 @@ class CTR(object): name = "CTR" def __init__(self, nonce): - utils._check_bytes("nonce", nonce) + utils._check_byteslike("nonce", nonce) self._nonce = nonce nonce = utils.read_only_property("_nonce") @@ -195,7 +195,7 @@ class GCM(object): # 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 - utils._check_bytes("initialization_vector", initialization_vector) + utils._check_byteslike("initialization_vector", initialization_vector) if len(initialization_vector) == 0: raise ValueError("initialization_vector must be at least 1 byte") self._initialization_vector = initialization_vector diff --git a/src/cryptography/utils.py b/src/cryptography/utils.py index 3d45a771..65a4ee71 100644 --- a/src/cryptography/utils.py +++ b/src/cryptography/utils.py @@ -30,6 +30,13 @@ def _check_bytes(name, value): raise TypeError("{0} must be bytes".format(name)) +def _check_byteslike(name, value): + try: + memoryview(value) + except TypeError: + raise TypeError("{0} must be bytes-like".format(name)) + + def read_only_property(name): return property(lambda self: getattr(self, name)) diff --git a/tests/hazmat/primitives/test_aes.py b/tests/hazmat/primitives/test_aes.py index 4ceccf15..90a6b3b0 100644 --- a/tests/hazmat/primitives/test_aes.py +++ b/tests/hazmat/primitives/test_aes.py @@ -455,3 +455,45 @@ class TestAESModeGCM(object): ).decryptor() with pytest.raises(ValueError): decryptor.finalize_with_tag(b"tagtooshort") + + def test_buffer_protocol(self, backend): + data = bytearray(b"helloworld") + enc = base.Cipher( + algorithms.AES(bytearray(b"\x00" * 16)), + modes.GCM(bytearray(b"\x00" * 12)), + backend + ).encryptor() + enc.authenticate_additional_data(bytearray(b"foo")) + ct = enc.update(data) + enc.finalize() + dec = base.Cipher( + algorithms.AES(bytearray(b"\x00" * 16)), + modes.GCM(bytearray(b"\x00" * 12), enc.tag), + backend + ).decryptor() + dec.authenticate_additional_data(bytearray(b"foo")) + pt = dec.update(ct) + dec.finalize() + assert pt == data + + +@pytest.mark.parametrize( + "mode", + [ + modes.CBC(bytearray(b"\x00" * 16)), + modes.CTR(bytearray(b"\x00" * 16)), + modes.OFB(bytearray(b"\x00" * 16)), + modes.CFB(bytearray(b"\x00" * 16)), + modes.CFB8(bytearray(b"\x00" * 16)), + modes.XTS(bytearray(b"\x00" * 16)), + ] +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +def test_buffer_protocol_alternate_modes(mode, backend): + data = bytearray(b"sixteen_byte_msg") + cipher = base.Cipher( + algorithms.AES(bytearray(b"\x00" * 32)), mode, backend + ) + enc = cipher.encryptor() + ct = enc.update(data) + enc.finalize() + dec = cipher.decryptor() + pt = dec.update(ct) + dec.finalize() + assert pt == data diff --git a/tests/hazmat/primitives/test_chacha20.py b/tests/hazmat/primitives/test_chacha20.py index 33730d91..7c475c0f 100644 --- a/tests/hazmat/primitives/test_chacha20.py +++ b/tests/hazmat/primitives/test_chacha20.py @@ -44,6 +44,18 @@ class TestChaCha20(object): computed_ct = encryptor.update(pt) + encryptor.finalize() assert binascii.hexlify(computed_ct) == vector["ciphertext"] + def test_buffer_protocol(self, backend): + key = bytearray(os.urandom(32)) + nonce = bytearray(os.urandom(16)) + cipher = Cipher( + algorithms.ChaCha20(key, nonce), None, backend + ) + enc = cipher.encryptor() + ct = enc.update(bytearray(b"hello")) + enc.finalize() + dec = cipher.decryptor() + pt = dec.update(ct) + dec.finalize() + assert pt == b"hello" + def test_key_size(self): chacha = algorithms.ChaCha20(b"0" * 32, b"0" * 16) assert chacha.key_size == 256 |