add ChaCha20 support (#3919)

* add ChaCha20 support

* review feedback

* 256 divided by 8 is what again?

* ...
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 6b4d538..91e450b 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -33,6 +33,11 @@
   :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 d6479a4..10a349b 100644
--- a/docs/hazmat/primitives/symmetric-encryption.rst
+++ b/docs/hazmat/primitives/symmetric-encryption.rst
@@ -104,6 +104,55 @@
     :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 ede35ec..2cbfca2 100644
--- a/src/cryptography/hazmat/backends/openssl/backend.py
+++ b/src/cryptography/hazmat/backends/openssl/backend.py
@@ -58,7 +58,7 @@
     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 @@
             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 e141e8e..dfb33a0 100644
--- a/src/cryptography/hazmat/backends/openssl/ciphers.py
+++ b/src/cryptography/hazmat/backends/openssl/ciphers.py
@@ -59,6 +59,8 @@
             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 c193f79..6e5eb31 100644
--- a/src/cryptography/hazmat/primitives/ciphers/algorithms.py
+++ b/src/cryptography/hazmat/primitives/ciphers/algorithms.py
@@ -8,6 +8,7 @@
 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 @@
     @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 0000000..16ef97e
--- /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())