diff options
author | Paul Kehrer <paul.l.kehrer@gmail.com> | 2017-02-16 22:20:38 -0600 |
---|---|---|
committer | Alex Gaynor <alex.gaynor@gmail.com> | 2017-02-16 23:20:38 -0500 |
commit | 9b34ca92c3ac061aee2301728dc1280a83890814 (patch) | |
tree | 250f7f978b69b1b933e2152a76477f0936705c0d /src | |
parent | 83d3adee771593f3b90a74ff2c2e1a7a2d98b668 (diff) | |
download | cryptography-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.py | 36 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/openssl/ciphers.py | 16 | ||||
-rw-r--r-- | src/cryptography/hazmat/primitives/ciphers/base.py | 42 | ||||
-rw-r--r-- | src/cryptography/utils.py | 7 |
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 |