From e2876f2051e9f6e7c535e8c3aca4bbd51caa3545 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Thu, 15 May 2014 16:59:15 -0400 Subject: add CFB8 support for AES/3DES on CommonCrypto and OpenSSL backends --- .../hazmat/backends/commoncrypto/backend.py | 6 ++-- cryptography/hazmat/backends/openssl/backend.py | 17 ++++++---- cryptography/hazmat/primitives/ciphers/modes.py | 19 ++++++++++- tests/hazmat/primitives/test_3des.py | 37 ++++++++++++++++++++++ tests/hazmat/primitives/test_aes.py | 33 +++++++++++++++++++ 5 files changed, 102 insertions(+), 10 deletions(-) diff --git a/cryptography/hazmat/backends/commoncrypto/backend.py b/cryptography/hazmat/backends/commoncrypto/backend.py index 4faca73e..91c0721d 100644 --- a/cryptography/hazmat/backends/commoncrypto/backend.py +++ b/cryptography/hazmat/backends/commoncrypto/backend.py @@ -28,7 +28,7 @@ from cryptography.hazmat.primitives.ciphers.algorithms import ( AES, ARC4, Blowfish, CAST5, TripleDES ) from cryptography.hazmat.primitives.ciphers.modes import ( - CBC, CFB, CTR, ECB, GCM, OFB + CBC, CFB, CFB8, CTR, ECB, GCM, OFB ) @@ -165,6 +165,7 @@ class Backend(object): (CBC, self._lib.kCCModeCBC), (ECB, self._lib.kCCModeECB), (CFB, self._lib.kCCModeCFB), + (CFB8, self._lib.kCCModeCFB8), (OFB, self._lib.kCCModeOFB), (CTR, self._lib.kCCModeCTR), (GCM, self._lib.kCCModeGCM), @@ -178,6 +179,7 @@ class Backend(object): for mode_cls, mode_const in [ (CBC, self._lib.kCCModeCBC), (CFB, self._lib.kCCModeCFB), + (CFB8, self._lib.kCCModeCFB8), (OFB, self._lib.kCCModeOFB), ]: self._register_cipher_adapter( @@ -264,7 +266,7 @@ class _CipherContext(object): # This bug has been filed as rdar://15589470 self._bytes_processed = 0 if (isinstance(cipher, interfaces.BlockCipherAlgorithm) and not - isinstance(mode, (OFB, CFB, CTR))): + isinstance(mode, (OFB, CFB, CFB8, CTR))): self._byte_block_size = cipher.block_size // 8 else: self._byte_block_size = 1 diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index e00be92f..af8fc751 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -38,7 +38,7 @@ from cryptography.hazmat.primitives.ciphers.algorithms import ( AES, ARC4, Blowfish, CAST5, Camellia, IDEA, SEED, TripleDES ) from cryptography.hazmat.primitives.ciphers.modes import ( - CBC, CFB, CTR, ECB, GCM, OFB + CBC, CFB, CFB8, CTR, ECB, GCM, OFB ) @@ -147,16 +147,19 @@ class Backend(object): self._cipher_registry[cipher_cls, mode_cls] = adapter def _register_default_ciphers(self): - for cipher_cls, mode_cls in itertools.product( - [AES, Camellia], - [CBC, CTR, ECB, OFB, CFB], - ): + for mode_cls in [CBC, CTR, ECB, OFB, CFB, CFB8]: self.register_cipher_adapter( - cipher_cls, + AES, + mode_cls, + GetCipherByName("{cipher.name}-{cipher.key_size}-{mode.name}") + ) + for mode_cls in [CBC, CTR, ECB, OFB, CFB]: + self.register_cipher_adapter( + Camellia, mode_cls, GetCipherByName("{cipher.name}-{cipher.key_size}-{mode.name}") ) - for mode_cls in [CBC, CFB, OFB]: + for mode_cls in [CBC, CFB, CFB8, OFB]: self.register_cipher_adapter( TripleDES, mode_cls, diff --git a/cryptography/hazmat/primitives/ciphers/modes.py b/cryptography/hazmat/primitives/ciphers/modes.py index 739f23dd..dee80fb3 100644 --- a/cryptography/hazmat/primitives/ciphers/modes.py +++ b/cryptography/hazmat/primitives/ciphers/modes.py @@ -59,8 +59,25 @@ class OFB(object): @utils.register_interface(interfaces.ModeWithInitializationVector) class CFB(object): name = "CFB" + NATIVE_SIZE = object() - def __init__(self, initialization_vector): + def __init__(self, initialization_vector, something=NATIVE_SIZE): + self.initialization_vector = initialization_vector + + def validate_for_algorithm(self, algorithm): + if len(self.initialization_vector) * 8 != algorithm.block_size: + raise ValueError("Invalid iv size ({0}) for {1}".format( + len(self.initialization_vector), self.name + )) + + +@utils.register_interface(interfaces.Mode) +@utils.register_interface(interfaces.ModeWithInitializationVector) +class CFB8(object): + name = "CFB8" + NATIVE_SIZE = object() + + def __init__(self, initialization_vector, something=NATIVE_SIZE): self.initialization_vector = initialization_vector def validate_for_algorithm(self, algorithm): diff --git a/tests/hazmat/primitives/test_3des.py b/tests/hazmat/primitives/test_3des.py index a4d696c9..b9354f0e 100644 --- a/tests/hazmat/primitives/test_3des.py +++ b/tests/hazmat/primitives/test_3des.py @@ -137,3 +137,40 @@ class TestTripleDESModeCFB(object): ), lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)), ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.TripleDES("\x00" * 8), modes.CFB8("\x00" * 8) + ), + skip_message="Does not support TripleDES CFB8", +) +@pytest.mark.cipher +class TestTripleDESModeCFB8(object): + test_KAT = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "3DES", "CFB"), + [ + "TCFB8invperm.rsp", + "TCFB8permop.rsp", + "TCFB8subtab.rsp", + "TCFB8varkey.rsp", + "TCFB8vartext.rsp", + ], + lambda keys, **kwargs: algorithms.TripleDES(binascii.unhexlify(keys)), + lambda iv, **kwargs: modes.CFB8(binascii.unhexlify(iv)), + ) + + test_MMT = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "3DES", "CFB"), + [ + "TCFB8MMT1.rsp", + "TCFB8MMT2.rsp", + "TCFB8MMT3.rsp", + ], + lambda key1, key2, key3, **kwargs: algorithms.TripleDES( + binascii.unhexlify(key1 + key2 + key3) + ), + lambda iv, **kwargs: modes.CFB8(binascii.unhexlify(iv)), + ) diff --git a/tests/hazmat/primitives/test_aes.py b/tests/hazmat/primitives/test_aes.py index 03be268d..173075d6 100644 --- a/tests/hazmat/primitives/test_aes.py +++ b/tests/hazmat/primitives/test_aes.py @@ -156,6 +156,39 @@ class TestAESModeCFB(object): ) +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.AES("\x00" * 16), modes.CFB8("\x00" * 16) + ), + skip_message="Does not support AES CFB8", +) +@pytest.mark.cipher +class TestAESModeCFB8(object): + test_CFB8 = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "AES", "CFB"), + [ + "CFB8GFSbox128.rsp", + "CFB8GFSbox192.rsp", + "CFB8GFSbox256.rsp", + "CFB8KeySbox128.rsp", + "CFB8KeySbox192.rsp", + "CFB8KeySbox256.rsp", + "CFB8VarKey128.rsp", + "CFB8VarKey192.rsp", + "CFB8VarKey256.rsp", + "CFB8VarTxt128.rsp", + "CFB8VarTxt192.rsp", + "CFB8VarTxt256.rsp", + "CFB8MMT128.rsp", + "CFB8MMT192.rsp", + "CFB8MMT256.rsp", + ], + lambda key, **kwargs: algorithms.AES(binascii.unhexlify(key)), + lambda iv, **kwargs: modes.CFB8(binascii.unhexlify(iv)), + ) + + @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( algorithms.AES("\x00" * 16), modes.CTR("\x00" * 16) -- cgit v1.2.3 From 2a947c4a5b59e3abe0fc092a6d5f5b3e3ad00314 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Thu, 15 May 2014 17:22:08 -0400 Subject: add some docs and changelog --- CHANGELOG.rst | 4 ++++ docs/hazmat/primitives/symmetric-encryption.rst | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8a2635ed..1801f33f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,6 +7,10 @@ Changelog .. note:: This version is not yet released and is under active development. * Added :class:`~cryptography.hazmat.primitives.HKDFExpand`. +* Added :class:`~cryptography.hazmat.primitives.ciphers.modes.CFB8` support + for :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES` and + :class:`~cryptography.hazmat.primitives.ciphers.algorithms.TripleDES` on + :doc:`/hazmat/backends/commoncrypto` and :doc:`/hazmat/backends/openssl`. 0.4 - 2014-05-03 ~~~~~~~~~~~~~~~~ diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst index e5d8c65b..0e3a451c 100644 --- a/docs/hazmat/primitives/symmetric-encryption.rst +++ b/docs/hazmat/primitives/symmetric-encryption.rst @@ -275,6 +275,19 @@ Modes Must be the same number of bytes as the ``block_size`` of the cipher. Do not reuse an ``initialization_vector`` with a given ``key``. +.. class:: CFB8(initialization_vector) + + CFB (Cipher Feedback) is a mode of operation for block ciphers. It + transforms a block cipher into a stream cipher. The CFB8 variant uses an + 8-bit shift register. + + **This mode does not require padding.** + + :param bytes initialization_vector: Must be random bytes. They do not need + to be kept secret and they can be included in a transmitted message. + Must be the same number of bytes as the ``block_size`` of the cipher. + Do not reuse an ``initialization_vector`` with a given ``key``. + .. class:: GCM(initialization_vector, tag=None) .. danger:: -- cgit v1.2.3 From d8efa499209a056f9334d3f772034c399fb695c0 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Fri, 16 May 2014 08:30:32 -0400 Subject: native_size is not needed with the CFB8 approach we chose --- cryptography/hazmat/primitives/ciphers/modes.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cryptography/hazmat/primitives/ciphers/modes.py b/cryptography/hazmat/primitives/ciphers/modes.py index dee80fb3..3d320c25 100644 --- a/cryptography/hazmat/primitives/ciphers/modes.py +++ b/cryptography/hazmat/primitives/ciphers/modes.py @@ -75,9 +75,8 @@ class CFB(object): @utils.register_interface(interfaces.ModeWithInitializationVector) class CFB8(object): name = "CFB8" - NATIVE_SIZE = object() - def __init__(self, initialization_vector, something=NATIVE_SIZE): + def __init__(self, initialization_vector): self.initialization_vector = initialization_vector def validate_for_algorithm(self, algorithm): -- cgit v1.2.3 From 2e36fe1289bcf44f9ba232218a0ee666ea34efd2 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Fri, 16 May 2014 09:26:30 -0400 Subject: sometimes you can't read and leave things in --- cryptography/hazmat/primitives/ciphers/modes.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cryptography/hazmat/primitives/ciphers/modes.py b/cryptography/hazmat/primitives/ciphers/modes.py index 3d320c25..004a68bf 100644 --- a/cryptography/hazmat/primitives/ciphers/modes.py +++ b/cryptography/hazmat/primitives/ciphers/modes.py @@ -59,9 +59,8 @@ class OFB(object): @utils.register_interface(interfaces.ModeWithInitializationVector) class CFB(object): name = "CFB" - NATIVE_SIZE = object() - def __init__(self, initialization_vector, something=NATIVE_SIZE): + def __init__(self, initialization_vector): self.initialization_vector = initialization_vector def validate_for_algorithm(self, algorithm): -- cgit v1.2.3