Repository: pyca/cryptography

Issue:
CFB Modes
Turns out PyCrypto uses CFB8 rather than CFB<native block size> which is what we use when you set CFB mode. Adding CFB8 is not difficult, but do we want to add it as a separate mode or as an optional argument to the CFB constructor? I am somewhat leaning towards a separate class, but not strongly.

``` python
class CFB(object):
    name = "CFB"
    NATIVE_BLOCK_SIZE = object()

    def __init__(self, initialization_vector, feedback_length=NATIVE_BLOCK_SIZE):
        self.initialization_vector = initialization_vector
        self.feedback_length = feedback_length
```

or

``` python
class CFB8(object):
    name = "CFB8"

    def __init__(self, initialization_vector):
        self.initialization_vector = initialization_vector
```



Discussion:
I think `CFB8` maybe makes sense? It makes it clear that only a few `feedback_length` values are supported.

+1 for a new class.

On 15 May 2014 18:38:33 Alex Gaynor notifications@github.com wrote:

> I think `CFB8` maybe makes sense? It makes it clear that only a few 
> `feedback_length` values are supported.
> 
> ---
> 
> Reply to this email directly or view it on GitHub:
> https://github.com/pyca/cryptography/issues/1041#issuecomment-43241042


Resolving patch:
diff --git a/cryptography/hazmat/backends/commoncrypto/backend.py b/cryptography/hazmat/backends/commoncrypto/backend.py
--- a/cryptography/hazmat/backends/commoncrypto/backend.py
+++ b/cryptography/hazmat/backends/commoncrypto/backend.py
@@ -28,7 +28,7 @@
     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 @@ def _register_default_ciphers(self):
             (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 @@ def _register_default_ciphers(self):
         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 @@ def __init__(self, backend, cipher, mode, operation):
         # 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
--- a/cryptography/hazmat/backends/openssl/backend.py
+++ b/cryptography/hazmat/backends/openssl/backend.py
@@ -38,7 +38,7 @@
     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 @@ def register_cipher_adapter(self, cipher_cls, mode_cls, adapter):
         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
--- a/cryptography/hazmat/primitives/ciphers/modes.py
+++ b/cryptography/hazmat/primitives/ciphers/modes.py
@@ -70,6 +70,21 @@ def validate_for_algorithm(self, algorithm):
             ))
 
 
+@utils.register_interface(interfaces.Mode)
+@utils.register_interface(interfaces.ModeWithInitializationVector)
+class CFB8(object):
+    name = "CFB8"
+
+    def __init__(self, initialization_vector):
+        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.ModeWithNonce)
 class CTR(object):


Test patch:
diff --git a/tests/hazmat/primitives/test_3des.py b/tests/hazmat/primitives/test_3des.py
--- 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
--- 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)