aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.rst5
-rw-r--r--docs/hazmat/primitives/symmetric-encryption.rst49
-rw-r--r--src/cryptography/hazmat/backends/openssl/backend.py7
-rw-r--r--src/cryptography/hazmat/backends/openssl/ciphers.py2
-rw-r--r--src/cryptography/hazmat/primitives/ciphers/algorithms.py24
-rw-r--r--tests/hazmat/primitives/test_chacha20.py60
6 files changed, 146 insertions, 1 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 6b4d5387..91e450b8 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -33,6 +33,11 @@ Changelog
:attr:`~cryptography.x509.RFC822Name.value` attribute was deprecated, users
should use :attr:`~cryptography.x509.RFC822Name.bytes_value` to access the
raw value.
+* Added support for
+ :class:`~cryptography.hazmat.primitives.ciphers.algorithms.ChaCha20`. In
+ most cases users should choose
+ :class:`~cryptography.hazmat.primitives.ciphers.aead.ChaCha20Poly1305`
+ rather than using this unauthenticated form.
* Added :meth:`~cryptography.x509.CertificateRevocationList.is_signature_valid`
to :class:`~cryptography.x509.CertificateRevocationList`.
* Support :class:`~cryptography.hazmat.primitives.hashes.BLAKE2b` and
diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst
index d6479a44..10a349b1 100644
--- a/docs/hazmat/primitives/symmetric-encryption.rst
+++ b/docs/hazmat/primitives/symmetric-encryption.rst
@@ -104,6 +104,55 @@ Algorithms
:param bytes key: The secret key. This must be kept secret. Either ``128``,
``192``, or ``256`` bits long.
+.. class:: ChaCha20(key)
+
+ .. versionadded:: 2.1
+
+ .. note::
+
+ In most cases users should use
+ :class:`~cryptography.hazmat.primitives.ciphers.aead.ChaCha20Poly1305`
+ instead of this class. `ChaCha20` alone does not provide integrity
+ so it must be combined with a MAC to be secure.
+ :class:`~cryptography.hazmat.primitives.ciphers.aead.ChaCha20Poly1305`
+ does this for you.
+
+ 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`` bits
+ (32 bytes) in length.
+
+ :param bytes 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`` bits in
+ length.
+
+ .. note::
+
+ In :rfc:`7539` the nonce is defined as a 96-bit value that is later
+ concatenated with a block counter (encoded as a 32-bit
+ little-endian). If you have a separate nonce and block counter
+ you will need to concatenate it yourself before passing it. For
+ example if you have an initial block counter of 2 and a 96-bit
+ nonce the concatenated nonce would be
+ ``struct.pack("<i", 2) + nonce``.
+
+ .. doctest::
+
+ >>> from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
+ >>> from cryptography.hazmat.backends import default_backend
+ >>> nonce = os.urandom(16)
+ >>> algorithm = algorithms.ChaCha20(key, nonce)
+ >>> cipher = Cipher(algorithm, mode=None, backend=default_backend())
+ >>> encryptor = cipher.encryptor()
+ >>> ct = encryptor.update(b"a secret message")
+ >>> decryptor = cipher.decryptor()
+ >>> decryptor.update(ct)
+ 'a secret message'
+
.. class:: TripleDES(key)
Triple DES (Data Encryption Standard), sometimes referred to as 3DES, is a
diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py
index ede35ec0..2cbfca2c 100644
--- a/src/cryptography/hazmat/backends/openssl/backend.py
+++ b/src/cryptography/hazmat/backends/openssl/backend.py
@@ -58,7 +58,7 @@ from cryptography.hazmat.primitives.asymmetric.padding import (
MGF1, OAEP, PKCS1v15, PSS
)
from cryptography.hazmat.primitives.ciphers.algorithms import (
- AES, ARC4, Blowfish, CAST5, Camellia, IDEA, SEED, TripleDES
+ AES, ARC4, Blowfish, CAST5, Camellia, ChaCha20, IDEA, SEED, TripleDES
)
from cryptography.hazmat.primitives.ciphers.modes import (
CBC, CFB, CFB8, CTR, ECB, GCM, OFB
@@ -258,6 +258,11 @@ class Backend(object):
type(None),
GetCipherByName("rc4")
)
+ self.register_cipher_adapter(
+ ChaCha20,
+ type(None),
+ GetCipherByName("chacha20")
+ )
def create_symmetric_encryption_ctx(self, cipher, mode):
return _CipherContext(self, cipher, mode, _CipherContext._ENCRYPT)
diff --git a/src/cryptography/hazmat/backends/openssl/ciphers.py b/src/cryptography/hazmat/backends/openssl/ciphers.py
index e141e8ec..dfb33a07 100644
--- a/src/cryptography/hazmat/backends/openssl/ciphers.py
+++ b/src/cryptography/hazmat/backends/openssl/ciphers.py
@@ -59,6 +59,8 @@ class _CipherContext(object):
iv_nonce = mode.initialization_vector
elif isinstance(mode, modes.ModeWithNonce):
iv_nonce = mode.nonce
+ elif isinstance(cipher, modes.ModeWithNonce):
+ iv_nonce = cipher.nonce
else:
iv_nonce = self._backend._ffi.NULL
# begin init with cipher and operation type
diff --git a/src/cryptography/hazmat/primitives/ciphers/algorithms.py b/src/cryptography/hazmat/primitives/ciphers/algorithms.py
index c193f797..6e5eb313 100644
--- a/src/cryptography/hazmat/primitives/ciphers/algorithms.py
+++ b/src/cryptography/hazmat/primitives/ciphers/algorithms.py
@@ -8,6 +8,7 @@ from cryptography import utils
from cryptography.hazmat.primitives.ciphers import (
BlockCipherAlgorithm, CipherAlgorithm
)
+from cryptography.hazmat.primitives.ciphers.modes import ModeWithNonce
def _verify_key_size(algorithm, key):
@@ -138,3 +139,26 @@ class SEED(object):
@property
def key_size(self):
return len(self.key) * 8
+
+
+@utils.register_interface(CipherAlgorithm)
+@utils.register_interface(ModeWithNonce)
+class ChaCha20(object):
+ name = "ChaCha20"
+ key_sizes = frozenset([256])
+
+ def __init__(self, key, nonce):
+ self.key = _verify_key_size(self, key)
+ if not isinstance(nonce, bytes):
+ raise TypeError("nonce must be bytes")
+
+ if len(nonce) != 16:
+ raise ValueError("nonce must be 128-bits (16 bytes)")
+
+ self._nonce = nonce
+
+ nonce = utils.read_only_property("_nonce")
+
+ @property
+ def key_size(self):
+ return len(self.key) * 8
diff --git a/tests/hazmat/primitives/test_chacha20.py b/tests/hazmat/primitives/test_chacha20.py
new file mode 100644
index 00000000..16ef97ed
--- /dev/null
+++ b/tests/hazmat/primitives/test_chacha20.py
@@ -0,0 +1,60 @@
+# 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
+
+import binascii
+import os
+import struct
+
+import pytest
+
+from cryptography.hazmat.backends.interfaces import CipherBackend
+from cryptography.hazmat.primitives.ciphers import Cipher, algorithms
+
+from .utils import _load_all_params
+from ...utils import load_nist_vectors
+
+
+@pytest.mark.supported(
+ only_if=lambda backend: backend.cipher_supported(
+ algorithms.ChaCha20(b"\x00" * 32, b"0" * 16), None
+ ),
+ skip_message="Does not support ChaCha20",
+)
+@pytest.mark.requires_backend_interface(interface=CipherBackend)
+class TestChaCha20(object):
+ @pytest.mark.parametrize(
+ "vector",
+ _load_all_params(
+ os.path.join("ciphers", "ChaCha20"),
+ ["rfc7539.txt"],
+ load_nist_vectors
+ )
+ )
+ def test_vectors(self, vector, backend):
+ key = binascii.unhexlify(vector["key"])
+ nonce = binascii.unhexlify(vector["nonce"])
+ ibc = struct.pack("<i", int(vector["initial_block_counter"]))
+ pt = binascii.unhexlify(vector["plaintext"])
+ encryptor = Cipher(
+ algorithms.ChaCha20(key, ibc + nonce), None, backend
+ ).encryptor()
+ computed_ct = encryptor.update(pt) + encryptor.finalize()
+ assert binascii.hexlify(computed_ct) == vector["ciphertext"]
+
+ def test_key_size(self):
+ chacha = algorithms.ChaCha20(b"0" * 32, b"0" * 16)
+ assert chacha.key_size == 256
+
+ def test_invalid_key_size(self):
+ with pytest.raises(ValueError):
+ algorithms.ChaCha20(b"wrongsize", b"0" * 16)
+
+ def test_invalid_nonce(self):
+ with pytest.raises(ValueError):
+ algorithms.ChaCha20(b"0" * 32, b"0")
+
+ with pytest.raises(TypeError):
+ algorithms.ChaCha20(b"0" * 32, object())