From 620c2aec10423c11e49cbffc71efe19a190f9187 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 19 Oct 2013 14:12:04 -0500 Subject: block cipher decryption support This is a squash of previous commits plus new ones. Ran into a pile of conflicts during the rebase and decided this was an easier way to retain a sane commit history --- cryptography/bindings/openssl/api.py | 70 +++++++++++++++++++++++++---------- cryptography/bindings/openssl/evp.py | 5 +++ cryptography/primitives/block/base.py | 60 +++++++++++++++++++----------- tests/primitives/test_block.py | 54 +++++++++++++++------------ tests/primitives/utils.py | 9 ++++- 5 files changed, 132 insertions(+), 66 deletions(-) diff --git a/cryptography/bindings/openssl/api.py b/cryptography/bindings/openssl/api.py index f5e042e7..fdac4e91 100644 --- a/cryptography/bindings/openssl/api.py +++ b/cryptography/bindings/openssl/api.py @@ -98,9 +98,25 @@ class API(object): return (self.ffi.NULL != self.lib.EVP_get_cipherbyname(ciphername.encode("ascii"))) - def create_block_cipher_context(self, cipher, mode): - ctx = self.lib.EVP_CIPHER_CTX_new() - ctx = self.ffi.gc(ctx, self.lib.EVP_CIPHER_CTX_free) + def create_block_cipher_encrypt_context(self, cipher, mode): + ctx, args = self._create_block_cipher_context(cipher, mode) + res = self.lib.EVP_EncryptInit_ex(*args) + assert res != 0 + self._disable_padding(ctx) + return ctx + + def create_block_cipher_decrypt_context(self, cipher, mode): + ctx, args = self._create_block_cipher_context(cipher, mode) + res = self.lib.EVP_DecryptInit_ex(*args) + assert res != 0 + self._disable_padding(ctx) + return ctx + + def _create_block_cipher_context(self, cipher, mode): + ctx = self.ffi.new("EVP_CIPHER_CTX *") + res = self.lib.EVP_CIPHER_CTX_init(ctx) + assert res != 0 + ctx = self.ffi.gc(ctx, self.lib.EVP_CIPHER_CTX_cleanup) # TODO: compute name using a better algorithm ciphername = "{0}-{1}-{2}".format( cipher.name, cipher.key_size, mode.name @@ -114,36 +130,51 @@ class API(object): else: iv_nonce = self.ffi.NULL - # TODO: Sometimes this needs to be a DecryptInit, when? - res = self.lib.EVP_EncryptInit_ex( - ctx, evp_cipher, self.ffi.NULL, cipher.key, iv_nonce - ) - assert res != 0 + return (ctx, (ctx, evp_cipher, self.ffi.NULL, cipher.key, iv_nonce)) + def _disable_padding(self, ctx): # We purposely disable padding here as it's handled higher up in the # API. self.lib.EVP_CIPHER_CTX_set_padding(ctx, 0) - return ctx - def update_encrypt_context(self, ctx, plaintext): - block_size = self.lib.EVP_CIPHER_CTX_block_size(ctx) - buf = self.ffi.new("unsigned char[]", len(plaintext) + block_size - 1) - outlen = self.ffi.new("int *") - res = self.lib.EVP_EncryptUpdate( - ctx, buf, outlen, plaintext, len(plaintext) - ) + def update_encrypt_context(self, ctx, data): + buf, outlen = self._create_buf_out(ctx, len(data)) + res = self.lib.EVP_EncryptUpdate(ctx, buf, outlen, data, len(data)) assert res != 0 return self.ffi.buffer(buf)[:outlen[0]] - def finalize_encrypt_context(self, ctx): + def update_decrypt_context(self, ctx, data): + buf, outlen = self._create_buf_out(ctx, len(data)) + res = self.lib.EVP_DecryptUpdate(ctx, buf, outlen, data, len(data)) + assert res != 0 + return self.ffi.buffer(buf)[:outlen[0]] + + def _create_buf_out(self, ctx, data_len): block_size = self.lib.EVP_CIPHER_CTX_block_size(ctx) - buf = self.ffi.new("unsigned char[]", block_size) + buf = self.ffi.new("unsigned char[]", data_len + block_size - 1) outlen = self.ffi.new("int *") + return (buf, outlen) + + def finalize_encrypt_context(self, ctx): + buf, outlen = self._create_final_buf_out(ctx) res = self.lib.EVP_EncryptFinal_ex(ctx, buf, outlen) assert res != 0 + self._cleanup_block_cipher(ctx) + return self.ffi.buffer(buf)[:outlen[0]] + + def finalize_decrypt_context(self, ctx): + buf, outlen = self._create_final_buf_out(ctx) + res = self.lib.EVP_DecryptFinal_ex(ctx, buf, outlen) + assert res != 0 + self._cleanup_block_cipher(ctx) + return self.ffi.buffer(buf)[:outlen[0]] + + def _create_final_buf_out(self, ctx): + return self._create_buf_out(ctx, 1) + + def _cleanup_block_cipher(self, ctx): res = self.lib.EVP_CIPHER_CTX_cleanup(ctx) assert res == 1 - return self.ffi.buffer(buf)[:outlen[0]] def supports_hash(self, hash_cls): return (self.ffi.NULL != @@ -177,5 +208,4 @@ class API(object): assert res != 0 return copied_ctx - api = API() diff --git a/cryptography/bindings/openssl/evp.py b/cryptography/bindings/openssl/evp.py index 2bb5b0f7..41df1056 100644 --- a/cryptography/bindings/openssl/evp.py +++ b/cryptography/bindings/openssl/evp.py @@ -41,6 +41,11 @@ int EVP_CIPHER_CTX_set_padding(EVP_CIPHER_CTX *, int); int EVP_EncryptUpdate(EVP_CIPHER_CTX *, unsigned char *, int *, const unsigned char *, int); int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *, unsigned char *, int *); +int EVP_DecryptInit_ex(EVP_CIPHER_CTX *, const EVP_CIPHER *, ENGINE *, + const unsigned char *, const unsigned char *); +int EVP_DecryptUpdate(EVP_CIPHER_CTX *, unsigned char *, int *, + const unsigned char *, int); +int EVP_DecryptFinal_ex(EVP_CIPHER_CTX *, unsigned char *, int *); int EVP_CIPHER_CTX_cleanup(EVP_CIPHER_CTX *); const EVP_CIPHER *EVP_CIPHER_CTX_cipher(const EVP_CIPHER_CTX *); int EVP_CIPHER_block_size(const EVP_CIPHER *); diff --git a/cryptography/primitives/block/base.py b/cryptography/primitives/block/base.py index 50e9e9e5..b6f45778 100644 --- a/cryptography/primitives/block/base.py +++ b/cryptography/primitives/block/base.py @@ -13,14 +13,11 @@ from __future__ import absolute_import, division, print_function -from enum import Enum - -from cryptography.bindings import _default_api +import abc +import six -class _Operation(Enum): - encrypt = 0 - decrypt = 1 +from cryptography.bindings import _default_api class BlockCipher(object): @@ -33,30 +30,51 @@ class BlockCipher(object): self.cipher = cipher self.mode = mode self._api = api - self._ctx = api.create_block_cipher_context(cipher, mode) - self._operation = None - def encrypt(self, plaintext): - if self._ctx is None: - raise ValueError("BlockCipher was already finalized") + def encryptor(self): + return _BlockCipherEncryptionContext(self.cipher, self.mode, self._api) + + def decryptor(self): + return _BlockCipherDecryptionContext(self.cipher, self.mode, self._api) - if self._operation is None: - self._operation = _Operation.encrypt - elif self._operation is not _Operation.encrypt: - raise ValueError("BlockCipher cannot encrypt when the operation is" - " set to %s" % self._operation.name) - return self._api.update_encrypt_context(self._ctx, plaintext) +class _BlockCipherContext(six.with_metaclass(abc.ABCMeta)): + def __init__(self, cipher, mode, api): + super(_BlockCipherContext, self).__init__() + self.cipher = cipher + self.mode = mode + self._api = api + if isinstance(self, _BlockCipherEncryptionContext): + ctx_method = self._api.create_block_cipher_encrypt_context + else: + ctx_method = self._api.create_block_cipher_decrypt_context + self._ctx = ctx_method(self.cipher, self.mode) def finalize(self): if self._ctx is None: - raise ValueError("BlockCipher was already finalized") + raise ValueError("Context was already finalized") - if self._operation is _Operation.encrypt: + if isinstance(self, _BlockCipherEncryptionContext): result = self._api.finalize_encrypt_context(self._ctx) else: - raise ValueError("BlockCipher cannot finalize the unknown " - "operation %s" % self._operation.name) + result = self._api.finalize_decrypt_context(self._ctx) self._ctx = None return result + + def update(self, data): + if self._ctx is None: + raise ValueError("Context was already finalized") + + if isinstance(self, _BlockCipherEncryptionContext): + return self._api.update_encrypt_context(self._ctx, data) + else: + return self._api.update_decrypt_context(self._ctx, data) + + +class _BlockCipherEncryptionContext(_BlockCipherContext): + pass + + +class _BlockCipherDecryptionContext(_BlockCipherContext): + pass diff --git a/tests/primitives/test_block.py b/tests/primitives/test_block.py index 9f5905bf..4a67002f 100644 --- a/tests/primitives/test_block.py +++ b/tests/primitives/test_block.py @@ -15,11 +15,9 @@ from __future__ import absolute_import, division, print_function import binascii -import pretend import pytest from cryptography.primitives.block import BlockCipher, ciphers, modes -from cryptography.primitives.block.base import _Operation class TestBlockCipher(object): @@ -29,40 +27,42 @@ class TestBlockCipher(object): modes.CBC(binascii.unhexlify(b"0" * 32)) ) - def test_use_after_finalize(self, api): + def test_creates_encryptor(self): cipher = BlockCipher( ciphers.AES(binascii.unhexlify(b"0" * 32)), - modes.CBC(binascii.unhexlify(b"0" * 32)), - api + modes.CBC(binascii.unhexlify(b"0" * 32)) ) - cipher.encrypt(b"a" * 16) - cipher.finalize() - with pytest.raises(ValueError): - cipher.encrypt(b"b" * 16) - with pytest.raises(ValueError): - cipher.finalize() + assert cipher.encryptor() is not None - def test_encrypt_with_invalid_operation(self, api): + def test_creates_decryptor(self): cipher = BlockCipher( ciphers.AES(binascii.unhexlify(b"0" * 32)), - modes.CBC(binascii.unhexlify(b"0" * 32)), - api + modes.CBC(binascii.unhexlify(b"0" * 32)) ) - cipher._operation = _Operation.decrypt + assert cipher.decryptor() is not None - with pytest.raises(ValueError): - cipher.encrypt(b"b" * 16) - def test_finalize_with_invalid_operation(self, api): +class TestBlockCipherContext(object): + def test_use_after_finalize(self, api): cipher = BlockCipher( ciphers.AES(binascii.unhexlify(b"0" * 32)), modes.CBC(binascii.unhexlify(b"0" * 32)), api ) - cipher._operation = pretend.stub(name="wat") - + context = cipher.encryptor() + context.update(b"a" * 16) + context.finalize() + with pytest.raises(ValueError): + context.update(b"b" * 16) + with pytest.raises(ValueError): + context.finalize() + context = cipher.decryptor() + context.update(b"a" * 16) + context.finalize() + with pytest.raises(ValueError): + context.update(b"b" * 16) with pytest.raises(ValueError): - cipher.finalize() + context.finalize() def test_unaligned_block_encryption(self, api): cipher = BlockCipher( @@ -70,7 +70,15 @@ class TestBlockCipher(object): modes.ECB(), api ) - ct = cipher.encrypt(b"a" * 15) + context = cipher.encryptor() + ct = context.update(b"a" * 15) assert ct == b"" - ct += cipher.encrypt(b"a" * 65) + ct += context.update(b"a" * 65) assert len(ct) == 80 + ct += context.finalize() + context = cipher.decryptor() + pt = context.update(ct[:3]) + assert pt == b"" + pt += context.update(ct[3:]) + assert len(pt) == 80 + context.finalize() diff --git a/tests/primitives/utils.py b/tests/primitives/utils.py index a3759b03..70ece52a 100644 --- a/tests/primitives/utils.py +++ b/tests/primitives/utils.py @@ -37,9 +37,14 @@ def encrypt_test(api, cipher_factory, mode_factory, params, only_if, mode_factory(**params), api ) - actual_ciphertext = cipher.encrypt(binascii.unhexlify(plaintext)) - actual_ciphertext += cipher.finalize() + encryptor = cipher.encryptor() + actual_ciphertext = encryptor.update(binascii.unhexlify(plaintext)) + actual_ciphertext += encryptor.finalize() assert actual_ciphertext == binascii.unhexlify(ciphertext) + decryptor = cipher.decryptor() + actual_plaintext = decryptor.update(binascii.unhexlify(ciphertext)) + actual_plaintext += decryptor.finalize() + assert actual_plaintext == binascii.unhexlify(plaintext) def generate_hash_test(param_loader, path, file_names, hash_cls, -- cgit v1.2.3 From dec09fc490a09785744e4885f705f231c7ba1eec Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 19 Oct 2013 17:23:16 -0500 Subject: update docs to reflect new encryptor API --- docs/primitives/symmetric-encryption.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/primitives/symmetric-encryption.rst b/docs/primitives/symmetric-encryption.rst index 7899e67d..4f404789 100644 --- a/docs/primitives/symmetric-encryption.rst +++ b/docs/primitives/symmetric-encryption.rst @@ -21,7 +21,8 @@ where the encrypter and decrypter both use the same key. >>> from cryptography.primitives.block import BlockCipher, ciphers, modes >>> cipher = BlockCipher(ciphers.AES(key), modes.CBC(iv)) - >>> cipher.encrypt(b"a secret message") + cipher.finalize() + >>> context = cipher.encryptor() + >>> context.update(b"a secret message") + context.finalize() '...' :param cipher: One of the ciphers described below. -- cgit v1.2.3 From afe8a896de570ab3135938a8e756bfa055fde241 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 19 Oct 2013 17:23:58 -0500 Subject: modified approach to encryption/decryption contexts --- cryptography/bindings/openssl/api.py | 1 + cryptography/primitives/block/base.py | 46 ++++++++++++++++++----------------- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/cryptography/bindings/openssl/api.py b/cryptography/bindings/openssl/api.py index fdac4e91..fc61c4b8 100644 --- a/cryptography/bindings/openssl/api.py +++ b/cryptography/bindings/openssl/api.py @@ -208,4 +208,5 @@ class API(object): assert res != 0 return copied_ctx + api = API() diff --git a/cryptography/primitives/block/base.py b/cryptography/primitives/block/base.py index b6f45778..adade3ca 100644 --- a/cryptography/primitives/block/base.py +++ b/cryptography/primitives/block/base.py @@ -44,37 +44,39 @@ class _BlockCipherContext(six.with_metaclass(abc.ABCMeta)): self.cipher = cipher self.mode = mode self._api = api - if isinstance(self, _BlockCipherEncryptionContext): - ctx_method = self._api.create_block_cipher_encrypt_context - else: - ctx_method = self._api.create_block_cipher_decrypt_context - self._ctx = ctx_method(self.cipher, self.mode) - def finalize(self): + def _check_ctx(self): if self._ctx is None: raise ValueError("Context was already finalized") - if isinstance(self, _BlockCipherEncryptionContext): - result = self._api.finalize_encrypt_context(self._ctx) - else: - result = self._api.finalize_decrypt_context(self._ctx) - self._ctx = None - return result +class _BlockCipherEncryptionContext(_BlockCipherContext): + def __init__(self, cipher, mode, api): + super(_BlockCipherEncryptionContext, self).__init__(cipher, mode, api) + self._ctx = self._api.create_block_cipher_encrypt_context(cipher, mode) def update(self, data): - if self._ctx is None: - raise ValueError("Context was already finalized") + self._check_ctx() + return self._api.update_encrypt_context(self._ctx, data) - if isinstance(self, _BlockCipherEncryptionContext): - return self._api.update_encrypt_context(self._ctx, data) - else: - return self._api.update_decrypt_context(self._ctx, data) + def finalize(self): + self._check_ctx() + data = self._api.finalize_encrypt_context(self._ctx) + self._ctx = None + return data -class _BlockCipherEncryptionContext(_BlockCipherContext): - pass +class _BlockCipherDecryptionContext(_BlockCipherContext): + def __init__(self, cipher, mode, api): + super(_BlockCipherDecryptionContext, self).__init__(cipher, mode, api) + self._ctx = self._api.create_block_cipher_decrypt_context(cipher, mode) + def update(self, data): + self._check_ctx() + return self._api.update_decrypt_context(self._ctx, data) -class _BlockCipherDecryptionContext(_BlockCipherContext): - pass + def finalize(self): + self._check_ctx() + data = self._api.finalize_decrypt_context(self._ctx) + self._ctx = None + return data -- cgit v1.2.3 From e98867acf056857d6e9b005fd00c07de2c31570f Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 19 Oct 2013 17:49:51 -0500 Subject: further simplify context objects --- cryptography/primitives/block/base.py | 67 +++++++++++++++-------------------- 1 file changed, 29 insertions(+), 38 deletions(-) diff --git a/cryptography/primitives/block/base.py b/cryptography/primitives/block/base.py index adade3ca..650e39c1 100644 --- a/cryptography/primitives/block/base.py +++ b/cryptography/primitives/block/base.py @@ -13,13 +13,16 @@ from __future__ import absolute_import, division, print_function -import abc - -import six +from enum import Enum from cryptography.bindings import _default_api +class _Operation(Enum): + encrypt = 0 + decrypt = 1 + + class BlockCipher(object): def __init__(self, cipher, mode, api=None): super(BlockCipher, self).__init__() @@ -32,51 +35,39 @@ class BlockCipher(object): self._api = api def encryptor(self): - return _BlockCipherEncryptionContext(self.cipher, self.mode, self._api) + return _BlockCipherContext(self.cipher, self.mode, self._api, + _Operation.encrypt) def decryptor(self): - return _BlockCipherDecryptionContext(self.cipher, self.mode, self._api) + return _BlockCipherContext(self.cipher, self.mode, self._api, + _Operation.decrypt) -class _BlockCipherContext(six.with_metaclass(abc.ABCMeta)): - def __init__(self, cipher, mode, api): +class _BlockCipherContext(object): + def __init__(self, cipher, mode, api, operation): super(_BlockCipherContext, self).__init__() - self.cipher = cipher - self.mode = mode self._api = api + self._operation = operation + args = (cipher, mode) + if self._operation == _Operation.encrypt: + self._ctx = self._api.create_block_cipher_encrypt_context(*args) + else: + self._ctx = self._api.create_block_cipher_decrypt_context(*args) - def _check_ctx(self): + def update(self, data): if self._ctx is None: raise ValueError("Context was already finalized") - - -class _BlockCipherEncryptionContext(_BlockCipherContext): - def __init__(self, cipher, mode, api): - super(_BlockCipherEncryptionContext, self).__init__(cipher, mode, api) - self._ctx = self._api.create_block_cipher_encrypt_context(cipher, mode) - - def update(self, data): - self._check_ctx() - return self._api.update_encrypt_context(self._ctx, data) + if self._operation == _Operation.encrypt: + return self._api.update_encrypt_context(self._ctx, data) + else: + return self._api.update_decrypt_context(self._ctx, data) def finalize(self): - self._check_ctx() - data = self._api.finalize_encrypt_context(self._ctx) - self._ctx = None - return data - - -class _BlockCipherDecryptionContext(_BlockCipherContext): - def __init__(self, cipher, mode, api): - super(_BlockCipherDecryptionContext, self).__init__(cipher, mode, api) - self._ctx = self._api.create_block_cipher_decrypt_context(cipher, mode) - - def update(self, data): - self._check_ctx() - return self._api.update_decrypt_context(self._ctx, data) - - def finalize(self): - self._check_ctx() - data = self._api.finalize_decrypt_context(self._ctx) + if self._ctx is None: + raise ValueError("Context was already finalized") + if self._operation == _Operation.encrypt: + data = self._api.finalize_encrypt_context(self._ctx) + else: + data = self._api.finalize_decrypt_context(self._ctx) self._ctx = None return data -- cgit v1.2.3 From 653463f0e133def71425a26fdd80bfe7c8ad5961 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Mon, 21 Oct 2013 17:55:01 -0500 Subject: address review comments * inline some methods * refactor enc/dec classes * modify docs --- cryptography/bindings/openssl/api.py | 56 +++++++++++++++----------------- cryptography/primitives/block/base.py | 55 +++++++++++++++---------------- docs/primitives/symmetric-encryption.rst | 9 +++-- tests/primitives/test_block.py | 37 +++++++++++---------- 4 files changed, 79 insertions(+), 78 deletions(-) diff --git a/cryptography/bindings/openssl/api.py b/cryptography/bindings/openssl/api.py index fc61c4b8..a3198c1a 100644 --- a/cryptography/bindings/openssl/api.py +++ b/cryptography/bindings/openssl/api.py @@ -99,17 +99,23 @@ class API(object): self.lib.EVP_get_cipherbyname(ciphername.encode("ascii"))) def create_block_cipher_encrypt_context(self, cipher, mode): - ctx, args = self._create_block_cipher_context(cipher, mode) - res = self.lib.EVP_EncryptInit_ex(*args) + ctx, evp, iv_nonce = self._create_block_cipher_context(cipher, mode) + res = self.lib.EVP_EncryptInit_ex(ctx, evp, api.ffi.NULL, cipher.key, + iv_nonce) assert res != 0 - self._disable_padding(ctx) + # We purposely disable padding here as it's handled higher up in the + # API. + self.lib.EVP_CIPHER_CTX_set_padding(ctx, 0) return ctx def create_block_cipher_decrypt_context(self, cipher, mode): - ctx, args = self._create_block_cipher_context(cipher, mode) - res = self.lib.EVP_DecryptInit_ex(*args) + ctx, evp, iv_nonce = self._create_block_cipher_context(cipher, mode) + res = self.lib.EVP_DecryptInit_ex(ctx, evp, api.ffi.NULL, cipher.key, + iv_nonce) assert res != 0 - self._disable_padding(ctx) + # We purposely disable padding here as it's handled higher up in the + # API. + self.lib.EVP_CIPHER_CTX_set_padding(ctx, 0) return ctx def _create_block_cipher_context(self, cipher, mode): @@ -130,51 +136,43 @@ class API(object): else: iv_nonce = self.ffi.NULL - return (ctx, (ctx, evp_cipher, self.ffi.NULL, cipher.key, iv_nonce)) - - def _disable_padding(self, ctx): - # We purposely disable padding here as it's handled higher up in the - # API. - self.lib.EVP_CIPHER_CTX_set_padding(ctx, 0) + return (ctx, evp_cipher, iv_nonce) def update_encrypt_context(self, ctx, data): - buf, outlen = self._create_buf_out(ctx, len(data)) + block_size = self.lib.EVP_CIPHER_CTX_block_size(ctx) + buf = self.ffi.new("unsigned char[]", len(data) + block_size - 1) + outlen = self.ffi.new("int *") res = self.lib.EVP_EncryptUpdate(ctx, buf, outlen, data, len(data)) assert res != 0 return self.ffi.buffer(buf)[:outlen[0]] def update_decrypt_context(self, ctx, data): - buf, outlen = self._create_buf_out(ctx, len(data)) + block_size = self.lib.EVP_CIPHER_CTX_block_size(ctx) + buf = self.ffi.new("unsigned char[]", len(data) + block_size - 1) + outlen = self.ffi.new("int *") res = self.lib.EVP_DecryptUpdate(ctx, buf, outlen, data, len(data)) assert res != 0 return self.ffi.buffer(buf)[:outlen[0]] - def _create_buf_out(self, ctx, data_len): + def finalize_encrypt_context(self, ctx): block_size = self.lib.EVP_CIPHER_CTX_block_size(ctx) - buf = self.ffi.new("unsigned char[]", data_len + block_size - 1) + buf = self.ffi.new("unsigned char[]", block_size) outlen = self.ffi.new("int *") - return (buf, outlen) - - def finalize_encrypt_context(self, ctx): - buf, outlen = self._create_final_buf_out(ctx) res = self.lib.EVP_EncryptFinal_ex(ctx, buf, outlen) assert res != 0 - self._cleanup_block_cipher(ctx) + res = self.lib.EVP_CIPHER_CTX_cleanup(ctx) + assert res == 1 return self.ffi.buffer(buf)[:outlen[0]] def finalize_decrypt_context(self, ctx): - buf, outlen = self._create_final_buf_out(ctx) + block_size = self.lib.EVP_CIPHER_CTX_block_size(ctx) + buf = self.ffi.new("unsigned char[]", block_size) + outlen = self.ffi.new("int *") res = self.lib.EVP_DecryptFinal_ex(ctx, buf, outlen) assert res != 0 - self._cleanup_block_cipher(ctx) - return self.ffi.buffer(buf)[:outlen[0]] - - def _create_final_buf_out(self, ctx): - return self._create_buf_out(ctx, 1) - - def _cleanup_block_cipher(self, ctx): res = self.lib.EVP_CIPHER_CTX_cleanup(ctx) assert res == 1 + return self.ffi.buffer(buf)[:outlen[0]] def supports_hash(self, hash_cls): return (self.ffi.NULL != diff --git a/cryptography/primitives/block/base.py b/cryptography/primitives/block/base.py index 650e39c1..14704ffe 100644 --- a/cryptography/primitives/block/base.py +++ b/cryptography/primitives/block/base.py @@ -13,16 +13,9 @@ from __future__ import absolute_import, division, print_function -from enum import Enum - from cryptography.bindings import _default_api -class _Operation(Enum): - encrypt = 0 - decrypt = 1 - - class BlockCipher(object): def __init__(self, cipher, mode, api=None): super(BlockCipher, self).__init__() @@ -35,39 +28,45 @@ class BlockCipher(object): self._api = api def encryptor(self): - return _BlockCipherContext(self.cipher, self.mode, self._api, - _Operation.encrypt) + return _BlockCipherEncryptionContext(self.cipher, self.mode, self._api) def decryptor(self): - return _BlockCipherContext(self.cipher, self.mode, self._api, - _Operation.decrypt) + return _BlockCipherDecryptionContext(self.cipher, self.mode, self._api) + + +class _BlockCipherEncryptionContext(object): + def __init__(self, cipher, mode, api): + super(_BlockCipherEncryptionContext, self).__init__() + self._api = api + self._ctx = self._api.create_block_cipher_encrypt_context(cipher, mode) + + def update(self, data): + if self._ctx is None: + raise ValueError("Context was already finalized") + return self._api.update_encrypt_context(self._ctx, data) + + def finalize(self): + if self._ctx is None: + raise ValueError("Context was already finalized") + data = self._api.finalize_encrypt_context(self._ctx) + self._ctx = None + return data -class _BlockCipherContext(object): - def __init__(self, cipher, mode, api, operation): - super(_BlockCipherContext, self).__init__() +class _BlockCipherDecryptionContext(object): + def __init__(self, cipher, mode, api): + super(_BlockCipherDecryptionContext, self).__init__() self._api = api - self._operation = operation - args = (cipher, mode) - if self._operation == _Operation.encrypt: - self._ctx = self._api.create_block_cipher_encrypt_context(*args) - else: - self._ctx = self._api.create_block_cipher_decrypt_context(*args) + self._ctx = self._api.create_block_cipher_decrypt_context(cipher, mode) def update(self, data): if self._ctx is None: raise ValueError("Context was already finalized") - if self._operation == _Operation.encrypt: - return self._api.update_encrypt_context(self._ctx, data) - else: - return self._api.update_decrypt_context(self._ctx, data) + return self._api.update_decrypt_context(self._ctx, data) def finalize(self): if self._ctx is None: raise ValueError("Context was already finalized") - if self._operation == _Operation.encrypt: - data = self._api.finalize_encrypt_context(self._ctx) - else: - data = self._api.finalize_decrypt_context(self._ctx) + data = self._api.finalize_decrypt_context(self._ctx) self._ctx = None return data diff --git a/docs/primitives/symmetric-encryption.rst b/docs/primitives/symmetric-encryption.rst index 4f404789..a8d9485d 100644 --- a/docs/primitives/symmetric-encryption.rst +++ b/docs/primitives/symmetric-encryption.rst @@ -15,14 +15,17 @@ where the encrypter and decrypter both use the same key. Block ciphers work by encrypting content in chunks, often 64- or 128-bits. They combine an underlying algorithm (such as AES), with a mode (such as - CBC, CTR, or GCM). A simple example of encrypting content with AES is: + CBC, CTR, or GCM). A simple example of encrypting (and then decrypting) + content with AES is: .. doctest:: >>> from cryptography.primitives.block import BlockCipher, ciphers, modes >>> cipher = BlockCipher(ciphers.AES(key), modes.CBC(iv)) - >>> context = cipher.encryptor() - >>> context.update(b"a secret message") + context.finalize() + >>> encrypt = cipher.encryptor() + >>> ct = encrypt.update(b"a secret message") + encrypt.finalize() + >>> decrypt = cipher.decryptor() + >>> decrypt.update(ct) + decrypt.finalize() '...' :param cipher: One of the ciphers described below. diff --git a/tests/primitives/test_block.py b/tests/primitives/test_block.py index 4a67002f..8e429085 100644 --- a/tests/primitives/test_block.py +++ b/tests/primitives/test_block.py @@ -49,20 +49,20 @@ class TestBlockCipherContext(object): modes.CBC(binascii.unhexlify(b"0" * 32)), api ) - context = cipher.encryptor() - context.update(b"a" * 16) - context.finalize() + encryptor = cipher.encryptor() + encryptor.update(b"a" * 16) + encryptor.finalize() with pytest.raises(ValueError): - context.update(b"b" * 16) + encryptor.update(b"b" * 16) with pytest.raises(ValueError): - context.finalize() - context = cipher.decryptor() - context.update(b"a" * 16) - context.finalize() + encryptor.finalize() + decryptor = cipher.decryptor() + decryptor.update(b"a" * 16) + decryptor.finalize() with pytest.raises(ValueError): - context.update(b"b" * 16) + decryptor.update(b"b" * 16) with pytest.raises(ValueError): - context.finalize() + decryptor.finalize() def test_unaligned_block_encryption(self, api): cipher = BlockCipher( @@ -70,15 +70,16 @@ class TestBlockCipherContext(object): modes.ECB(), api ) - context = cipher.encryptor() - ct = context.update(b"a" * 15) + encryptor = cipher.encryptor() + ct = encryptor.update(b"a" * 15) assert ct == b"" - ct += context.update(b"a" * 65) + ct += encryptor.update(b"a" * 65) assert len(ct) == 80 - ct += context.finalize() - context = cipher.decryptor() - pt = context.update(ct[:3]) + ct += encryptor.finalize() + decryptor = cipher.decryptor() + pt = decryptor.update(ct[:3]) assert pt == b"" - pt += context.update(ct[3:]) + pt += decryptor.update(ct[3:]) assert len(pt) == 80 - context.finalize() + assert pt == b"a" * 80 + decryptor.finalize() -- cgit v1.2.3 From fd56c5f84a247988f4665713eda83452103d3cf4 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Mon, 21 Oct 2013 22:19:19 -0500 Subject: remove enum requirement now that we're not using enum --- setup.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/setup.py b/setup.py index cbbf100a..d37b3fbb 100644 --- a/setup.py +++ b/setup.py @@ -32,9 +32,6 @@ setup_requires = [ CFFI_DEPENDENCY, ] -if sys.version_info[:2] < (3, 4): - install_requires += ["enum34"] - setup( name=about["__title__"], version=about["__version__"], -- cgit v1.2.3 From 3e0895c66f3e220d9beaf4f3c53a687a81669bc8 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Mon, 21 Oct 2013 22:19:29 -0500 Subject: rename variables in encrypt/decrypt example --- docs/primitives/symmetric-encryption.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/primitives/symmetric-encryption.rst b/docs/primitives/symmetric-encryption.rst index a8d9485d..1ec1ee01 100644 --- a/docs/primitives/symmetric-encryption.rst +++ b/docs/primitives/symmetric-encryption.rst @@ -22,10 +22,10 @@ where the encrypter and decrypter both use the same key. >>> from cryptography.primitives.block import BlockCipher, ciphers, modes >>> cipher = BlockCipher(ciphers.AES(key), modes.CBC(iv)) - >>> encrypt = cipher.encryptor() - >>> ct = encrypt.update(b"a secret message") + encrypt.finalize() - >>> decrypt = cipher.decryptor() - >>> decrypt.update(ct) + decrypt.finalize() + >>> encryptor = cipher.encryptor() + >>> ct = encryptor.update(b"a secret message") + encryptor.finalize() + >>> decryptor = cipher.decryptor() + >>> decryptor.update(ct) + decryptor.finalize() '...' :param cipher: One of the ciphers described below. -- cgit v1.2.3 From b59377d9a31d8614e1b883f7523939c8ab271500 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Mon, 21 Oct 2013 22:21:22 -0500 Subject: remove unneeded import...whoops --- setup.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/setup.py b/setup.py index d37b3fbb..1856cadb 100644 --- a/setup.py +++ b/setup.py @@ -10,8 +10,6 @@ # implied. # See the License for the specific language governing permissions and # limitations under the License. -import sys - from setuptools import setup, find_packages -- cgit v1.2.3 From 5399fd087268b671c61ad3710cdec6d540c02f22 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Mon, 21 Oct 2013 23:48:25 -0500 Subject: Create CipherContext interface & document it * Rename BlockCipherEncryption/DecryptionContexts to just CipherEncryption/DecryptionContext * Moved register to interfaces.py from modes.py since it is generic and can be used to decorate the _CipherEncryption/DecryptionContexts --- cryptography/primitives/block/base.py | 16 ++++++++++------ cryptography/primitives/block/modes.py | 15 ++++----------- cryptography/primitives/interfaces.py | 21 +++++++++++++++++++++ docs/primitives/symmetric-encryption.rst | 29 ++++++++++++++++++++++------- 4 files changed, 57 insertions(+), 24 deletions(-) diff --git a/cryptography/primitives/block/base.py b/cryptography/primitives/block/base.py index e625dc7c..12b6f626 100644 --- a/cryptography/primitives/block/base.py +++ b/cryptography/primitives/block/base.py @@ -13,6 +13,8 @@ from __future__ import absolute_import, division, print_function +from cryptography.primitives import interfaces + class BlockCipher(object): def __init__(self, cipher, mode, api=None): @@ -26,15 +28,16 @@ class BlockCipher(object): self._api = api def encryptor(self): - return _BlockCipherEncryptionContext(self.cipher, self.mode, self._api) + return _CipherEncryptionContext(self.cipher, self.mode, self._api) def decryptor(self): - return _BlockCipherDecryptionContext(self.cipher, self.mode, self._api) + return _CipherDecryptionContext(self.cipher, self.mode, self._api) -class _BlockCipherEncryptionContext(object): +@interfaces.register(interfaces.CipherContext) +class _CipherEncryptionContext(object): def __init__(self, cipher, mode, api): - super(_BlockCipherEncryptionContext, self).__init__() + super(_CipherEncryptionContext, self).__init__() self._api = api self._ctx = self._api.create_block_cipher_encrypt_context(cipher, mode) @@ -51,9 +54,10 @@ class _BlockCipherEncryptionContext(object): return data -class _BlockCipherDecryptionContext(object): +@interfaces.register(interfaces.CipherContext) +class _CipherDecryptionContext(object): def __init__(self, cipher, mode, api): - super(_BlockCipherDecryptionContext, self).__init__() + super(_CipherDecryptionContext, self).__init__() self._api = api self._ctx = self._api.create_block_cipher_decrypt_context(cipher, mode) diff --git a/cryptography/primitives/block/modes.py b/cryptography/primitives/block/modes.py index 43631801..a933c187 100644 --- a/cryptography/primitives/block/modes.py +++ b/cryptography/primitives/block/modes.py @@ -16,14 +16,7 @@ from __future__ import absolute_import, division, print_function from cryptography.primitives import interfaces -def register(iface): - def register_decorator(klass): - iface.register(klass) - return klass - return register_decorator - - -@register(interfaces.ModeWithInitializationVector) +@interfaces.register(interfaces.ModeWithInitializationVector) class CBC(object): name = "CBC" @@ -36,7 +29,7 @@ class ECB(object): name = "ECB" -@register(interfaces.ModeWithInitializationVector) +@interfaces.register(interfaces.ModeWithInitializationVector) class OFB(object): name = "OFB" @@ -45,7 +38,7 @@ class OFB(object): self.initialization_vector = initialization_vector -@register(interfaces.ModeWithInitializationVector) +@interfaces.register(interfaces.ModeWithInitializationVector) class CFB(object): name = "CFB" @@ -54,7 +47,7 @@ class CFB(object): self.initialization_vector = initialization_vector -@register(interfaces.ModeWithNonce) +@interfaces.register(interfaces.ModeWithNonce) class CTR(object): name = "CTR" diff --git a/cryptography/primitives/interfaces.py b/cryptography/primitives/interfaces.py index c1fc9910..49c19d0e 100644 --- a/cryptography/primitives/interfaces.py +++ b/cryptography/primitives/interfaces.py @@ -18,9 +18,30 @@ import abc import six +def register(iface): + def register_decorator(klass): + iface.register(klass) + return klass + return register_decorator + + class ModeWithInitializationVector(six.with_metaclass(abc.ABCMeta)): pass class ModeWithNonce(six.with_metaclass(abc.ABCMeta)): pass + + +class CipherContext(six.with_metaclass(abc.ABCMeta)): + @abc.abstractmethod + def update(self, data): + """ + update takes bytes and return bytes + """ + + @abc.abstractmethod + def finalize(self): + """ + finalize return bytes + """ diff --git a/docs/primitives/symmetric-encryption.rst b/docs/primitives/symmetric-encryption.rst index 1ec1ee01..2021356c 100644 --- a/docs/primitives/symmetric-encryption.rst +++ b/docs/primitives/symmetric-encryption.rst @@ -15,7 +15,7 @@ where the encrypter and decrypter both use the same key. Block ciphers work by encrypting content in chunks, often 64- or 128-bits. They combine an underlying algorithm (such as AES), with a mode (such as - CBC, CTR, or GCM). A simple example of encrypting (and then decrypting) + CBC, CTR, or GCM).A simple example of encrypting (and then decrypting) content with AES is: .. doctest:: @@ -31,17 +31,32 @@ where the encrypter and decrypter both use the same key. :param cipher: One of the ciphers described below. :param mode: One of the modes described below. - ``encrypt()`` should be called repeatedly with new plaintext, and once the - full plaintext is fed in, ``finalize()`` should be called. + .. method:: encryptor() - .. method:: encrypt(plaintext) + :return :ref:`CipherContext `: encryption instance - :param bytes plaintext: The text you wish to encrypt. - :return bytes: Returns the ciphertext that was added. + .. method:: decryptor() + + :return :ref:`CipherContext `: decryption instance + +.. _ciphercontext: +.. class:: cryptography.primitives.interfaces.CipherContext() + + When calling ``encryptor()`` or ``decryptor()`` on a BlockCipher object you + will receive a return object conforming to the CipherContext interface. You + can then call ``update(data)`` with data until you have fed everything into + the context. Once that is done call ``finalize()`` to finish the operation and + obtain the remainder of the data. + + + .. method:: update(data) + + :param bytes data: The text you wish to pass into the context. + :return bytes: Returns the data that was encrypted or decrypted. .. method:: finalize() - :return bytes: Returns the remainder of the ciphertext. + :return bytes: Returns the remainder of the data. Ciphers ~~~~~~~ -- cgit v1.2.3 From d1afe39ac8961d865974b746ff072ecedc9abeee Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 22 Oct 2013 08:24:44 -0500 Subject: fix typo and show result of decryption in docs --- docs/primitives/symmetric-encryption.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/primitives/symmetric-encryption.rst b/docs/primitives/symmetric-encryption.rst index 2021356c..544c7163 100644 --- a/docs/primitives/symmetric-encryption.rst +++ b/docs/primitives/symmetric-encryption.rst @@ -15,7 +15,7 @@ where the encrypter and decrypter both use the same key. Block ciphers work by encrypting content in chunks, often 64- or 128-bits. They combine an underlying algorithm (such as AES), with a mode (such as - CBC, CTR, or GCM).A simple example of encrypting (and then decrypting) + CBC, CTR, or GCM). A simple example of encrypting (and then decrypting) content with AES is: .. doctest:: @@ -26,7 +26,7 @@ where the encrypter and decrypter both use the same key. >>> ct = encryptor.update(b"a secret message") + encryptor.finalize() >>> decryptor = cipher.decryptor() >>> decryptor.update(ct) + decryptor.finalize() - '...' + b"a secret message" :param cipher: One of the ciphers described below. :param mode: One of the modes described below. -- cgit v1.2.3 From 2f63b77ce840f5dc0239e72c8581ab76dbc9881f Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 22 Oct 2013 08:27:06 -0500 Subject: exclude abstractmethod decorator from coverage --- .coveragerc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.coveragerc b/.coveragerc index 398ff08a..b891cb7c 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,2 +1,6 @@ [run] branch = True + +[report] +exclude_lines = + @abc.abstractmethod -- cgit v1.2.3 From f1903e96777ac8722357a45dbc5da3276c9f8e06 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 22 Oct 2013 10:11:27 -0500 Subject: fix doc test failure --- docs/primitives/symmetric-encryption.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/primitives/symmetric-encryption.rst b/docs/primitives/symmetric-encryption.rst index 544c7163..72bf9711 100644 --- a/docs/primitives/symmetric-encryption.rst +++ b/docs/primitives/symmetric-encryption.rst @@ -26,7 +26,7 @@ where the encrypter and decrypter both use the same key. >>> ct = encryptor.update(b"a secret message") + encryptor.finalize() >>> decryptor = cipher.decryptor() >>> decryptor.update(ct) + decryptor.finalize() - b"a secret message" + "a secret message" :param cipher: One of the ciphers described below. :param mode: One of the modes described below. -- cgit v1.2.3 From f6cf956321ad99fe5bf071eb8832dbf2192c0ff5 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 22 Oct 2013 10:36:00 -0500 Subject: more docs --- docs/primitives/symmetric-encryption.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/primitives/symmetric-encryption.rst b/docs/primitives/symmetric-encryption.rst index 72bf9711..79d712e5 100644 --- a/docs/primitives/symmetric-encryption.rst +++ b/docs/primitives/symmetric-encryption.rst @@ -26,20 +26,19 @@ where the encrypter and decrypter both use the same key. >>> ct = encryptor.update(b"a secret message") + encryptor.finalize() >>> decryptor = cipher.decryptor() >>> decryptor.update(ct) + decryptor.finalize() - "a secret message" + 'a secret message' :param cipher: One of the ciphers described below. :param mode: One of the modes described below. .. method:: encryptor() - :return :ref:`CipherContext `: encryption instance + :return :class:`CipherContext`: encryption instance .. method:: decryptor() - :return :ref:`CipherContext `: decryption instance + :return :class:`CipherContext`: decryption instance -.. _ciphercontext: .. class:: cryptography.primitives.interfaces.CipherContext() When calling ``encryptor()`` or ``decryptor()`` on a BlockCipher object you -- cgit v1.2.3 From b2c94fd22e0da4159be8c98dd32917bdf9cfb504 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 22 Oct 2013 12:45:07 -0500 Subject: verify that encryptor/decryptor returns CipherContext compliant interface --- tests/primitives/test_block.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/primitives/test_block.py b/tests/primitives/test_block.py index 8e429085..5e147a79 100644 --- a/tests/primitives/test_block.py +++ b/tests/primitives/test_block.py @@ -17,6 +17,7 @@ import binascii import pytest +from cryptography.primitives import interfaces from cryptography.primitives.block import BlockCipher, ciphers, modes @@ -32,14 +33,14 @@ class TestBlockCipher(object): ciphers.AES(binascii.unhexlify(b"0" * 32)), modes.CBC(binascii.unhexlify(b"0" * 32)) ) - assert cipher.encryptor() is not None + assert isinstance(cipher.encryptor(), interfaces.CipherContext) def test_creates_decryptor(self): cipher = BlockCipher( ciphers.AES(binascii.unhexlify(b"0" * 32)), modes.CBC(binascii.unhexlify(b"0" * 32)) ) - assert cipher.decryptor() is not None + assert isinstance(cipher.decryptor(), interfaces.CipherContext) class TestBlockCipherContext(object): -- cgit v1.2.3