From 22e80cb96e034679750a38702aaa55e30da05f69 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 20 Nov 2013 21:27:00 -0600 Subject: GCM support --- cryptography/exceptions.py | 4 + cryptography/hazmat/bindings/openssl/backend.py | 43 +++++++++- cryptography/hazmat/primitives/ciphers/base.py | 50 +++++++++-- cryptography/hazmat/primitives/ciphers/modes.py | 11 +++ cryptography/hazmat/primitives/interfaces.py | 18 ++++ docs/exceptions.rst | 5 ++ docs/hazmat/primitives/symmetric-encryption.rst | 48 +++++++++++ tests/hazmat/primitives/test_aes.py | 21 ++++- tests/hazmat/primitives/test_block.py | 17 +++- tests/hazmat/primitives/test_utils.py | 25 +++++- tests/hazmat/primitives/utils.py | 105 +++++++++++++++++++++++- 11 files changed, 334 insertions(+), 13 deletions(-) diff --git a/cryptography/exceptions.py b/cryptography/exceptions.py index c2e71493..f2a731f0 100644 --- a/cryptography/exceptions.py +++ b/cryptography/exceptions.py @@ -18,3 +18,7 @@ class UnsupportedAlgorithm(Exception): class AlreadyFinalized(Exception): pass + + +class NotFinalized(Exception): + pass diff --git a/cryptography/hazmat/bindings/openssl/backend.py b/cryptography/hazmat/bindings/openssl/backend.py index 9f8ea939..08afa4d6 100644 --- a/cryptography/hazmat/bindings/openssl/backend.py +++ b/cryptography/hazmat/bindings/openssl/backend.py @@ -28,7 +28,7 @@ from cryptography.hazmat.primitives.ciphers.algorithms import ( AES, Blowfish, Camellia, CAST5, TripleDES, ARC4, ) from cryptography.hazmat.primitives.ciphers.modes import ( - CBC, CTR, ECB, OFB, CFB + CBC, CTR, ECB, OFB, CFB, GCM, ) @@ -186,6 +186,11 @@ class Backend(object): type(None), GetCipherByName("rc4") ) + self.register_cipher_adapter( + AES, + GCM, + GetCipherByName("{cipher.name}-{cipher.key_size}-{mode.name}") + ) def create_symmetric_encryption_ctx(self, cipher, mode): return _CipherContext(self, cipher, mode, _CipherContext._ENCRYPT) @@ -238,6 +243,9 @@ class _CipherContext(object): def __init__(self, backend, cipher, mode, operation): self._backend = backend self._cipher = cipher + self._mode = mode + self._operation = operation + self._tag = None ctx = self._backend.lib.EVP_CIPHER_CTX_new() ctx = self._backend.ffi.gc(ctx, self._backend.lib.EVP_CIPHER_CTX_free) @@ -270,6 +278,20 @@ class _CipherContext(object): ctx, len(cipher.key) ) assert res != 0 + if isinstance(mode, GCM): + res = self._backend.lib.EVP_CIPHER_CTX_ctrl( + ctx, self._backend.lib.Cryptography_EVP_CTRL_GCM_SET_IVLEN, + len(iv_nonce), self._backend.ffi.NULL + ) + assert res != 0 + if operation == self._DECRYPT: + assert mode.tag is not None + res = self._backend.lib.EVP_CIPHER_CTX_ctrl( + ctx, self._backend.lib.Cryptography_EVP_CTRL_GCM_SET_TAG, + len(mode.tag), mode.tag + ) + assert res != 0 + # pass key/iv res = self._backend.lib.EVP_CipherInit_ex(ctx, self._backend.ffi.NULL, self._backend.ffi.NULL, @@ -298,10 +320,29 @@ class _CipherContext(object): if res == 0: self._backend._handle_error() + if (isinstance(self._mode, GCM) and + self._operation == self._ENCRYPT): + block_byte_size = self._cipher.block_size // 8 + tag_buf = self._backend.ffi.new("unsigned char[]", block_byte_size) + res = self._backend.lib.EVP_CIPHER_CTX_ctrl( + self._ctx, self._backend.lib.Cryptography_EVP_CTRL_GCM_GET_TAG, + block_byte_size, tag_buf + ) + assert res != 0 + size = self._cipher.block_size + self._tag = self._backend.ffi.buffer(tag_buf)[:size] + res = self._backend.lib.EVP_CIPHER_CTX_cleanup(self._ctx) assert res == 1 return self._backend.ffi.buffer(buf)[:outlen[0]] + def add_data(self, data): + outlen = self._backend.ffi.new("int *") + res = self._backend.lib.EVP_CipherUpdate( + self._ctx, self._backend.ffi.NULL, outlen, data, len(data) + ) + assert res != 0 + @utils.register_interface(interfaces.HashContext) class _HashContext(object): diff --git a/cryptography/hazmat/primitives/ciphers/base.py b/cryptography/hazmat/primitives/ciphers/base.py index 48e6da6f..5a4e7850 100644 --- a/cryptography/hazmat/primitives/ciphers/base.py +++ b/cryptography/hazmat/primitives/ciphers/base.py @@ -14,7 +14,7 @@ from __future__ import absolute_import, division, print_function from cryptography import utils -from cryptography.exceptions import AlreadyFinalized +from cryptography.exceptions import AlreadyFinalized, NotFinalized from cryptography.hazmat.primitives import interfaces @@ -28,20 +28,39 @@ class Cipher(object): self._backend = backend def encryptor(self): - return _CipherContext(self._backend.create_symmetric_encryption_ctx( - self.algorithm, self.mode - )) + if isinstance(self.mode, interfaces.ModeWithAAD): + return _AEADCipherContext( + self._backend.create_symmetric_encryption_ctx( + self.algorithm, self.mode + ) + ) + else: + return _CipherContext( + self._backend.create_symmetric_encryption_ctx( + self.algorithm, self.mode + ) + ) def decryptor(self): - return _CipherContext(self._backend.create_symmetric_decryption_ctx( - self.algorithm, self.mode - )) + if isinstance(self.mode, interfaces.ModeWithAAD): + return _AEADCipherContext( + self._backend.create_symmetric_decryption_ctx( + self.algorithm, self.mode + ) + ) + else: + return _CipherContext( + self._backend.create_symmetric_decryption_ctx( + self.algorithm, self.mode + ) + ) @utils.register_interface(interfaces.CipherContext) class _CipherContext(object): def __init__(self, ctx): self._ctx = ctx + self._tag = None def update(self, data): if self._ctx is None: @@ -52,5 +71,22 @@ class _CipherContext(object): if self._ctx is None: raise AlreadyFinalized("Context was already finalized") data = self._ctx.finalize() + self._tag = self._ctx._tag self._ctx = None return data + + +@utils.register_interface(interfaces.AEADCipherContext) +@utils.register_interface(interfaces.CipherContext) +class _AEADCipherContext(_CipherContext): + def add_data(self, data): + if self._ctx is None: + raise AlreadyFinalized("Context was already finalized") + self._ctx.add_data(data) + + @property + def tag(self): + if self._ctx is not None: + raise NotFinalized("You must finalize encryption before " + "getting the tag") + return self._tag diff --git a/cryptography/hazmat/primitives/ciphers/modes.py b/cryptography/hazmat/primitives/ciphers/modes.py index 1d0de689..cb191d98 100644 --- a/cryptography/hazmat/primitives/ciphers/modes.py +++ b/cryptography/hazmat/primitives/ciphers/modes.py @@ -56,3 +56,14 @@ class CTR(object): def __init__(self, nonce): self.nonce = nonce + + +@utils.register_interface(interfaces.Mode) +@utils.register_interface(interfaces.ModeWithInitializationVector) +@utils.register_interface(interfaces.ModeWithAAD) +class GCM(object): + name = "GCM" + + def __init__(self, initialization_vector, tag=None): + self.initialization_vector = initialization_vector + self.tag = tag diff --git a/cryptography/hazmat/primitives/interfaces.py b/cryptography/hazmat/primitives/interfaces.py index 8cc9d42c..574c8226 100644 --- a/cryptography/hazmat/primitives/interfaces.py +++ b/cryptography/hazmat/primitives/interfaces.py @@ -56,6 +56,10 @@ class ModeWithNonce(six.with_metaclass(abc.ABCMeta)): """ +class ModeWithAAD(six.with_metaclass(abc.ABCMeta)): + pass + + class CipherContext(six.with_metaclass(abc.ABCMeta)): @abc.abstractmethod def update(self, data): @@ -70,6 +74,20 @@ class CipherContext(six.with_metaclass(abc.ABCMeta)): """ +class AEADCipherContext(six.with_metaclass(abc.ABCMeta)): + @abc.abstractproperty + def tag(self): + """ + Returns tag bytes after finalizing encryption. + """ + + @abc.abstractmethod + def add_data(self, data): + """ + add_data takes bytes and returns nothing. + """ + + class PaddingContext(six.with_metaclass(abc.ABCMeta)): @abc.abstractmethod def update(self, data): diff --git a/docs/exceptions.rst b/docs/exceptions.rst index c6f5a7cc..7ec3cd27 100644 --- a/docs/exceptions.rst +++ b/docs/exceptions.rst @@ -7,6 +7,11 @@ Exceptions This is raised when a context is used after being finalized. +.. class:: NotFinalized + + This is raised when the AEAD tag property is accessed on a context + before it is finalized. + .. class:: UnsupportedAlgorithm diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst index edf3c050..5b249c06 100644 --- a/docs/hazmat/primitives/symmetric-encryption.rst +++ b/docs/hazmat/primitives/symmetric-encryption.rst @@ -118,6 +118,27 @@ an "encrypt-then-MAC" formulation as `described by Colin Percival`_. :meth:`update` and :meth:`finalize` will raise :class:`~cryptography.exceptions.AlreadyFinalized`. +.. class:: AEADCipherContext + + When calling ``encryptor()`` or ``decryptor()`` on a ``Cipher`` object + with an AEAD mode you will receive a return object conforming to the + ``AEADCipherContext`` interface in addition to the ``CipherContext`` + interface. ``AEADCipherContext`` contains an additional method ``add_data`` + for adding additional authenticated by non-encrypted data. You should call + this before calls to ``update``. When you are done call ``finalize()`` to + finish the operation. Once this is complete you can obtain the tag value + from the ``tag`` property. + + .. method:: add_data(data) + + :param bytes data: The data you wish to authenticate but not encrypt. + :raises: :class:`~cryptography.exceptions.AlreadyFinalized` + + .. method:: tag + + :return bytes: Returns the tag value as bytes. + :raises: :class:`~cryptography.exceptions.NotFinalized` + .. _symmetric-encryption-algorithms: Algorithms @@ -295,6 +316,33 @@ Modes reuse an ``initialization_vector`` with a given ``key``. +.. class:: GCM(initialization_vector, tag=None) + + GCM (Galois Counter Mode) is a mode of operation for block ciphers. It + is an AEAD (authenticated encryption with additional data) mode. + + :param bytes initialization_vector: Must be random bytes. They do not need + to be kept secret (they can be included + in a transmitted message). Recommended + to be 96-bit by NIST, but can be up to + 2\ :sup:`64` - 1 bits. Do not reuse an + ``initialization_vector`` with a given + ``key``. + + .. doctest:: + + >>> from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes + >>> cipher = Cipher(algorithms.AES(key), modes.GCM(iv)) + >>> encryptor = cipher.encryptor() + >>> encryptor.add_data(b"authenticated but encrypted payload") + >>> ct = encryptor.update(b"a secret message") + encryptor.finalize() + >>> tag = encryptor.tag + >>> cipher = Cipher(algorithms.AES(key), modes.GCM(iv, tag)) + >>> decryptor = cipher.decryptor() + >>> decryptor.add_data(b"authenticated but encrypted payload") + >>> decryptor.update(ct) + decryptor.finalize() + 'a secret message' + Insecure Modes -------------- diff --git a/tests/hazmat/primitives/test_aes.py b/tests/hazmat/primitives/test_aes.py index d178da7b..f7b0b9a0 100644 --- a/tests/hazmat/primitives/test_aes.py +++ b/tests/hazmat/primitives/test_aes.py @@ -18,7 +18,7 @@ import os from cryptography.hazmat.primitives.ciphers import algorithms, modes -from .utils import generate_encrypt_test +from .utils import generate_encrypt_test, generate_aead_test from ...utils import ( load_nist_vectors, load_openssl_vectors, ) @@ -132,3 +132,22 @@ class TestAES(object): ), skip_message="Does not support AES CTR", ) + + test_GCM = generate_aead_test( + load_nist_vectors, + os.path.join("ciphers", "AES", "GCM"), + [ + "gcmDecrypt128.rsp", + "gcmDecrypt192.rsp", + "gcmDecrypt256.rsp", + "gcmEncryptExtIV128.rsp", + "gcmEncryptExtIV192.rsp", + "gcmEncryptExtIV256.rsp", + ], + lambda key: algorithms.AES(key), + lambda iv, tag: modes.GCM(iv, tag), + only_if=lambda backend: backend.cipher_supported( + algorithms.AES("\x00" * 16), modes.GCM("\x00" * 12) + ), + skip_message="Does not support AES GCM", + ) diff --git a/tests/hazmat/primitives/test_block.py b/tests/hazmat/primitives/test_block.py index f6c44b47..296821a4 100644 --- a/tests/hazmat/primitives/test_block.py +++ b/tests/hazmat/primitives/test_block.py @@ -18,12 +18,16 @@ import binascii import pytest from cryptography import utils -from cryptography.exceptions import UnsupportedAlgorithm, AlreadyFinalized +from cryptography.exceptions import ( + UnsupportedAlgorithm, AlreadyFinalized, +) from cryptography.hazmat.primitives import interfaces from cryptography.hazmat.primitives.ciphers import ( Cipher, algorithms, modes ) +from .utils import generate_aead_use_after_finalize_test + @utils.register_interface(interfaces.CipherAlgorithm) class DummyCipher(object): @@ -120,3 +124,14 @@ class TestCipherContext(object): decryptor.update(b"1") with pytest.raises(ValueError): decryptor.finalize() + + +class TestAEADCipherContext(object): + test_use_after_finalize = generate_aead_use_after_finalize_test( + algorithms.AES, + modes.GCM, + only_if=lambda backend: backend.cipher_supported( + algorithms.AES("\x00" * 16), modes.GCM("\x00" * 12) + ), + skip_message="Does not support AES GCM", + ) diff --git a/tests/hazmat/primitives/test_utils.py b/tests/hazmat/primitives/test_utils.py index cee0b20e..f286e02d 100644 --- a/tests/hazmat/primitives/test_utils.py +++ b/tests/hazmat/primitives/test_utils.py @@ -2,7 +2,8 @@ import pytest from .utils import ( base_hash_test, encrypt_test, hash_test, long_string_hash_test, - base_hmac_test, hmac_test, stream_encryption_test + base_hmac_test, hmac_test, stream_encryption_test, aead_test, + aead_use_after_finalize_test, ) @@ -17,6 +18,28 @@ class TestEncryptTest(object): assert exc_info.value.args[0] == "message!" +class TestAEADTest(object): + def test_skips_if_only_if_returns_false(self): + with pytest.raises(pytest.skip.Exception) as exc_info: + aead_test( + None, None, None, None, + only_if=lambda backend: False, + skip_message="message!" + ) + assert exc_info.value.args[0] == "message!" + + +class TestAEADFinalizeTest(object): + def test_skips_if_only_if_returns_false(self): + with pytest.raises(pytest.skip.Exception) as exc_info: + aead_use_after_finalize_test( + None, None, None, + only_if=lambda backend: False, + skip_message="message!" + ) + assert exc_info.value.args[0] == "message!" + + class TestHashTest(object): def test_skips_if_only_if_returns_false(self): with pytest.raises(pytest.skip.Exception) as exc_info: diff --git a/tests/hazmat/primitives/utils.py b/tests/hazmat/primitives/utils.py index 6c67ddb3..839ff822 100644 --- a/tests/hazmat/primitives/utils.py +++ b/tests/hazmat/primitives/utils.py @@ -4,9 +4,11 @@ import os import pytest from cryptography.hazmat.bindings import _ALL_BACKENDS -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives import hmac +from cryptography.hazmat.primitives import hashes, hmac from cryptography.hazmat.primitives.ciphers import Cipher +from cryptography.exceptions import ( + AlreadyFinalized, NotFinalized, +) from ...utils import load_vectors_from_file @@ -54,6 +56,72 @@ def encrypt_test(backend, cipher_factory, mode_factory, params, only_if, assert actual_plaintext == binascii.unhexlify(plaintext) +def generate_aead_test(param_loader, path, file_names, cipher_factory, + mode_factory, only_if, skip_message): + def test_aead(self): + for backend in _ALL_BACKENDS: + for file_name in file_names: + for params in load_vectors_from_file( + os.path.join(path, file_name), + param_loader + ): + yield ( + aead_test, + backend, + cipher_factory, + mode_factory, + params, + only_if, + skip_message + ) + return test_aead + + +def aead_test(backend, cipher_factory, mode_factory, params, only_if, + skip_message): + if not only_if(backend): + pytest.skip(skip_message) + if params.get("pt") is not None: + plaintext = params.pop("pt") + ciphertext = params.pop("ct") + aad = params.pop("aad") + if params.get("fail") is True: + cipher = Cipher( + cipher_factory(binascii.unhexlify(params["key"])), + mode_factory(binascii.unhexlify(params["iv"]), + binascii.unhexlify(params["tag"])), + backend + ) + decryptor = cipher.decryptor() + decryptor.add_data(binascii.unhexlify(aad)) + actual_plaintext = decryptor.update(binascii.unhexlify(ciphertext)) + with pytest.raises(AssertionError): + decryptor.finalize() + else: + cipher = Cipher( + cipher_factory(binascii.unhexlify(params["key"])), + mode_factory(binascii.unhexlify(params["iv"]), None), + backend + ) + encryptor = cipher.encryptor() + encryptor.add_data(binascii.unhexlify(aad)) + actual_ciphertext = encryptor.update(binascii.unhexlify(plaintext)) + actual_ciphertext += encryptor.finalize() + tag_len = len(params["tag"]) + assert binascii.hexlify(encryptor.tag)[:tag_len] == params["tag"] + cipher = Cipher( + cipher_factory(binascii.unhexlify(params["key"])), + mode_factory(binascii.unhexlify(params["iv"]), + binascii.unhexlify(params["tag"])), + backend + ) + decryptor = cipher.decryptor() + decryptor.add_data(binascii.unhexlify(aad)) + actual_plaintext = decryptor.update(binascii.unhexlify(ciphertext)) + actual_plaintext += decryptor.finalize() + assert actual_plaintext == binascii.unhexlify(plaintext) + + def generate_stream_encryption_test(param_loader, path, file_names, cipher_factory, only_if=None, skip_message=None): @@ -237,3 +305,36 @@ def base_hmac_test(backend, algorithm, only_if, skip_message): h_copy = h.copy() assert h != h_copy assert h._ctx != h_copy._ctx + + +def generate_aead_use_after_finalize_test(cipher_factory, mode_factory, + only_if, skip_message): + def test_aead_use_after_finalize(self): + for backend in _ALL_BACKENDS: + yield ( + aead_use_after_finalize_test, + backend, + cipher_factory, + mode_factory, + only_if, + skip_message + ) + return test_aead_use_after_finalize + + +def aead_use_after_finalize_test(backend, cipher_factory, mode_factory, + only_if, skip_message): + if not only_if(backend): + pytest.skip(skip_message) + cipher = Cipher( + cipher_factory(binascii.unhexlify(b"0" * 32)), + mode_factory(binascii.unhexlify(b"0" * 24)), + backend + ) + encryptor = cipher.encryptor() + encryptor.update(b"a" * 16) + with pytest.raises(NotFinalized): + encryptor.tag + encryptor.finalize() + with pytest.raises(AlreadyFinalized): + encryptor.add_data(b"b" * 16) -- cgit v1.2.3 From cf6032648a93329e93f40a3654c68d8d57cf0d63 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Thu, 21 Nov 2013 11:19:45 -0600 Subject: fix buffer size bug for gcm tag fetch --- cryptography/hazmat/bindings/openssl/backend.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cryptography/hazmat/bindings/openssl/backend.py b/cryptography/hazmat/bindings/openssl/backend.py index 08afa4d6..fdb67628 100644 --- a/cryptography/hazmat/bindings/openssl/backend.py +++ b/cryptography/hazmat/bindings/openssl/backend.py @@ -329,8 +329,7 @@ class _CipherContext(object): block_byte_size, tag_buf ) assert res != 0 - size = self._cipher.block_size - self._tag = self._backend.ffi.buffer(tag_buf)[:size] + self._tag = self._backend.ffi.buffer(tag_buf)[:] res = self._backend.lib.EVP_CIPHER_CTX_cleanup(self._ctx) assert res == 1 -- cgit v1.2.3 From 65c4e0a396b9d4183d9ce16b27742d407eb9d91d Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Thu, 21 Nov 2013 11:20:56 -0600 Subject: gcm doc fixes --- docs/hazmat/primitives/symmetric-encryption.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst index 5b249c06..bd9a3f60 100644 --- a/docs/hazmat/primitives/symmetric-encryption.rst +++ b/docs/hazmat/primitives/symmetric-encryption.rst @@ -122,7 +122,7 @@ an "encrypt-then-MAC" formulation as `described by Colin Percival`_. When calling ``encryptor()`` or ``decryptor()`` on a ``Cipher`` object with an AEAD mode you will receive a return object conforming to the - ``AEADCipherContext`` interface in addition to the ``CipherContext`` + ``AEADCipherContext`` interface, in addition to the ``CipherContext`` interface. ``AEADCipherContext`` contains an additional method ``add_data`` for adding additional authenticated by non-encrypted data. You should call this before calls to ``update``. When you are done call ``finalize()`` to @@ -134,7 +134,7 @@ an "encrypt-then-MAC" formulation as `described by Colin Percival`_. :param bytes data: The data you wish to authenticate but not encrypt. :raises: :class:`~cryptography.exceptions.AlreadyFinalized` - .. method:: tag + .. attribute:: tag :return bytes: Returns the tag value as bytes. :raises: :class:`~cryptography.exceptions.NotFinalized` -- cgit v1.2.3 From a5e1081098be6734f8abe31ce0b84c72490f4cb3 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Thu, 21 Nov 2013 11:21:21 -0600 Subject: add tag as abstractproperty for modewithaad --- cryptography/hazmat/primitives/interfaces.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cryptography/hazmat/primitives/interfaces.py b/cryptography/hazmat/primitives/interfaces.py index 574c8226..f216686a 100644 --- a/cryptography/hazmat/primitives/interfaces.py +++ b/cryptography/hazmat/primitives/interfaces.py @@ -57,7 +57,11 @@ class ModeWithNonce(six.with_metaclass(abc.ABCMeta)): class ModeWithAAD(six.with_metaclass(abc.ABCMeta)): - pass + @abc.abstractproperty + def tag(self): + """ + The value of the tag supplied to the constructor of this mode. + """ class CipherContext(six.with_metaclass(abc.ABCMeta)): -- cgit v1.2.3 From cc9ec987e82d1c4e2d42e6ef41664a090425287c Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Thu, 21 Nov 2013 11:21:35 -0600 Subject: rename NotFinalized exception to NotYetFinalized because alex is right ...it does read better that way --- cryptography/exceptions.py | 2 +- cryptography/hazmat/primitives/ciphers/base.py | 6 +++--- tests/hazmat/primitives/utils.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cryptography/exceptions.py b/cryptography/exceptions.py index f2a731f0..8b286679 100644 --- a/cryptography/exceptions.py +++ b/cryptography/exceptions.py @@ -20,5 +20,5 @@ class AlreadyFinalized(Exception): pass -class NotFinalized(Exception): +class NotYetFinalized(Exception): pass diff --git a/cryptography/hazmat/primitives/ciphers/base.py b/cryptography/hazmat/primitives/ciphers/base.py index 5a4e7850..3a27030a 100644 --- a/cryptography/hazmat/primitives/ciphers/base.py +++ b/cryptography/hazmat/primitives/ciphers/base.py @@ -14,7 +14,7 @@ from __future__ import absolute_import, division, print_function from cryptography import utils -from cryptography.exceptions import AlreadyFinalized, NotFinalized +from cryptography.exceptions import AlreadyFinalized, NotYetFinalized from cryptography.hazmat.primitives import interfaces @@ -87,6 +87,6 @@ class _AEADCipherContext(_CipherContext): @property def tag(self): if self._ctx is not None: - raise NotFinalized("You must finalize encryption before " - "getting the tag") + raise NotYetFinalized("You must finalize encryption before " + "getting the tag") return self._tag diff --git a/tests/hazmat/primitives/utils.py b/tests/hazmat/primitives/utils.py index 839ff822..98455556 100644 --- a/tests/hazmat/primitives/utils.py +++ b/tests/hazmat/primitives/utils.py @@ -7,7 +7,7 @@ from cryptography.hazmat.bindings import _ALL_BACKENDS from cryptography.hazmat.primitives import hashes, hmac from cryptography.hazmat.primitives.ciphers import Cipher from cryptography.exceptions import ( - AlreadyFinalized, NotFinalized, + AlreadyFinalized, NotYetFinalized, ) from ...utils import load_vectors_from_file @@ -333,7 +333,7 @@ def aead_use_after_finalize_test(backend, cipher_factory, mode_factory, ) encryptor = cipher.encryptor() encryptor.update(b"a" * 16) - with pytest.raises(NotFinalized): + with pytest.raises(NotYetFinalized): encryptor.tag encryptor.finalize() with pytest.raises(AlreadyFinalized): -- cgit v1.2.3 From 24316fd1945909ef720ceb0e294752c4d3b6bbb2 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Fri, 22 Nov 2013 13:30:46 -0600 Subject: _AEADCipherContext refactor * No longer extends _CipherContext * Remove _tag from _CipherContext * This change duplicates a small amount of code from _CipherContext --- cryptography/hazmat/primitives/ciphers/base.py | 21 ++++++++++++++++++--- tests/hazmat/primitives/utils.py | 4 ++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/cryptography/hazmat/primitives/ciphers/base.py b/cryptography/hazmat/primitives/ciphers/base.py index 3a27030a..89e56547 100644 --- a/cryptography/hazmat/primitives/ciphers/base.py +++ b/cryptography/hazmat/primitives/ciphers/base.py @@ -60,7 +60,6 @@ class Cipher(object): class _CipherContext(object): def __init__(self, ctx): self._ctx = ctx - self._tag = None def update(self, data): if self._ctx is None: @@ -71,14 +70,30 @@ class _CipherContext(object): if self._ctx is None: raise AlreadyFinalized("Context was already finalized") data = self._ctx.finalize() - self._tag = self._ctx._tag self._ctx = None return data @utils.register_interface(interfaces.AEADCipherContext) @utils.register_interface(interfaces.CipherContext) -class _AEADCipherContext(_CipherContext): +class _AEADCipherContext(object): + def __init__(self, ctx): + self._ctx = ctx + self._tag = None + + def update(self, data): + if self._ctx is None: + raise AlreadyFinalized("Context was already finalized") + return self._ctx.update(data) + + def finalize(self): + if self._ctx is None: + raise AlreadyFinalized("Context was already finalized") + data = self._ctx.finalize() + self._tag = self._ctx._tag + self._ctx = None + return data + def add_data(self, data): if self._ctx is None: raise AlreadyFinalized("Context was already finalized") diff --git a/tests/hazmat/primitives/utils.py b/tests/hazmat/primitives/utils.py index 98455556..2a99cab9 100644 --- a/tests/hazmat/primitives/utils.py +++ b/tests/hazmat/primitives/utils.py @@ -338,3 +338,7 @@ def aead_use_after_finalize_test(backend, cipher_factory, mode_factory, encryptor.finalize() with pytest.raises(AlreadyFinalized): encryptor.add_data(b"b" * 16) + with pytest.raises(AlreadyFinalized): + encryptor.update(b"b" * 16) + with pytest.raises(AlreadyFinalized): + encryptor.finalize() -- cgit v1.2.3 From 6331daa36902edf5a5dd04e4e3fa0e188db59420 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Fri, 22 Nov 2013 13:42:02 -0600 Subject: gcm doc improvements --- docs/hazmat/primitives/symmetric-encryption.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst index bd9a3f60..4e7990b0 100644 --- a/docs/hazmat/primitives/symmetric-encryption.rst +++ b/docs/hazmat/primitives/symmetric-encryption.rst @@ -137,7 +137,8 @@ an "encrypt-then-MAC" formulation as `described by Colin Percival`_. .. attribute:: tag :return bytes: Returns the tag value as bytes. - :raises: :class:`~cryptography.exceptions.NotFinalized` + :raises: :class:`~cryptography.exceptions.NotYetFinalized` if called + before the context is finalized. .. _symmetric-encryption-algorithms: @@ -319,7 +320,10 @@ Modes .. class:: GCM(initialization_vector, tag=None) GCM (Galois Counter Mode) is a mode of operation for block ciphers. It - is an AEAD (authenticated encryption with additional data) mode. + is an AEAD (authenticated encryption with additional data) mode. AEAD + is a type of block cipher mode that encrypts the message as well as + authenticating it (and optionally additional data that is not encrypted) + simultaneously. :param bytes initialization_vector: Must be random bytes. They do not need to be kept secret (they can be included -- cgit v1.2.3 From ce9c611feb4db781fcab5b7bbc68b936816d6a73 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Fri, 22 Nov 2013 14:10:59 -0600 Subject: enforce AEAD add_data before update --- cryptography/exceptions.py | 4 ++++ cryptography/hazmat/primitives/ciphers/base.py | 8 +++++++- docs/exceptions.rst | 9 ++++++++- tests/hazmat/primitives/test_block.py | 4 ++-- tests/hazmat/primitives/test_utils.py | 4 ++-- tests/hazmat/primitives/utils.py | 18 ++++++++++-------- 6 files changed, 33 insertions(+), 14 deletions(-) diff --git a/cryptography/exceptions.py b/cryptography/exceptions.py index 8b286679..d56db214 100644 --- a/cryptography/exceptions.py +++ b/cryptography/exceptions.py @@ -20,5 +20,9 @@ class AlreadyFinalized(Exception): pass +class AlreadyUpdated(Exception): + pass + + class NotYetFinalized(Exception): pass diff --git a/cryptography/hazmat/primitives/ciphers/base.py b/cryptography/hazmat/primitives/ciphers/base.py index 89e56547..7c315898 100644 --- a/cryptography/hazmat/primitives/ciphers/base.py +++ b/cryptography/hazmat/primitives/ciphers/base.py @@ -14,7 +14,9 @@ from __future__ import absolute_import, division, print_function from cryptography import utils -from cryptography.exceptions import AlreadyFinalized, NotYetFinalized +from cryptography.exceptions import ( + AlreadyFinalized, NotYetFinalized, AlreadyUpdated, +) from cryptography.hazmat.primitives import interfaces @@ -80,10 +82,12 @@ class _AEADCipherContext(object): def __init__(self, ctx): self._ctx = ctx self._tag = None + self._updated = False def update(self, data): if self._ctx is None: raise AlreadyFinalized("Context was already finalized") + self._updated = True return self._ctx.update(data) def finalize(self): @@ -97,6 +101,8 @@ class _AEADCipherContext(object): def add_data(self, data): if self._ctx is None: raise AlreadyFinalized("Context was already finalized") + if self._updated: + raise AlreadyUpdated("Update has been called on this context") self._ctx.add_data(data) @property diff --git a/docs/exceptions.rst b/docs/exceptions.rst index 7ec3cd27..087066b8 100644 --- a/docs/exceptions.rst +++ b/docs/exceptions.rst @@ -7,12 +7,19 @@ Exceptions This is raised when a context is used after being finalized. -.. class:: NotFinalized + +.. class:: NotYetFinalized This is raised when the AEAD tag property is accessed on a context before it is finalized. +.. class:: AlreadyUpdated + + This is raised when additional data is added to a context after update + has already been called. + + .. class:: UnsupportedAlgorithm This is raised when a backend doesn't support the requested algorithm (or diff --git a/tests/hazmat/primitives/test_block.py b/tests/hazmat/primitives/test_block.py index 296821a4..2806efd5 100644 --- a/tests/hazmat/primitives/test_block.py +++ b/tests/hazmat/primitives/test_block.py @@ -26,7 +26,7 @@ from cryptography.hazmat.primitives.ciphers import ( Cipher, algorithms, modes ) -from .utils import generate_aead_use_after_finalize_test +from .utils import generate_aead_exception_test @utils.register_interface(interfaces.CipherAlgorithm) @@ -127,7 +127,7 @@ class TestCipherContext(object): class TestAEADCipherContext(object): - test_use_after_finalize = generate_aead_use_after_finalize_test( + test_aead_exceptions = generate_aead_exception_test( algorithms.AES, modes.GCM, only_if=lambda backend: backend.cipher_supported( diff --git a/tests/hazmat/primitives/test_utils.py b/tests/hazmat/primitives/test_utils.py index f286e02d..ebb8b5c4 100644 --- a/tests/hazmat/primitives/test_utils.py +++ b/tests/hazmat/primitives/test_utils.py @@ -3,7 +3,7 @@ import pytest from .utils import ( base_hash_test, encrypt_test, hash_test, long_string_hash_test, base_hmac_test, hmac_test, stream_encryption_test, aead_test, - aead_use_after_finalize_test, + aead_exception_test, ) @@ -32,7 +32,7 @@ class TestAEADTest(object): class TestAEADFinalizeTest(object): def test_skips_if_only_if_returns_false(self): with pytest.raises(pytest.skip.Exception) as exc_info: - aead_use_after_finalize_test( + aead_exception_test( None, None, None, only_if=lambda backend: False, skip_message="message!" diff --git a/tests/hazmat/primitives/utils.py b/tests/hazmat/primitives/utils.py index 2a99cab9..8df02e78 100644 --- a/tests/hazmat/primitives/utils.py +++ b/tests/hazmat/primitives/utils.py @@ -7,7 +7,7 @@ from cryptography.hazmat.bindings import _ALL_BACKENDS from cryptography.hazmat.primitives import hashes, hmac from cryptography.hazmat.primitives.ciphers import Cipher from cryptography.exceptions import ( - AlreadyFinalized, NotYetFinalized, + AlreadyFinalized, NotYetFinalized, AlreadyUpdated, ) from ...utils import load_vectors_from_file @@ -307,23 +307,23 @@ def base_hmac_test(backend, algorithm, only_if, skip_message): assert h._ctx != h_copy._ctx -def generate_aead_use_after_finalize_test(cipher_factory, mode_factory, - only_if, skip_message): - def test_aead_use_after_finalize(self): +def generate_aead_exception_test(cipher_factory, mode_factory, + only_if, skip_message): + def test_aead_exception(self): for backend in _ALL_BACKENDS: yield ( - aead_use_after_finalize_test, + aead_exception_test, backend, cipher_factory, mode_factory, only_if, skip_message ) - return test_aead_use_after_finalize + return test_aead_exception -def aead_use_after_finalize_test(backend, cipher_factory, mode_factory, - only_if, skip_message): +def aead_exception_test(backend, cipher_factory, mode_factory, + only_if, skip_message): if not only_if(backend): pytest.skip(skip_message) cipher = Cipher( @@ -335,6 +335,8 @@ def aead_use_after_finalize_test(backend, cipher_factory, mode_factory, encryptor.update(b"a" * 16) with pytest.raises(NotYetFinalized): encryptor.tag + with pytest.raises(AlreadyUpdated): + encryptor.add_data(b"b" * 16) encryptor.finalize() with pytest.raises(AlreadyFinalized): encryptor.add_data(b"b" * 16) -- cgit v1.2.3 From a4bfc08b8d2ed312eeb1b0558ac20f285feb8cc2 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Fri, 22 Nov 2013 19:57:37 -0600 Subject: invalidtag exception for gcm This exception is probably not safe. It depends on the assumption that if ERR_get_error returns a 0 then it is an AEAD tag error. --- cryptography/exceptions.py | 4 ++++ cryptography/hazmat/bindings/openssl/backend.py | 5 +++-- tests/hazmat/primitives/utils.py | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/cryptography/exceptions.py b/cryptography/exceptions.py index d56db214..e9d88199 100644 --- a/cryptography/exceptions.py +++ b/cryptography/exceptions.py @@ -26,3 +26,7 @@ class AlreadyUpdated(Exception): class NotYetFinalized(Exception): pass + + +class InvalidTag(Exception): + pass diff --git a/cryptography/hazmat/bindings/openssl/backend.py b/cryptography/hazmat/bindings/openssl/backend.py index fdb67628..527706bc 100644 --- a/cryptography/hazmat/bindings/openssl/backend.py +++ b/cryptography/hazmat/bindings/openssl/backend.py @@ -19,7 +19,7 @@ import sys import cffi from cryptography import utils -from cryptography.exceptions import UnsupportedAlgorithm +from cryptography.exceptions import UnsupportedAlgorithm, InvalidTag from cryptography.hazmat.bindings.interfaces import ( CipherBackend, HashBackend, HMACBackend ) @@ -200,7 +200,8 @@ class Backend(object): def _handle_error(self): code = self.lib.ERR_get_error() - assert code != 0 + if not code: + raise InvalidTag lib = self.lib.ERR_GET_LIB(code) func = self.lib.ERR_GET_FUNC(code) reason = self.lib.ERR_GET_REASON(code) diff --git a/tests/hazmat/primitives/utils.py b/tests/hazmat/primitives/utils.py index 8df02e78..39f5ae82 100644 --- a/tests/hazmat/primitives/utils.py +++ b/tests/hazmat/primitives/utils.py @@ -7,7 +7,7 @@ from cryptography.hazmat.bindings import _ALL_BACKENDS from cryptography.hazmat.primitives import hashes, hmac from cryptography.hazmat.primitives.ciphers import Cipher from cryptography.exceptions import ( - AlreadyFinalized, NotYetFinalized, AlreadyUpdated, + AlreadyFinalized, NotYetFinalized, AlreadyUpdated, InvalidTag, ) from ...utils import load_vectors_from_file @@ -95,7 +95,7 @@ def aead_test(backend, cipher_factory, mode_factory, params, only_if, decryptor = cipher.decryptor() decryptor.add_data(binascii.unhexlify(aad)) actual_plaintext = decryptor.update(binascii.unhexlify(ciphertext)) - with pytest.raises(AssertionError): + with pytest.raises(InvalidTag): decryptor.finalize() else: cipher = Cipher( -- cgit v1.2.3 From 2631c2b7be22f15f83810df1b8664bf388ad02a6 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sun, 24 Nov 2013 10:20:50 -0600 Subject: gcm doc fixes (per review from alex) --- docs/hazmat/primitives/symmetric-encryption.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst index 4e7990b0..3ed8c9e2 100644 --- a/docs/hazmat/primitives/symmetric-encryption.rst +++ b/docs/hazmat/primitives/symmetric-encryption.rst @@ -323,7 +323,8 @@ Modes is an AEAD (authenticated encryption with additional data) mode. AEAD is a type of block cipher mode that encrypts the message as well as authenticating it (and optionally additional data that is not encrypted) - simultaneously. + simultaneously. Additional means of verifying integrity (like + :doc:`HMAC `) are not necessary. :param bytes initialization_vector: Must be random bytes. They do not need to be kept secret (they can be included @@ -338,12 +339,12 @@ Modes >>> from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes >>> cipher = Cipher(algorithms.AES(key), modes.GCM(iv)) >>> encryptor = cipher.encryptor() - >>> encryptor.add_data(b"authenticated but encrypted payload") + >>> encryptor.add_data(b"authenticated but not encrypted payload") >>> ct = encryptor.update(b"a secret message") + encryptor.finalize() >>> tag = encryptor.tag >>> cipher = Cipher(algorithms.AES(key), modes.GCM(iv, tag)) >>> decryptor = cipher.decryptor() - >>> decryptor.add_data(b"authenticated but encrypted payload") + >>> decryptor.add_data(b"authenticated but not encrypted payload") >>> decryptor.update(ct) + decryptor.finalize() 'a secret message' -- cgit v1.2.3 From bc31fb22979df3f034ce286fab20da71be76fe58 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sun, 24 Nov 2013 11:03:53 -0600 Subject: rename add_data to authenticate_additional_data for clarity (hopefully) --- cryptography/hazmat/bindings/openssl/backend.py | 2 +- cryptography/hazmat/primitives/ciphers/base.py | 4 ++-- cryptography/hazmat/primitives/interfaces.py | 4 ++-- docs/hazmat/primitives/symmetric-encryption.rst | 16 ++++++++-------- tests/hazmat/primitives/utils.py | 10 +++++----- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/cryptography/hazmat/bindings/openssl/backend.py b/cryptography/hazmat/bindings/openssl/backend.py index 527706bc..1a534011 100644 --- a/cryptography/hazmat/bindings/openssl/backend.py +++ b/cryptography/hazmat/bindings/openssl/backend.py @@ -336,7 +336,7 @@ class _CipherContext(object): assert res == 1 return self._backend.ffi.buffer(buf)[:outlen[0]] - def add_data(self, data): + def authenticate_additional_data(self, data): outlen = self._backend.ffi.new("int *") res = self._backend.lib.EVP_CipherUpdate( self._ctx, self._backend.ffi.NULL, outlen, data, len(data) diff --git a/cryptography/hazmat/primitives/ciphers/base.py b/cryptography/hazmat/primitives/ciphers/base.py index 7c315898..c8c4533b 100644 --- a/cryptography/hazmat/primitives/ciphers/base.py +++ b/cryptography/hazmat/primitives/ciphers/base.py @@ -98,12 +98,12 @@ class _AEADCipherContext(object): self._ctx = None return data - def add_data(self, data): + def authenticate_additional_data(self, data): if self._ctx is None: raise AlreadyFinalized("Context was already finalized") if self._updated: raise AlreadyUpdated("Update has been called on this context") - self._ctx.add_data(data) + self._ctx.authenticate_additional_data(data) @property def tag(self): diff --git a/cryptography/hazmat/primitives/interfaces.py b/cryptography/hazmat/primitives/interfaces.py index f216686a..c0548dfd 100644 --- a/cryptography/hazmat/primitives/interfaces.py +++ b/cryptography/hazmat/primitives/interfaces.py @@ -86,9 +86,9 @@ class AEADCipherContext(six.with_metaclass(abc.ABCMeta)): """ @abc.abstractmethod - def add_data(self, data): + def authenticate_additional_data(self, data): """ - add_data takes bytes and returns nothing. + authenticate_additional_data takes bytes and returns nothing. """ diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst index 3ed8c9e2..d123d15c 100644 --- a/docs/hazmat/primitives/symmetric-encryption.rst +++ b/docs/hazmat/primitives/symmetric-encryption.rst @@ -123,13 +123,13 @@ an "encrypt-then-MAC" formulation as `described by Colin Percival`_. When calling ``encryptor()`` or ``decryptor()`` on a ``Cipher`` object with an AEAD mode you will receive a return object conforming to the ``AEADCipherContext`` interface, in addition to the ``CipherContext`` - interface. ``AEADCipherContext`` contains an additional method ``add_data`` - for adding additional authenticated by non-encrypted data. You should call - this before calls to ``update``. When you are done call ``finalize()`` to - finish the operation. Once this is complete you can obtain the tag value - from the ``tag`` property. + interface. ``AEADCipherContext`` contains an additional method + ``authenticate_additional_data`` for adding additional authenticated but + unencrypted data. You should call this before calls to ``update``. When you + are done call ``finalize()`` to finish the operation. Once this is complete + you can obtain the tag value from the ``tag`` property. - .. method:: add_data(data) + .. method:: authenticate_additional_data(data) :param bytes data: The data you wish to authenticate but not encrypt. :raises: :class:`~cryptography.exceptions.AlreadyFinalized` @@ -339,12 +339,12 @@ Modes >>> from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes >>> cipher = Cipher(algorithms.AES(key), modes.GCM(iv)) >>> encryptor = cipher.encryptor() - >>> encryptor.add_data(b"authenticated but not encrypted payload") + >>> encryptor.authenticate_additional_data(b"authenticated but not encrypted payload") >>> ct = encryptor.update(b"a secret message") + encryptor.finalize() >>> tag = encryptor.tag >>> cipher = Cipher(algorithms.AES(key), modes.GCM(iv, tag)) >>> decryptor = cipher.decryptor() - >>> decryptor.add_data(b"authenticated but not encrypted payload") + >>> decryptor.authenticate_additional_data(b"authenticated but not encrypted payload") >>> decryptor.update(ct) + decryptor.finalize() 'a secret message' diff --git a/tests/hazmat/primitives/utils.py b/tests/hazmat/primitives/utils.py index 39f5ae82..b6f9e0f5 100644 --- a/tests/hazmat/primitives/utils.py +++ b/tests/hazmat/primitives/utils.py @@ -93,7 +93,7 @@ def aead_test(backend, cipher_factory, mode_factory, params, only_if, backend ) decryptor = cipher.decryptor() - decryptor.add_data(binascii.unhexlify(aad)) + decryptor.authenticate_additional_data(binascii.unhexlify(aad)) actual_plaintext = decryptor.update(binascii.unhexlify(ciphertext)) with pytest.raises(InvalidTag): decryptor.finalize() @@ -104,7 +104,7 @@ def aead_test(backend, cipher_factory, mode_factory, params, only_if, backend ) encryptor = cipher.encryptor() - encryptor.add_data(binascii.unhexlify(aad)) + encryptor.authenticate_additional_data(binascii.unhexlify(aad)) actual_ciphertext = encryptor.update(binascii.unhexlify(plaintext)) actual_ciphertext += encryptor.finalize() tag_len = len(params["tag"]) @@ -116,7 +116,7 @@ def aead_test(backend, cipher_factory, mode_factory, params, only_if, backend ) decryptor = cipher.decryptor() - decryptor.add_data(binascii.unhexlify(aad)) + decryptor.authenticate_additional_data(binascii.unhexlify(aad)) actual_plaintext = decryptor.update(binascii.unhexlify(ciphertext)) actual_plaintext += decryptor.finalize() assert actual_plaintext == binascii.unhexlify(plaintext) @@ -336,10 +336,10 @@ def aead_exception_test(backend, cipher_factory, mode_factory, with pytest.raises(NotYetFinalized): encryptor.tag with pytest.raises(AlreadyUpdated): - encryptor.add_data(b"b" * 16) + encryptor.authenticate_additional_data(b"b" * 16) encryptor.finalize() with pytest.raises(AlreadyFinalized): - encryptor.add_data(b"b" * 16) + encryptor.authenticate_additional_data(b"b" * 16) with pytest.raises(AlreadyFinalized): encryptor.update(b"b" * 16) with pytest.raises(AlreadyFinalized): -- cgit v1.2.3 From 4664108ac1771b694f917b9ef70d358638371c04 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sun, 24 Nov 2013 11:14:27 -0600 Subject: simplify code for wrapping ciphercontext/aeadciphercontext --- cryptography/hazmat/primitives/ciphers/base.py | 34 +++++++++----------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/cryptography/hazmat/primitives/ciphers/base.py b/cryptography/hazmat/primitives/ciphers/base.py index c8c4533b..3f6ca0fe 100644 --- a/cryptography/hazmat/primitives/ciphers/base.py +++ b/cryptography/hazmat/primitives/ciphers/base.py @@ -30,32 +30,22 @@ class Cipher(object): self._backend = backend def encryptor(self): - if isinstance(self.mode, interfaces.ModeWithAAD): - return _AEADCipherContext( - self._backend.create_symmetric_encryption_ctx( - self.algorithm, self.mode - ) - ) - else: - return _CipherContext( - self._backend.create_symmetric_encryption_ctx( - self.algorithm, self.mode - ) - ) + ctx = self._backend.create_symmetric_encryption_ctx( + self.algorithm, self.mode + ) + return self._wrap_ctx(ctx) def decryptor(self): + ctx = self._backend.create_symmetric_decryption_ctx( + self.algorithm, self.mode + ) + return self._wrap_ctx(ctx) + + def _wrap_ctx(self, ctx): if isinstance(self.mode, interfaces.ModeWithAAD): - return _AEADCipherContext( - self._backend.create_symmetric_decryption_ctx( - self.algorithm, self.mode - ) - ) + return _AEADCipherContext(ctx) else: - return _CipherContext( - self._backend.create_symmetric_decryption_ctx( - self.algorithm, self.mode - ) - ) + return _CipherContext(ctx) @utils.register_interface(interfaces.CipherContext) -- cgit v1.2.3 From 0092c205657789e15848c7848eec768720de468f Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sun, 24 Nov 2013 11:39:14 -0600 Subject: raise TypeError if you attempt to get the tag attribute on a decrypt * To support this the _AEADCipherContext in base.py now needs to be aware of whether it is encrypting/decrypting --- cryptography/hazmat/primitives/ciphers/base.py | 14 +++++++++----- docs/hazmat/primitives/symmetric-encryption.rst | 1 + tests/hazmat/primitives/utils.py | 9 +++++++++ 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/cryptography/hazmat/primitives/ciphers/base.py b/cryptography/hazmat/primitives/ciphers/base.py index 3f6ca0fe..252a9feb 100644 --- a/cryptography/hazmat/primitives/ciphers/base.py +++ b/cryptography/hazmat/primitives/ciphers/base.py @@ -33,17 +33,17 @@ class Cipher(object): ctx = self._backend.create_symmetric_encryption_ctx( self.algorithm, self.mode ) - return self._wrap_ctx(ctx) + return self._wrap_ctx(ctx, True) def decryptor(self): ctx = self._backend.create_symmetric_decryption_ctx( self.algorithm, self.mode ) - return self._wrap_ctx(ctx) + return self._wrap_ctx(ctx, False) - def _wrap_ctx(self, ctx): + def _wrap_ctx(self, ctx, encrypt): if isinstance(self.mode, interfaces.ModeWithAAD): - return _AEADCipherContext(ctx) + return _AEADCipherContext(ctx, encrypt) else: return _CipherContext(ctx) @@ -69,10 +69,11 @@ class _CipherContext(object): @utils.register_interface(interfaces.AEADCipherContext) @utils.register_interface(interfaces.CipherContext) class _AEADCipherContext(object): - def __init__(self, ctx): + def __init__(self, ctx, encrypt): self._ctx = ctx self._tag = None self._updated = False + self._encrypt = encrypt def update(self, data): if self._ctx is None: @@ -97,6 +98,9 @@ class _AEADCipherContext(object): @property def tag(self): + if not self._encrypt: + raise TypeError("The tag attribute is unavailable on a " + "decryption context") if self._ctx is not None: raise NotYetFinalized("You must finalize encryption before " "getting the tag") diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst index d123d15c..f35357d0 100644 --- a/docs/hazmat/primitives/symmetric-encryption.rst +++ b/docs/hazmat/primitives/symmetric-encryption.rst @@ -139,6 +139,7 @@ an "encrypt-then-MAC" formulation as `described by Colin Percival`_. :return bytes: Returns the tag value as bytes. :raises: :class:`~cryptography.exceptions.NotYetFinalized` if called before the context is finalized. + :raises TypeError: If called on a decryption context. .. _symmetric-encryption-algorithms: diff --git a/tests/hazmat/primitives/utils.py b/tests/hazmat/primitives/utils.py index b6f9e0f5..58b9a917 100644 --- a/tests/hazmat/primitives/utils.py +++ b/tests/hazmat/primitives/utils.py @@ -344,3 +344,12 @@ def aead_exception_test(backend, cipher_factory, mode_factory, encryptor.update(b"b" * 16) with pytest.raises(AlreadyFinalized): encryptor.finalize() + cipher = Cipher( + cipher_factory(binascii.unhexlify(b"0" * 32)), + mode_factory(binascii.unhexlify(b"0" * 24), b"0" * 16), + backend + ) + decryptor = cipher.decryptor() + decryptor.update(b"a" * 16) + with pytest.raises(TypeError): + decryptor.tag -- cgit v1.2.3 From a8b35f4a882ddd02fefed69163e9f226eab99ce9 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sun, 24 Nov 2013 11:51:22 -0600 Subject: make _CipherContext in backend.py compliant with AEADCipherContext * Might make more sense to split it into _CipherContext and _AEADCipherContext like we do in base.py, but there would be quite a bit of duplicate code. --- cryptography/hazmat/bindings/openssl/backend.py | 5 +++++ cryptography/hazmat/primitives/ciphers/base.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/cryptography/hazmat/bindings/openssl/backend.py b/cryptography/hazmat/bindings/openssl/backend.py index 1a534011..4d9a8ce5 100644 --- a/cryptography/hazmat/bindings/openssl/backend.py +++ b/cryptography/hazmat/bindings/openssl/backend.py @@ -237,6 +237,7 @@ class GetCipherByName(object): @utils.register_interface(interfaces.CipherContext) +@utils.register_interface(interfaces.AEADCipherContext) class _CipherContext(object): _ENCRYPT = 1 _DECRYPT = 0 @@ -343,6 +344,10 @@ class _CipherContext(object): ) assert res != 0 + @property + def tag(self): + return self._tag + @utils.register_interface(interfaces.HashContext) class _HashContext(object): diff --git a/cryptography/hazmat/primitives/ciphers/base.py b/cryptography/hazmat/primitives/ciphers/base.py index 252a9feb..a6f06b82 100644 --- a/cryptography/hazmat/primitives/ciphers/base.py +++ b/cryptography/hazmat/primitives/ciphers/base.py @@ -85,7 +85,7 @@ class _AEADCipherContext(object): if self._ctx is None: raise AlreadyFinalized("Context was already finalized") data = self._ctx.finalize() - self._tag = self._ctx._tag + self._tag = self._ctx.tag self._ctx = None return data -- cgit v1.2.3 From 86699be866b1ef3390da31f74a63980e4d2b7d99 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sun, 24 Nov 2013 12:17:51 -0600 Subject: narrow the potential cases where invalidtag can be raised --- cryptography/hazmat/bindings/openssl/backend.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cryptography/hazmat/bindings/openssl/backend.py b/cryptography/hazmat/bindings/openssl/backend.py index 4d9a8ce5..e9ecc800 100644 --- a/cryptography/hazmat/bindings/openssl/backend.py +++ b/cryptography/hazmat/bindings/openssl/backend.py @@ -198,10 +198,11 @@ class Backend(object): def create_symmetric_decryption_ctx(self, cipher, mode): return _CipherContext(self, cipher, mode, _CipherContext._DECRYPT) - def _handle_error(self): + def _handle_error(self, mode): code = self.lib.ERR_get_error() - if not code: + if not code and isinstance(mode, GCM): raise InvalidTag + assert code != 0 lib = self.lib.ERR_GET_LIB(code) func = self.lib.ERR_GET_FUNC(code) reason = self.lib.ERR_GET_REASON(code) @@ -320,7 +321,7 @@ class _CipherContext(object): outlen = self._backend.ffi.new("int *") res = self._backend.lib.EVP_CipherFinal_ex(self._ctx, buf, outlen) if res == 0: - self._backend._handle_error() + self._backend._handle_error(self._mode) if (isinstance(self._mode, GCM) and self._operation == self._ENCRYPT): -- cgit v1.2.3 From 67abc864cb64033333aa08a03fba1dd153074dfd Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Mon, 25 Nov 2013 14:29:35 -0600 Subject: document tag param for GCM object --- docs/hazmat/primitives/symmetric-encryption.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst index f35357d0..70c3d2f4 100644 --- a/docs/hazmat/primitives/symmetric-encryption.rst +++ b/docs/hazmat/primitives/symmetric-encryption.rst @@ -335,6 +335,9 @@ Modes ``initialization_vector`` with a given ``key``. + :param bytes tag: The tag bytes to verify during decryption. Must be provided + for decryption, but is ignored when encrypting. + .. doctest:: >>> from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes -- cgit v1.2.3 From 26c8c6adcb9a6485966070418080a17cd2445bed Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Fri, 29 Nov 2013 16:24:56 -0600 Subject: begin adding warnings to GCM mode --- docs/hazmat/primitives/symmetric-encryption.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst index 70c3d2f4..a77e0e79 100644 --- a/docs/hazmat/primitives/symmetric-encryption.rst +++ b/docs/hazmat/primitives/symmetric-encryption.rst @@ -320,6 +320,12 @@ Modes .. class:: GCM(initialization_vector, tag=None) + .. warning:: + + When using this mode you MUST not use the decrypted data until every + byte has been decrypted. GCM provides NO guarantees of ciphertext + integrity until decryption is complete. + GCM (Galois Counter Mode) is a mode of operation for block ciphers. It is an AEAD (authenticated encryption with additional data) mode. AEAD is a type of block cipher mode that encrypts the message as well as -- cgit v1.2.3 From e0b5bb18c3963ebaa66d537d2cf00c2cc0dd804c Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Fri, 29 Nov 2013 16:35:04 -0600 Subject: explicit backend fix for gcm docs --- docs/hazmat/primitives/symmetric-encryption.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst index a77e0e79..aefc2d7e 100644 --- a/docs/hazmat/primitives/symmetric-encryption.rst +++ b/docs/hazmat/primitives/symmetric-encryption.rst @@ -347,12 +347,12 @@ Modes .. doctest:: >>> from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes - >>> cipher = Cipher(algorithms.AES(key), modes.GCM(iv)) + >>> cipher = Cipher(algorithms.AES(key), modes.GCM(iv), backend) >>> encryptor = cipher.encryptor() >>> encryptor.authenticate_additional_data(b"authenticated but not encrypted payload") >>> ct = encryptor.update(b"a secret message") + encryptor.finalize() >>> tag = encryptor.tag - >>> cipher = Cipher(algorithms.AES(key), modes.GCM(iv, tag)) + >>> cipher = Cipher(algorithms.AES(key), modes.GCM(iv, tag), backend) >>> decryptor = cipher.decryptor() >>> decryptor.authenticate_additional_data(b"authenticated but not encrypted payload") >>> decryptor.update(ct) + decryptor.finalize() -- cgit v1.2.3 From 5a40896cbeae2cc2673c86aa18d3953314e760ba Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Fri, 29 Nov 2013 17:19:25 -0600 Subject: create AEADEncryptionContext and DecryptionContext --- cryptography/hazmat/bindings/openssl/backend.py | 2 ++ cryptography/hazmat/primitives/ciphers/base.py | 19 +++++++++++++------ cryptography/hazmat/primitives/interfaces.py | 16 +++++++++++----- tests/hazmat/primitives/utils.py | 2 +- 4 files changed, 27 insertions(+), 12 deletions(-) diff --git a/cryptography/hazmat/bindings/openssl/backend.py b/cryptography/hazmat/bindings/openssl/backend.py index e9ecc800..7d3eb3d7 100644 --- a/cryptography/hazmat/bindings/openssl/backend.py +++ b/cryptography/hazmat/bindings/openssl/backend.py @@ -239,6 +239,8 @@ class GetCipherByName(object): @utils.register_interface(interfaces.CipherContext) @utils.register_interface(interfaces.AEADCipherContext) +@utils.register_interface(interfaces.AEADEncryptionContext) +@utils.register_interface(interfaces.AEADDecryptionContext) class _CipherContext(object): _ENCRYPT = 1 _DECRYPT = 0 diff --git a/cryptography/hazmat/primitives/ciphers/base.py b/cryptography/hazmat/primitives/ciphers/base.py index a6f06b82..f24fd000 100644 --- a/cryptography/hazmat/primitives/ciphers/base.py +++ b/cryptography/hazmat/primitives/ciphers/base.py @@ -43,7 +43,10 @@ class Cipher(object): def _wrap_ctx(self, ctx, encrypt): if isinstance(self.mode, interfaces.ModeWithAAD): - return _AEADCipherContext(ctx, encrypt) + if encrypt: + return _AEADEncryptionContext(ctx) + else: + return _AEADDecryptionContext(ctx) else: return _CipherContext(ctx) @@ -69,11 +72,10 @@ class _CipherContext(object): @utils.register_interface(interfaces.AEADCipherContext) @utils.register_interface(interfaces.CipherContext) class _AEADCipherContext(object): - def __init__(self, ctx, encrypt): + def __init__(self, ctx): self._ctx = ctx self._tag = None self._updated = False - self._encrypt = encrypt def update(self, data): if self._ctx is None: @@ -96,11 +98,16 @@ class _AEADCipherContext(object): raise AlreadyUpdated("Update has been called on this context") self._ctx.authenticate_additional_data(data) + +@utils.register_interface(interfaces.AEADDecryptionContext) +class _AEADDecryptionContext(_AEADCipherContext): + pass + + +@utils.register_interface(interfaces.AEADEncryptionContext) +class _AEADEncryptionContext(_AEADCipherContext): @property def tag(self): - if not self._encrypt: - raise TypeError("The tag attribute is unavailable on a " - "decryption context") if self._ctx is not None: raise NotYetFinalized("You must finalize encryption before " "getting the tag") diff --git a/cryptography/hazmat/primitives/interfaces.py b/cryptography/hazmat/primitives/interfaces.py index c0548dfd..1884e560 100644 --- a/cryptography/hazmat/primitives/interfaces.py +++ b/cryptography/hazmat/primitives/interfaces.py @@ -79,17 +79,23 @@ class CipherContext(six.with_metaclass(abc.ABCMeta)): class AEADCipherContext(six.with_metaclass(abc.ABCMeta)): + @abc.abstractmethod + def authenticate_additional_data(self, data): + """ + authenticate_additional_data takes bytes and returns nothing. + """ + + +class AEADEncryptionContext(six.with_metaclass(abc.ABCMeta)): @abc.abstractproperty def tag(self): """ Returns tag bytes after finalizing encryption. """ - @abc.abstractmethod - def authenticate_additional_data(self, data): - """ - authenticate_additional_data takes bytes and returns nothing. - """ + +class AEADDecryptionContext(six.with_metaclass(abc.ABCMeta)): + pass class PaddingContext(six.with_metaclass(abc.ABCMeta)): diff --git a/tests/hazmat/primitives/utils.py b/tests/hazmat/primitives/utils.py index 58b9a917..9aa3a89a 100644 --- a/tests/hazmat/primitives/utils.py +++ b/tests/hazmat/primitives/utils.py @@ -351,5 +351,5 @@ def aead_exception_test(backend, cipher_factory, mode_factory, ) decryptor = cipher.decryptor() decryptor.update(b"a" * 16) - with pytest.raises(TypeError): + with pytest.raises(AttributeError): decryptor.tag -- cgit v1.2.3 From 5b828b142b4e8fea021567038e2dba6cf6cd9221 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Fri, 29 Nov 2013 17:32:08 -0600 Subject: attempt to document the new interfaces for AEAD --- docs/hazmat/primitives/symmetric-encryption.rst | 26 +++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst index aefc2d7e..9d4f0355 100644 --- a/docs/hazmat/primitives/symmetric-encryption.rst +++ b/docs/hazmat/primitives/symmetric-encryption.rst @@ -122,24 +122,38 @@ an "encrypt-then-MAC" formulation as `described by Colin Percival`_. When calling ``encryptor()`` or ``decryptor()`` on a ``Cipher`` object with an AEAD mode you will receive a return object conforming to the - ``AEADCipherContext`` interface, in addition to the ``CipherContext`` - interface. ``AEADCipherContext`` contains an additional method + ``AEADCipherContext`` interface (in addition to the ``CipherContext`` + interface and either the ``AEADEncryptionContext`` or ``AEADDecryptionContext`` + interface). ``AEADCipherContext`` contains an additional method ``authenticate_additional_data`` for adding additional authenticated but unencrypted data. You should call this before calls to ``update``. When you - are done call ``finalize()`` to finish the operation. Once this is complete - you can obtain the tag value from the ``tag`` property. + are done call ``finalize()`` to finish the operation. .. method:: authenticate_additional_data(data) :param bytes data: The data you wish to authenticate but not encrypt. :raises: :class:`~cryptography.exceptions.AlreadyFinalized` +.. class:: AEADEncryptionContext + + When creating an encryption context using ``encryptor()`` on a ``Cipher`` + object with an AEAD mode you will receive a return object conforming to the + ``AEADEncryptionContext`` interface (as well as ``AEADCipherContext``). + This interface provides one additional attribute ``tag``. ``tag`` can only + be obtained after ``finalize()``. + .. attribute:: tag :return bytes: Returns the tag value as bytes. :raises: :class:`~cryptography.exceptions.NotYetFinalized` if called before the context is finalized. - :raises TypeError: If called on a decryption context. + +.. class:: AEADDecryptionContext + + When creating an encryption context using ``encryptor()`` on a ``Cipher`` + object with an AEAD mode you will receive a return object conforming to the + ``AEADDecryptionContext`` interface (as well as ``AEADCipherContext``). This + interface does not provide any additional methods or attributes. .. _symmetric-encryption-algorithms: @@ -320,7 +334,7 @@ Modes .. class:: GCM(initialization_vector, tag=None) - .. warning:: + .. danger:: When using this mode you MUST not use the decrypted data until every byte has been decrypted. GCM provides NO guarantees of ciphertext -- cgit v1.2.3 From 89d19a411edba0cb52da89801e3de1ddfd9f0dc5 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 3 Dec 2013 17:30:19 -0600 Subject: rename ModeWithAAD to ModeWithAuthenticationTag --- cryptography/hazmat/primitives/ciphers/base.py | 2 +- cryptography/hazmat/primitives/ciphers/modes.py | 2 +- cryptography/hazmat/primitives/interfaces.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cryptography/hazmat/primitives/ciphers/base.py b/cryptography/hazmat/primitives/ciphers/base.py index f24fd000..cdaf2c0c 100644 --- a/cryptography/hazmat/primitives/ciphers/base.py +++ b/cryptography/hazmat/primitives/ciphers/base.py @@ -42,7 +42,7 @@ class Cipher(object): return self._wrap_ctx(ctx, False) def _wrap_ctx(self, ctx, encrypt): - if isinstance(self.mode, interfaces.ModeWithAAD): + if isinstance(self.mode, interfaces.ModeWithAuthenticationTag): if encrypt: return _AEADEncryptionContext(ctx) else: diff --git a/cryptography/hazmat/primitives/ciphers/modes.py b/cryptography/hazmat/primitives/ciphers/modes.py index cb191d98..e1c70185 100644 --- a/cryptography/hazmat/primitives/ciphers/modes.py +++ b/cryptography/hazmat/primitives/ciphers/modes.py @@ -60,7 +60,7 @@ class CTR(object): @utils.register_interface(interfaces.Mode) @utils.register_interface(interfaces.ModeWithInitializationVector) -@utils.register_interface(interfaces.ModeWithAAD) +@utils.register_interface(interfaces.ModeWithAuthenticationTag) class GCM(object): name = "GCM" diff --git a/cryptography/hazmat/primitives/interfaces.py b/cryptography/hazmat/primitives/interfaces.py index 1884e560..582876fe 100644 --- a/cryptography/hazmat/primitives/interfaces.py +++ b/cryptography/hazmat/primitives/interfaces.py @@ -56,7 +56,7 @@ class ModeWithNonce(six.with_metaclass(abc.ABCMeta)): """ -class ModeWithAAD(six.with_metaclass(abc.ABCMeta)): +class ModeWithAuthenticationTag(six.with_metaclass(abc.ABCMeta)): @abc.abstractproperty def tag(self): """ -- cgit v1.2.3 From 128b948b3cb5840b35ae12613b2215bfe9f098bd Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 3 Dec 2013 17:34:26 -0600 Subject: remove AEADDecryptionContext per review. decryption will return AEADCipherContext and encryption returns AEADEncryptionContext --- cryptography/hazmat/bindings/openssl/backend.py | 1 - cryptography/hazmat/primitives/ciphers/base.py | 7 +------ cryptography/hazmat/primitives/interfaces.py | 4 ---- 3 files changed, 1 insertion(+), 11 deletions(-) diff --git a/cryptography/hazmat/bindings/openssl/backend.py b/cryptography/hazmat/bindings/openssl/backend.py index 7d3eb3d7..1b19ddaa 100644 --- a/cryptography/hazmat/bindings/openssl/backend.py +++ b/cryptography/hazmat/bindings/openssl/backend.py @@ -240,7 +240,6 @@ class GetCipherByName(object): @utils.register_interface(interfaces.CipherContext) @utils.register_interface(interfaces.AEADCipherContext) @utils.register_interface(interfaces.AEADEncryptionContext) -@utils.register_interface(interfaces.AEADDecryptionContext) class _CipherContext(object): _ENCRYPT = 1 _DECRYPT = 0 diff --git a/cryptography/hazmat/primitives/ciphers/base.py b/cryptography/hazmat/primitives/ciphers/base.py index cdaf2c0c..b8615cb9 100644 --- a/cryptography/hazmat/primitives/ciphers/base.py +++ b/cryptography/hazmat/primitives/ciphers/base.py @@ -46,7 +46,7 @@ class Cipher(object): if encrypt: return _AEADEncryptionContext(ctx) else: - return _AEADDecryptionContext(ctx) + return _AEADCipherContext(ctx) else: return _CipherContext(ctx) @@ -99,11 +99,6 @@ class _AEADCipherContext(object): self._ctx.authenticate_additional_data(data) -@utils.register_interface(interfaces.AEADDecryptionContext) -class _AEADDecryptionContext(_AEADCipherContext): - pass - - @utils.register_interface(interfaces.AEADEncryptionContext) class _AEADEncryptionContext(_AEADCipherContext): @property diff --git a/cryptography/hazmat/primitives/interfaces.py b/cryptography/hazmat/primitives/interfaces.py index 582876fe..e3f4f586 100644 --- a/cryptography/hazmat/primitives/interfaces.py +++ b/cryptography/hazmat/primitives/interfaces.py @@ -94,10 +94,6 @@ class AEADEncryptionContext(six.with_metaclass(abc.ABCMeta)): """ -class AEADDecryptionContext(six.with_metaclass(abc.ABCMeta)): - pass - - class PaddingContext(six.with_metaclass(abc.ABCMeta)): @abc.abstractmethod def update(self, data): -- cgit v1.2.3 From 5578c66babf3cb214114617bdd29c28129f31c37 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 3 Dec 2013 17:37:42 -0600 Subject: improve language for gcm docs --- docs/hazmat/primitives/symmetric-encryption.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst index 9d4f0355..c97d6b0b 100644 --- a/docs/hazmat/primitives/symmetric-encryption.rst +++ b/docs/hazmat/primitives/symmetric-encryption.rst @@ -340,11 +340,11 @@ Modes byte has been decrypted. GCM provides NO guarantees of ciphertext integrity until decryption is complete. - GCM (Galois Counter Mode) is a mode of operation for block ciphers. It - is an AEAD (authenticated encryption with additional data) mode. AEAD - is a type of block cipher mode that encrypts the message as well as - authenticating it (and optionally additional data that is not encrypted) - simultaneously. Additional means of verifying integrity (like + GCM (Galois Counter Mode) is a mode of operation for block ciphers. An + AEAD (authenticated encryption with additional data) mode is a type of + block cipher mode that encrypts the message as well as authenticating it + (and optionally additional data that is not encrypted) simultaneously. + Additional means of verifying integrity (like :doc:`HMAC `) are not necessary. :param bytes initialization_vector: Must be random bytes. They do not need -- cgit v1.2.3 From cd28a7cca32c734ddd7f7ad353b27b2cf276aa6e Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 3 Dec 2013 18:17:57 -0600 Subject: remove AEADDecryptionContext references from GCM docs --- docs/hazmat/primitives/symmetric-encryption.rst | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst index c97d6b0b..bb0308bc 100644 --- a/docs/hazmat/primitives/symmetric-encryption.rst +++ b/docs/hazmat/primitives/symmetric-encryption.rst @@ -123,11 +123,11 @@ an "encrypt-then-MAC" formulation as `described by Colin Percival`_. When calling ``encryptor()`` or ``decryptor()`` on a ``Cipher`` object with an AEAD mode you will receive a return object conforming to the ``AEADCipherContext`` interface (in addition to the ``CipherContext`` - interface and either the ``AEADEncryptionContext`` or ``AEADDecryptionContext`` - interface). ``AEADCipherContext`` contains an additional method - ``authenticate_additional_data`` for adding additional authenticated but - unencrypted data. You should call this before calls to ``update``. When you - are done call ``finalize()`` to finish the operation. + interface). If it is an encryption context it will additionally be an + ``AEADEncryptionContext`` interface. ``AEADCipherContext`` contains an + additional method ``authenticate_additional_data`` for adding additional + authenticated but unencrypted data. You should call this before calls to + ``update``. When you are done call ``finalize()`` to finish the operation. .. method:: authenticate_additional_data(data) @@ -148,13 +148,6 @@ an "encrypt-then-MAC" formulation as `described by Colin Percival`_. :raises: :class:`~cryptography.exceptions.NotYetFinalized` if called before the context is finalized. -.. class:: AEADDecryptionContext - - When creating an encryption context using ``encryptor()`` on a ``Cipher`` - object with an AEAD mode you will receive a return object conforming to the - ``AEADDecryptionContext`` interface (as well as ``AEADCipherContext``). This - interface does not provide any additional methods or attributes. - .. _symmetric-encryption-algorithms: Algorithms -- cgit v1.2.3 From 672843712d6b42404fea27a07a87b70d850cc0dd Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 3 Dec 2013 18:58:14 -0600 Subject: link to NIST GCM PDF where NIST recommends 96-bit IV for perf with GCM Clarify that 96-bit IV is only recommended in performance critical situations...otherwise feel free to use something longer. --- docs/hazmat/primitives/symmetric-encryption.rst | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst index bb0308bc..8d8d558b 100644 --- a/docs/hazmat/primitives/symmetric-encryption.rst +++ b/docs/hazmat/primitives/symmetric-encryption.rst @@ -342,11 +342,12 @@ Modes :param bytes initialization_vector: Must be random bytes. They do not need to be kept secret (they can be included - in a transmitted message). Recommended - to be 96-bit by NIST, but can be up to - 2\ :sup:`64` - 1 bits. Do not reuse an - ``initialization_vector`` with a given - ``key``. + in a transmitted message). NIST + `recommends 96-bit IV length`_ for + performance critical situations, but it + can be up to 2\ :sup:`64` - 1 bits. + Do not reuse an ``initialization_vector`` + with a given ``key``. :param bytes tag: The tag bytes to verify during decryption. Must be provided for decryption, but is ignored when encrypting. @@ -384,3 +385,4 @@ Insecure Modes .. _`described by Colin Percival`: http://www.daemonology.net/blog/2009-06-11-cryptographic-right-answers.html +.. _`recommends 96-bit IV length`: http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-spec.pdf -- cgit v1.2.3