aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorPaul Kehrer <paul.l.kehrer@gmail.com>2017-06-07 18:08:57 -1000
committerAlex Gaynor <alex.gaynor@gmail.com>2017-06-08 00:08:57 -0400
commit7e53d911577881d87ce30291cef68e24f3c1b763 (patch)
tree3a8a0b43fdaae7d3d44549f7282048f5f3f1db58 /src
parentf12955cd242664cffbaa041ef815579a8d6b2d3a (diff)
downloadcryptography-7e53d911577881d87ce30291cef68e24f3c1b763.tar.gz
cryptography-7e53d911577881d87ce30291cef68e24f3c1b763.tar.bz2
cryptography-7e53d911577881d87ce30291cef68e24f3c1b763.zip
ChaCha20Poly1305 support (#3680)
* chacha20poly1305 support * add chacha20poly1305 backend and some fixes * refactor * forgot to remove this * pep8 * review feedback and a lot of type/value checking * review feedback * raise unsupportedalgorithm when creating a ChaCha20Poly1305 object if it's not supported. * switch to ciphertext||tag * typo * remove a branch we don't need * review feedback * decrypts is *also* a word * use reasons
Diffstat (limited to 'src')
-rw-r--r--src/cryptography/hazmat/backends/openssl/backend.py17
-rw-r--r--src/cryptography/hazmat/backends/openssl/chacha20poly1305.py101
-rw-r--r--src/cryptography/hazmat/primitives/ciphers/aead.py54
-rw-r--r--src/cryptography/utils.py5
4 files changed, 177 insertions, 0 deletions
diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py
index 412432df..c003b6d3 100644
--- a/src/cryptography/hazmat/backends/openssl/backend.py
+++ b/src/cryptography/hazmat/backends/openssl/backend.py
@@ -20,6 +20,7 @@ from cryptography.hazmat.backends.interfaces import (
EllipticCurveBackend, HMACBackend, HashBackend, PBKDF2HMACBackend,
PEMSerializationBackend, RSABackend, ScryptBackend, X509Backend
)
+from cryptography.hazmat.backends.openssl import chacha20poly1305
from cryptography.hazmat.backends.openssl.ciphers import _CipherContext
from cryptography.hazmat.backends.openssl.cmac import _CMACContext
from cryptography.hazmat.backends.openssl.dh import (
@@ -1780,6 +1781,22 @@ class Backend(object):
self.openssl_assert(res == 1)
return self._ffi.buffer(buf)[:]
+ def chacha20poly1305_encrypt(self, key, nonce, data, associated_data):
+ return chacha20poly1305.encrypt(
+ self, key, nonce, data, associated_data
+ )
+
+ def chacha20poly1305_decrypt(self, key, nonce, data, associated_data):
+ return chacha20poly1305.decrypt(
+ self, key, nonce, data, associated_data
+ )
+
+ def chacha20poly1305_supported(self):
+ return (
+ self._lib.EVP_get_cipherbyname(b"chacha20-poly1305") !=
+ self._ffi.NULL
+ )
+
class GetCipherByName(object):
def __init__(self, fmt):
diff --git a/src/cryptography/hazmat/backends/openssl/chacha20poly1305.py b/src/cryptography/hazmat/backends/openssl/chacha20poly1305.py
new file mode 100644
index 00000000..0834f19c
--- /dev/null
+++ b/src/cryptography/hazmat/backends/openssl/chacha20poly1305.py
@@ -0,0 +1,101 @@
+# 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.exceptions import InvalidTag
+
+
+_ENCRYPT = 1
+_DECRYPT = 0
+
+
+def _chacha20poly1305_setup(backend, key, nonce, tag, operation):
+ evp_cipher = backend._lib.EVP_get_cipherbyname(b"chacha20-poly1305")
+ ctx = backend._lib.EVP_CIPHER_CTX_new()
+ ctx = backend._ffi.gc(ctx, backend._lib.EVP_CIPHER_CTX_free)
+ res = backend._lib.EVP_CipherInit_ex(
+ ctx, evp_cipher,
+ backend._ffi.NULL,
+ backend._ffi.NULL,
+ backend._ffi.NULL,
+ int(operation == _ENCRYPT)
+ )
+ backend.openssl_assert(res != 0)
+ res = backend._lib.EVP_CIPHER_CTX_set_key_length(ctx, len(key))
+ backend.openssl_assert(res != 0)
+ res = backend._lib.EVP_CIPHER_CTX_ctrl(
+ ctx, backend._lib.EVP_CTRL_AEAD_SET_IVLEN, len(nonce),
+ backend._ffi.NULL
+ )
+ backend.openssl_assert(res != 0)
+ if operation == _DECRYPT:
+ res = backend._lib.EVP_CIPHER_CTX_ctrl(
+ ctx, backend._lib.EVP_CTRL_AEAD_SET_TAG, len(tag), tag
+ )
+ backend.openssl_assert(res != 0)
+
+ res = backend._lib.EVP_CipherInit_ex(
+ ctx,
+ backend._ffi.NULL,
+ backend._ffi.NULL,
+ key,
+ nonce,
+ int(operation == _ENCRYPT)
+ )
+ backend.openssl_assert(res != 0)
+ return ctx
+
+
+def _process_aad(backend, ctx, associated_data):
+ outlen = backend._ffi.new("int *")
+ res = backend._lib.EVP_CipherUpdate(
+ ctx, backend._ffi.NULL, outlen, associated_data, len(associated_data)
+ )
+ backend.openssl_assert(res != 0)
+
+
+def _process_data(backend, ctx, data):
+ outlen = backend._ffi.new("int *")
+ buf = backend._ffi.new("unsigned char[]", len(data))
+ res = backend._lib.EVP_CipherUpdate(ctx, buf, outlen, data, len(data))
+ backend.openssl_assert(res != 0)
+ return backend._ffi.buffer(buf, outlen[0])[:]
+
+
+def encrypt(backend, key, nonce, data, associated_data):
+ ctx = _chacha20poly1305_setup(backend, key, nonce, None, _ENCRYPT)
+
+ _process_aad(backend, ctx, associated_data)
+ processed_data = _process_data(backend, ctx, data)
+ outlen = backend._ffi.new("int *")
+ res = backend._lib.EVP_CipherFinal_ex(ctx, backend._ffi.NULL, outlen)
+ backend.openssl_assert(res != 0)
+ backend.openssl_assert(outlen[0] == 0)
+ # get the tag
+ tag_buf = backend._ffi.new("unsigned char[]", 16)
+ res = backend._lib.EVP_CIPHER_CTX_ctrl(
+ ctx, backend._lib.EVP_CTRL_AEAD_GET_TAG, 16, tag_buf
+ )
+ backend.openssl_assert(res != 0)
+ tag = backend._ffi.buffer(tag_buf)[:]
+
+ return processed_data + tag
+
+
+def decrypt(backend, key, nonce, data, associated_data):
+ if len(data) < 16:
+ raise InvalidTag
+ tag = data[-16:]
+ data = data[:-16]
+ ctx = _chacha20poly1305_setup(backend, key, nonce, tag, _DECRYPT)
+ _process_aad(backend, ctx, associated_data)
+ processed_data = _process_data(backend, ctx, data)
+ outlen = backend._ffi.new("int *")
+ res = backend._lib.EVP_CipherFinal_ex(ctx, backend._ffi.NULL, outlen)
+ if res == 0:
+ backend._consume_errors()
+ raise InvalidTag
+
+ return processed_data
diff --git a/src/cryptography/hazmat/primitives/ciphers/aead.py b/src/cryptography/hazmat/primitives/ciphers/aead.py
new file mode 100644
index 00000000..e89c6979
--- /dev/null
+++ b/src/cryptography/hazmat/primitives/ciphers/aead.py
@@ -0,0 +1,54 @@
+# 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 os
+
+from cryptography import exceptions, utils
+from cryptography.hazmat.backends.openssl.backend import backend
+
+
+class ChaCha20Poly1305(object):
+ def __init__(self, key):
+ if not backend.chacha20poly1305_supported():
+ raise exceptions.UnsupportedAlgorithm(
+ "ChaCha20Poly1305 is not supported by this version of OpenSSL",
+ exceptions._Reasons.UNSUPPORTED_CIPHER
+ )
+ utils._check_bytes("key", key)
+
+ if len(key) != 32:
+ raise ValueError("ChaCha20Poly1305 key must be 32 bytes.")
+
+ self._key = key
+
+ @classmethod
+ def generate_key(cls):
+ return os.urandom(32)
+
+ def encrypt(self, nonce, data, associated_data):
+ if associated_data is None:
+ associated_data = b""
+
+ self._check_params(nonce, data, associated_data)
+ return backend.chacha20poly1305_encrypt(
+ self._key, nonce, data, associated_data
+ )
+
+ def decrypt(self, nonce, data, associated_data):
+ if associated_data is None:
+ associated_data = b""
+
+ self._check_params(nonce, data, associated_data)
+ return backend.chacha20poly1305_decrypt(
+ self._key, nonce, data, associated_data
+ )
+
+ def _check_params(self, nonce, data, associated_data):
+ utils._check_bytes("nonce", nonce)
+ utils._check_bytes("data", data)
+ utils._check_bytes("associated_data", associated_data)
+ if len(nonce) != 12:
+ raise ValueError("Nonce must be 12 bytes")
diff --git a/src/cryptography/utils.py b/src/cryptography/utils.py
index cb7137cc..d28dc71d 100644
--- a/src/cryptography/utils.py
+++ b/src/cryptography/utils.py
@@ -18,6 +18,11 @@ PersistentlyDeprecated = DeprecationWarning
DeprecatedIn19 = DeprecationWarning
+def _check_bytes(name, value):
+ if not isinstance(value, bytes):
+ raise TypeError("{0} must be bytes".format(name))
+
+
def read_only_property(name):
return property(lambda self: getattr(self, name))