Repository: pyca/cryptography

Issue:
Deprecate/remove legacy symmetric algorithms
`cryptography` ships numerous legacy symmetric algorithms that, anecdotally, have very low use. These algorithms have a variety of security drawbacks when compared to more modern equivalents (namely AES or ChaCha20) and generally shouldn't be used. We plan to deprecate them on a long cycle such that users have a chance to speak up if they have a use case that we should consider.

The algorithms we'd like to deprecate and remove are:
* CAST5
* SEED
* IDEA
* Blowfish

3DES and ARC4 are also legacy (and ARC4 has serious security issues), but their common use in many scenarios means they will not be deprecated.


Discussion:

Resolving patch:
diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py
--- a/src/cryptography/hazmat/backends/openssl/backend.py
+++ b/src/cryptography/hazmat/backends/openssl/backend.py
@@ -91,14 +91,14 @@
 from cryptography.hazmat.primitives.ciphers.algorithms import (
     AES,
     ARC4,
-    Blowfish,
-    CAST5,
     Camellia,
     ChaCha20,
-    IDEA,
-    SEED,
     SM4,
     TripleDES,
+    _BlowfishInternal,
+    _CAST5Internal,
+    _IDEAInternal,
+    _SEEDInternal,
 )
 from cryptography.hazmat.primitives.ciphers.modes import (
     CBC,
@@ -389,14 +389,14 @@ def _register_default_ciphers(self) -> None:
         )
         for mode_cls in [CBC, CFB, OFB, ECB]:
             self.register_cipher_adapter(
-                Blowfish, mode_cls, GetCipherByName("bf-{mode.name}")
+                _BlowfishInternal, mode_cls, GetCipherByName("bf-{mode.name}")
             )
         for mode_cls in [CBC, CFB, OFB, ECB]:
             self.register_cipher_adapter(
-                SEED, mode_cls, GetCipherByName("seed-{mode.name}")
+                _SEEDInternal, mode_cls, GetCipherByName("seed-{mode.name}")
             )
         for cipher_cls, mode_cls in itertools.product(
-            [CAST5, IDEA],
+            [_CAST5Internal, _IDEAInternal],
             [CBC, OFB, CFB, ECB],
         ):
             self.register_cipher_adapter(
diff --git a/src/cryptography/hazmat/primitives/ciphers/algorithms.py b/src/cryptography/hazmat/primitives/ciphers/algorithms.py
--- a/src/cryptography/hazmat/primitives/ciphers/algorithms.py
+++ b/src/cryptography/hazmat/primitives/ciphers/algorithms.py
@@ -81,6 +81,16 @@ def key_size(self) -> int:
         return len(self.key) * 8
 
 
+_BlowfishInternal = Blowfish
+utils.deprecated(
+    Blowfish,
+    __name__,
+    "Blowfish has been deprecated",
+    utils.DeprecatedIn37,
+    name="Blowfish",
+)
+
+
 class CAST5(CipherAlgorithm, BlockCipherAlgorithm):
     name = "CAST5"
     block_size = 64
@@ -94,6 +104,16 @@ def key_size(self) -> int:
         return len(self.key) * 8
 
 
+_CAST5Internal = CAST5
+utils.deprecated(
+    CAST5,
+    __name__,
+    "CAST5 has been deprecated",
+    utils.DeprecatedIn37,
+    name="CAST5",
+)
+
+
 class ARC4(CipherAlgorithm):
     name = "RC4"
     key_sizes = frozenset([40, 56, 64, 80, 128, 160, 192, 256])
@@ -119,6 +139,16 @@ def key_size(self) -> int:
         return len(self.key) * 8
 
 
+_IDEAInternal = IDEA
+utils.deprecated(
+    IDEA,
+    __name__,
+    "IDEA has been deprecated",
+    utils.DeprecatedIn37,
+    name="IDEA",
+)
+
+
 class SEED(CipherAlgorithm, BlockCipherAlgorithm):
     name = "SEED"
     block_size = 128
@@ -132,6 +162,16 @@ def key_size(self) -> int:
         return len(self.key) * 8
 
 
+_SEEDInternal = SEED
+utils.deprecated(
+    SEED,
+    __name__,
+    "SEED has been deprecated",
+    utils.DeprecatedIn37,
+    name="SEED",
+)
+
+
 class ChaCha20(CipherAlgorithm):
     name = "ChaCha20"
     key_sizes = frozenset([256])


Test patch:
diff --git a/tests/hazmat/primitives/test_blowfish.py b/tests/hazmat/primitives/test_blowfish.py
--- a/tests/hazmat/primitives/test_blowfish.py
+++ b/tests/hazmat/primitives/test_blowfish.py
@@ -16,7 +16,7 @@
 
 @pytest.mark.supported(
     only_if=lambda backend: backend.cipher_supported(
-        algorithms.Blowfish(b"\x00" * 56), modes.ECB()
+        algorithms._BlowfishInternal(b"\x00" * 56), modes.ECB()
     ),
     skip_message="Does not support Blowfish ECB",
 )
@@ -25,14 +25,16 @@ class TestBlowfishModeECB:
         load_nist_vectors,
         os.path.join("ciphers", "Blowfish"),
         ["bf-ecb.txt"],
-        lambda key, **kwargs: algorithms.Blowfish(binascii.unhexlify(key)),
+        lambda key, **kwargs: algorithms._BlowfishInternal(
+            binascii.unhexlify(key)
+        ),
         lambda **kwargs: modes.ECB(),
     )
 
 
 @pytest.mark.supported(
     only_if=lambda backend: backend.cipher_supported(
-        algorithms.Blowfish(b"\x00" * 56), modes.CBC(b"\x00" * 8)
+        algorithms._BlowfishInternal(b"\x00" * 56), modes.CBC(b"\x00" * 8)
     ),
     skip_message="Does not support Blowfish CBC",
 )
@@ -41,14 +43,16 @@ class TestBlowfishModeCBC:
         load_nist_vectors,
         os.path.join("ciphers", "Blowfish"),
         ["bf-cbc.txt"],
-        lambda key, **kwargs: algorithms.Blowfish(binascii.unhexlify(key)),
+        lambda key, **kwargs: algorithms._BlowfishInternal(
+            binascii.unhexlify(key)
+        ),
         lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)),
     )
 
 
 @pytest.mark.supported(
     only_if=lambda backend: backend.cipher_supported(
-        algorithms.Blowfish(b"\x00" * 56), modes.OFB(b"\x00" * 8)
+        algorithms._BlowfishInternal(b"\x00" * 56), modes.OFB(b"\x00" * 8)
     ),
     skip_message="Does not support Blowfish OFB",
 )
@@ -57,14 +61,16 @@ class TestBlowfishModeOFB:
         load_nist_vectors,
         os.path.join("ciphers", "Blowfish"),
         ["bf-ofb.txt"],
-        lambda key, **kwargs: algorithms.Blowfish(binascii.unhexlify(key)),
+        lambda key, **kwargs: algorithms._BlowfishInternal(
+            binascii.unhexlify(key)
+        ),
         lambda iv, **kwargs: modes.OFB(binascii.unhexlify(iv)),
     )
 
 
 @pytest.mark.supported(
     only_if=lambda backend: backend.cipher_supported(
-        algorithms.Blowfish(b"\x00" * 56), modes.CFB(b"\x00" * 8)
+        algorithms._BlowfishInternal(b"\x00" * 56), modes.CFB(b"\x00" * 8)
     ),
     skip_message="Does not support Blowfish CFB",
 )
@@ -73,6 +79,8 @@ class TestBlowfishModeCFB:
         load_nist_vectors,
         os.path.join("ciphers", "Blowfish"),
         ["bf-cfb.txt"],
-        lambda key, **kwargs: algorithms.Blowfish(binascii.unhexlify(key)),
+        lambda key, **kwargs: algorithms._BlowfishInternal(
+            binascii.unhexlify(key)
+        ),
         lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)),
     )
diff --git a/tests/hazmat/primitives/test_cast5.py b/tests/hazmat/primitives/test_cast5.py
--- a/tests/hazmat/primitives/test_cast5.py
+++ b/tests/hazmat/primitives/test_cast5.py
@@ -16,7 +16,7 @@
 
 @pytest.mark.supported(
     only_if=lambda backend: backend.cipher_supported(
-        algorithms.CAST5(b"\x00" * 16), modes.ECB()
+        algorithms._CAST5Internal(b"\x00" * 16), modes.ECB()
     ),
     skip_message="Does not support CAST5 ECB",
 )
@@ -25,14 +25,16 @@ class TestCAST5ModeECB:
         load_nist_vectors,
         os.path.join("ciphers", "CAST5"),
         ["cast5-ecb.txt"],
-        lambda key, **kwargs: algorithms.CAST5(binascii.unhexlify((key))),
+        lambda key, **kwargs: algorithms._CAST5Internal(
+            binascii.unhexlify((key))
+        ),
         lambda **kwargs: modes.ECB(),
     )
 
 
 @pytest.mark.supported(
     only_if=lambda backend: backend.cipher_supported(
-        algorithms.CAST5(b"\x00" * 16), modes.CBC(b"\x00" * 8)
+        algorithms._CAST5Internal(b"\x00" * 16), modes.CBC(b"\x00" * 8)
     ),
     skip_message="Does not support CAST5 CBC",
 )
@@ -41,14 +43,16 @@ class TestCAST5ModeCBC:
         load_nist_vectors,
         os.path.join("ciphers", "CAST5"),
         ["cast5-cbc.txt"],
-        lambda key, **kwargs: algorithms.CAST5(binascii.unhexlify((key))),
+        lambda key, **kwargs: algorithms._CAST5Internal(
+            binascii.unhexlify((key))
+        ),
         lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)),
     )
 
 
 @pytest.mark.supported(
     only_if=lambda backend: backend.cipher_supported(
-        algorithms.CAST5(b"\x00" * 16), modes.OFB(b"\x00" * 8)
+        algorithms._CAST5Internal(b"\x00" * 16), modes.OFB(b"\x00" * 8)
     ),
     skip_message="Does not support CAST5 OFB",
 )
@@ -57,14 +61,16 @@ class TestCAST5ModeOFB:
         load_nist_vectors,
         os.path.join("ciphers", "CAST5"),
         ["cast5-ofb.txt"],
-        lambda key, **kwargs: algorithms.CAST5(binascii.unhexlify((key))),
+        lambda key, **kwargs: algorithms._CAST5Internal(
+            binascii.unhexlify((key))
+        ),
         lambda iv, **kwargs: modes.OFB(binascii.unhexlify(iv)),
     )
 
 
 @pytest.mark.supported(
     only_if=lambda backend: backend.cipher_supported(
-        algorithms.CAST5(b"\x00" * 16), modes.CFB(b"\x00" * 8)
+        algorithms._CAST5Internal(b"\x00" * 16), modes.CFB(b"\x00" * 8)
     ),
     skip_message="Does not support CAST5 CFB",
 )
@@ -73,6 +79,8 @@ class TestCAST5ModeCFB:
         load_nist_vectors,
         os.path.join("ciphers", "CAST5"),
         ["cast5-cfb.txt"],
-        lambda key, **kwargs: algorithms.CAST5(binascii.unhexlify((key))),
+        lambda key, **kwargs: algorithms._CAST5Internal(
+            binascii.unhexlify((key))
+        ),
         lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)),
     )
diff --git a/tests/hazmat/primitives/test_ciphers.py b/tests/hazmat/primitives/test_ciphers.py
--- a/tests/hazmat/primitives/test_ciphers.py
+++ b/tests/hazmat/primitives/test_ciphers.py
@@ -14,12 +14,12 @@
 from cryptography.hazmat.primitives.ciphers.algorithms import (
     AES,
     ARC4,
-    Blowfish,
-    CAST5,
     Camellia,
-    IDEA,
-    SEED,
     TripleDES,
+    _BlowfishInternal,
+    _CAST5Internal,
+    _IDEAInternal,
+    _SEEDInternal,
 )
 
 from ...utils import (
@@ -114,16 +114,16 @@ class TestBlowfish:
         [(b"0" * (keysize // 4), keysize) for keysize in range(32, 449, 8)],
     )
     def test_key_size(self, key, keysize):
-        cipher = Blowfish(binascii.unhexlify(key))
+        cipher = _BlowfishInternal(binascii.unhexlify(key))
         assert cipher.key_size == keysize
 
     def test_invalid_key_size(self):
         with pytest.raises(ValueError):
-            Blowfish(binascii.unhexlify(b"0" * 6))
+            _BlowfishInternal(binascii.unhexlify(b"0" * 6))
 
     def test_invalid_key_type(self):
         with pytest.raises(TypeError, match="key must be bytes"):
-            Blowfish("0" * 8)  # type: ignore[arg-type]
+            _BlowfishInternal("0" * 8)  # type: ignore[arg-type]
 
 
 class TestCAST5:
@@ -132,16 +132,16 @@ class TestCAST5:
         [(b"0" * (keysize // 4), keysize) for keysize in range(40, 129, 8)],
     )
     def test_key_size(self, key, keysize):
-        cipher = CAST5(binascii.unhexlify(key))
+        cipher = _CAST5Internal(binascii.unhexlify(key))
         assert cipher.key_size == keysize
 
     def test_invalid_key_size(self):
         with pytest.raises(ValueError):
-            CAST5(binascii.unhexlify(b"0" * 34))
+            _CAST5Internal(binascii.unhexlify(b"0" * 34))
 
     def test_invalid_key_type(self):
         with pytest.raises(TypeError, match="key must be bytes"):
-            CAST5("0" * 10)  # type: ignore[arg-type]
+            _CAST5Internal("0" * 10)  # type: ignore[arg-type]
 
 
 class TestARC4:
@@ -172,30 +172,30 @@ def test_invalid_key_type(self):
 
 class TestIDEA:
     def test_key_size(self):
-        cipher = IDEA(b"\x00" * 16)
+        cipher = _IDEAInternal(b"\x00" * 16)
         assert cipher.key_size == 128
 
     def test_invalid_key_size(self):
         with pytest.raises(ValueError):
-            IDEA(b"\x00" * 17)
+            _IDEAInternal(b"\x00" * 17)
 
     def test_invalid_key_type(self):
         with pytest.raises(TypeError, match="key must be bytes"):
-            IDEA("0" * 16)  # type: ignore[arg-type]
+            _IDEAInternal("0" * 16)  # type: ignore[arg-type]
 
 
 class TestSEED:
     def test_key_size(self):
-        cipher = SEED(b"\x00" * 16)
+        cipher = _SEEDInternal(b"\x00" * 16)
         assert cipher.key_size == 128
 
     def test_invalid_key_size(self):
         with pytest.raises(ValueError):
-            SEED(b"\x00" * 17)
+            _SEEDInternal(b"\x00" * 17)
 
     def test_invalid_key_type(self):
         with pytest.raises(TypeError, match="key must be bytes"):
-            SEED("0" * 16)  # type: ignore[arg-type]
+            _SEEDInternal("0" * 16)  # type: ignore[arg-type]
 
 
 def test_invalid_mode_algorithm():
diff --git a/tests/hazmat/primitives/test_idea.py b/tests/hazmat/primitives/test_idea.py
--- a/tests/hazmat/primitives/test_idea.py
+++ b/tests/hazmat/primitives/test_idea.py
@@ -16,7 +16,7 @@
 
 @pytest.mark.supported(
     only_if=lambda backend: backend.cipher_supported(
-        algorithms.IDEA(b"\x00" * 16), modes.ECB()
+        algorithms._IDEAInternal(b"\x00" * 16), modes.ECB()
     ),
     skip_message="Does not support IDEA ECB",
 )
@@ -25,14 +25,16 @@ class TestIDEAModeECB:
         load_nist_vectors,
         os.path.join("ciphers", "IDEA"),
         ["idea-ecb.txt"],
-        lambda key, **kwargs: algorithms.IDEA(binascii.unhexlify((key))),
+        lambda key, **kwargs: algorithms._IDEAInternal(
+            binascii.unhexlify((key))
+        ),
         lambda **kwargs: modes.ECB(),
     )
 
 
 @pytest.mark.supported(
     only_if=lambda backend: backend.cipher_supported(
-        algorithms.IDEA(b"\x00" * 16), modes.CBC(b"\x00" * 8)
+        algorithms._IDEAInternal(b"\x00" * 16), modes.CBC(b"\x00" * 8)
     ),
     skip_message="Does not support IDEA CBC",
 )
@@ -41,14 +43,16 @@ class TestIDEAModeCBC:
         load_nist_vectors,
         os.path.join("ciphers", "IDEA"),
         ["idea-cbc.txt"],
-        lambda key, **kwargs: algorithms.IDEA(binascii.unhexlify((key))),
+        lambda key, **kwargs: algorithms._IDEAInternal(
+            binascii.unhexlify((key))
+        ),
         lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)),
     )
 
 
 @pytest.mark.supported(
     only_if=lambda backend: backend.cipher_supported(
-        algorithms.IDEA(b"\x00" * 16), modes.OFB(b"\x00" * 8)
+        algorithms._IDEAInternal(b"\x00" * 16), modes.OFB(b"\x00" * 8)
     ),
     skip_message="Does not support IDEA OFB",
 )
@@ -57,14 +61,16 @@ class TestIDEAModeOFB:
         load_nist_vectors,
         os.path.join("ciphers", "IDEA"),
         ["idea-ofb.txt"],
-        lambda key, **kwargs: algorithms.IDEA(binascii.unhexlify((key))),
+        lambda key, **kwargs: algorithms._IDEAInternal(
+            binascii.unhexlify((key))
+        ),
         lambda iv, **kwargs: modes.OFB(binascii.unhexlify(iv)),
     )
 
 
 @pytest.mark.supported(
     only_if=lambda backend: backend.cipher_supported(
-        algorithms.IDEA(b"\x00" * 16), modes.CFB(b"\x00" * 8)
+        algorithms._IDEAInternal(b"\x00" * 16), modes.CFB(b"\x00" * 8)
     ),
     skip_message="Does not support IDEA CFB",
 )
@@ -73,6 +79,8 @@ class TestIDEAModeCFB:
         load_nist_vectors,
         os.path.join("ciphers", "IDEA"),
         ["idea-cfb.txt"],
-        lambda key, **kwargs: algorithms.IDEA(binascii.unhexlify((key))),
+        lambda key, **kwargs: algorithms._IDEAInternal(
+            binascii.unhexlify((key))
+        ),
         lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)),
     )
diff --git a/tests/hazmat/primitives/test_seed.py b/tests/hazmat/primitives/test_seed.py
--- a/tests/hazmat/primitives/test_seed.py
+++ b/tests/hazmat/primitives/test_seed.py
@@ -16,7 +16,7 @@
 
 @pytest.mark.supported(
     only_if=lambda backend: backend.cipher_supported(
-        algorithms.SEED(b"\x00" * 16), modes.ECB()
+        algorithms._SEEDInternal(b"\x00" * 16), modes.ECB()
     ),
     skip_message="Does not support SEED ECB",
 )
@@ -25,14 +25,16 @@ class TestSEEDModeECB:
         load_nist_vectors,
         os.path.join("ciphers", "SEED"),
         ["rfc-4269.txt"],
-        lambda key, **kwargs: algorithms.SEED(binascii.unhexlify((key))),
+        lambda key, **kwargs: algorithms._SEEDInternal(
+            binascii.unhexlify((key))
+        ),
         lambda **kwargs: modes.ECB(),
     )
 
 
 @pytest.mark.supported(
     only_if=lambda backend: backend.cipher_supported(
-        algorithms.SEED(b"\x00" * 16), modes.CBC(b"\x00" * 16)
+        algorithms._SEEDInternal(b"\x00" * 16), modes.CBC(b"\x00" * 16)
     ),
     skip_message="Does not support SEED CBC",
 )
@@ -41,14 +43,16 @@ class TestSEEDModeCBC:
         load_nist_vectors,
         os.path.join("ciphers", "SEED"),
         ["rfc-4196.txt"],
-        lambda key, **kwargs: algorithms.SEED(binascii.unhexlify((key))),
+        lambda key, **kwargs: algorithms._SEEDInternal(
+            binascii.unhexlify((key))
+        ),
         lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)),
     )
 
 
 @pytest.mark.supported(
     only_if=lambda backend: backend.cipher_supported(
-        algorithms.SEED(b"\x00" * 16), modes.OFB(b"\x00" * 16)
+        algorithms._SEEDInternal(b"\x00" * 16), modes.OFB(b"\x00" * 16)
     ),
     skip_message="Does not support SEED OFB",
 )
@@ -57,14 +61,16 @@ class TestSEEDModeOFB:
         load_nist_vectors,
         os.path.join("ciphers", "SEED"),
         ["seed-ofb.txt"],
-        lambda key, **kwargs: algorithms.SEED(binascii.unhexlify((key))),
+        lambda key, **kwargs: algorithms._SEEDInternal(
+            binascii.unhexlify((key))
+        ),
         lambda iv, **kwargs: modes.OFB(binascii.unhexlify(iv)),
     )
 
 
 @pytest.mark.supported(
     only_if=lambda backend: backend.cipher_supported(
-        algorithms.SEED(b"\x00" * 16), modes.CFB(b"\x00" * 16)
+        algorithms._SEEDInternal(b"\x00" * 16), modes.CFB(b"\x00" * 16)
     ),
     skip_message="Does not support SEED CFB",
 )
@@ -73,6 +79,8 @@ class TestSEEDModeCFB:
         load_nist_vectors,
         os.path.join("ciphers", "SEED"),
         ["seed-cfb.txt"],
-        lambda key, **kwargs: algorithms.SEED(binascii.unhexlify((key))),
+        lambda key, **kwargs: algorithms._SEEDInternal(
+            binascii.unhexlify((key))
+        ),
         lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)),
     )