aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorPaul Kehrer <paul.l.kehrer@gmail.com>2017-02-16 22:20:38 -0600
committerAlex Gaynor <alex.gaynor@gmail.com>2017-02-16 23:20:38 -0500
commit9b34ca92c3ac061aee2301728dc1280a83890814 (patch)
tree250f7f978b69b1b933e2152a76477f0936705c0d /src
parent83d3adee771593f3b90a74ff2c2e1a7a2d98b668 (diff)
downloadcryptography-9b34ca92c3ac061aee2301728dc1280a83890814.tar.gz
cryptography-9b34ca92c3ac061aee2301728dc1280a83890814.tar.bz2
cryptography-9b34ca92c3ac061aee2301728dc1280a83890814.zip
add support for update_into on CipherContext (#3190)
* add support for update_into on CipherContext This allows you to provide your own buffer (like recv_into) to improve performance when repeatedly calling encrypt/decrypt on large payloads. * another skip_if * more skip_if complexity * maybe do this right * correct number of args * coverage for the coverage gods * add a cffi minimum test tox target and travis builder This tests against macOS so we capture some commoncrypto branches * extra arg * need to actually install py35 * fix * coverage for GCM decrypt in CC * no longer relevant * 1.8 now * pep8 * dramatically simplify * update docs * remove unneeded test * changelog entry * test improvements * coverage fix * add some comments to example * move the comments to their own line * fix and move comment
Diffstat (limited to 'src')
-rw-r--r--src/cryptography/hazmat/backends/commoncrypto/ciphers.py36
-rw-r--r--src/cryptography/hazmat/backends/openssl/ciphers.py16
-rw-r--r--src/cryptography/hazmat/primitives/ciphers/base.py42
-rw-r--r--src/cryptography/utils.py7
4 files changed, 99 insertions, 2 deletions
diff --git a/src/cryptography/hazmat/backends/commoncrypto/ciphers.py b/src/cryptography/hazmat/backends/commoncrypto/ciphers.py
index 1ce8aec5..b59381cb 100644
--- a/src/cryptography/hazmat/backends/commoncrypto/ciphers.py
+++ b/src/cryptography/hazmat/backends/commoncrypto/ciphers.py
@@ -86,6 +86,24 @@ class _CipherContext(object):
self._backend._check_cipher_response(res)
return self._backend._ffi.buffer(buf)[:outlen[0]]
+ def update_into(self, data, buf):
+ if len(buf) < (len(data) + self._byte_block_size - 1):
+ raise ValueError(
+ "buffer must be at least {0} bytes for this "
+ "payload".format(len(data) + self._byte_block_size - 1)
+ )
+ # Count bytes processed to handle block alignment.
+ self._bytes_processed += len(data)
+ outlen = self._backend._ffi.new("size_t *")
+ buf = self._backend._ffi.cast(
+ "unsigned char *", self._backend._ffi.from_buffer(buf)
+ )
+ res = self._backend._lib.CCCryptorUpdate(
+ self._ctx[0], data, len(data), buf,
+ len(data) + self._byte_block_size - 1, outlen)
+ self._backend._check_cipher_response(res)
+ return outlen[0]
+
def finalize(self):
# Raise error if block alignment is wrong.
if self._bytes_processed % self._byte_block_size:
@@ -161,6 +179,24 @@ class _GCMCipherContext(object):
self._backend._check_cipher_response(res)
return self._backend._ffi.buffer(buf)[:]
+ def update_into(self, data, buf):
+ if len(buf) < len(data):
+ raise ValueError(
+ "buffer must be at least {0} bytes".format(len(data))
+ )
+
+ buf = self._backend._ffi.cast(
+ "unsigned char *", self._backend._ffi.from_buffer(buf)
+ )
+ args = (self._ctx[0], data, len(data), buf)
+ if self._operation == self._backend._lib.kCCEncrypt:
+ res = self._backend._lib.CCCryptorGCMEncrypt(*args)
+ else:
+ res = self._backend._lib.CCCryptorGCMDecrypt(*args)
+
+ self._backend._check_cipher_response(res)
+ return len(data)
+
def finalize(self):
# CommonCrypto has a yet another bug where you must make at least one
# call to update. If you pass just AAD and call finalize without a call
diff --git a/src/cryptography/hazmat/backends/openssl/ciphers.py b/src/cryptography/hazmat/backends/openssl/ciphers.py
index 898b3497..0e0918af 100644
--- a/src/cryptography/hazmat/backends/openssl/ciphers.py
+++ b/src/cryptography/hazmat/backends/openssl/ciphers.py
@@ -109,6 +109,22 @@ class _CipherContext(object):
self._backend.openssl_assert(res != 0)
return self._backend._ffi.buffer(buf)[:outlen[0]]
+ def update_into(self, data, buf):
+ if len(buf) < (len(data) + self._block_size_bytes - 1):
+ raise ValueError(
+ "buffer must be at least {0} bytes for this "
+ "payload".format(len(data) + self._block_size_bytes - 1)
+ )
+
+ buf = self._backend._ffi.cast(
+ "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))
+ self._backend.openssl_assert(res != 0)
+ return outlen[0]
+
def finalize(self):
# OpenSSL 1.0.1 on Ubuntu 12.04 (and possibly other distributions)
# appears to have a bug where you must make at least one call to update
diff --git a/src/cryptography/hazmat/primitives/ciphers/base.py b/src/cryptography/hazmat/primitives/ciphers/base.py
index 496975ae..502d9804 100644
--- a/src/cryptography/hazmat/primitives/ciphers/base.py
+++ b/src/cryptography/hazmat/primitives/ciphers/base.py
@@ -6,6 +6,8 @@ from __future__ import absolute_import, division, print_function
import abc
+import cffi
+
import six
from cryptography import utils
@@ -51,6 +53,13 @@ class CipherContext(object):
"""
@abc.abstractmethod
+ def update_into(self, data, buf):
+ """
+ Processes the provided bytes and writes the resulting data into the
+ provided buffer. Returns the number of bytes written.
+ """
+
+ @abc.abstractmethod
def finalize(self):
"""
Returns the results of processing the final block as bytes.
@@ -136,6 +145,20 @@ class _CipherContext(object):
raise AlreadyFinalized("Context was already finalized.")
return self._ctx.update(data)
+ # cffi 1.7 supports from_buffer on bytearray, which is required. We can
+ # remove this check in the future when we raise our minimum PyPy version.
+ if utils._version_check(cffi.__version__, "1.7"):
+ def update_into(self, data, buf):
+ if self._ctx is None:
+ raise AlreadyFinalized("Context was already finalized.")
+ return self._ctx.update_into(data, buf)
+ else:
+ def update_into(self, data, buf):
+ raise NotImplementedError(
+ "update_into requires cffi 1.7+. To use this method please "
+ "update cffi."
+ )
+
def finalize(self):
if self._ctx is None:
raise AlreadyFinalized("Context was already finalized.")
@@ -154,11 +177,11 @@ class _AEADCipherContext(object):
self._tag = None
self._updated = False
- def update(self, data):
+ def _check_limit(self, data_size):
if self._ctx is None:
raise AlreadyFinalized("Context was already finalized.")
self._updated = True
- self._bytes_processed += len(data)
+ self._bytes_processed += data_size
if self._bytes_processed > self._ctx._mode._MAX_ENCRYPTED_BYTES:
raise ValueError(
"{0} has a maximum encrypted byte limit of {1}".format(
@@ -166,8 +189,23 @@ class _AEADCipherContext(object):
)
)
+ def update(self, data):
+ self._check_limit(len(data))
return self._ctx.update(data)
+ # cffi 1.7 supports from_buffer on bytearray, which is required. We can
+ # remove this check in the future when we raise our minimum PyPy version.
+ if utils._version_check(cffi.__version__, "1.7"):
+ def update_into(self, data, buf):
+ self._check_limit(len(data))
+ return self._ctx.update_into(data, buf)
+ else:
+ def update_into(self, data, buf):
+ raise NotImplementedError(
+ "update_into requires cffi 1.7+. To use this method please "
+ "update cffi."
+ )
+
def finalize(self):
if self._ctx is None:
raise AlreadyFinalized("Context was already finalized.")
diff --git a/src/cryptography/utils.py b/src/cryptography/utils.py
index f16b7efa..8183bdaf 100644
--- a/src/cryptography/utils.py
+++ b/src/cryptography/utils.py
@@ -10,6 +10,8 @@ import inspect
import sys
import warnings
+from packaging.version import parse
+
# the functions deprecated in 1.0 and 1.4 are on an arbitrarily extended
# deprecation cycle and should not be removed until we agree on when that cycle
@@ -98,6 +100,11 @@ else:
return len(bin(x)) - (2 + (x <= 0))
+def _version_check(version, required_version):
+ # This is used to check if we support update_into on CipherContext.
+ return parse(version) >= parse(required_version)
+
+
class _DeprecatedValue(object):
def __init__(self, value, message, warning_class):
self.value = value