Repository: pyca/cryptography

Issue:
ARC4 should permit keys in range 40-2048 bits
ARC4 in this library is currently too restrictive on the key size. It should allow any key 5 to 256 bytes in length.



Discussion:
This restriction was put in place because [RFC 6229](https://tools.ietf.org/html/rfc6229) only specifies vectors for 40, 56, 64, 80, 128, 192, and 256 bit keys. It is a project policy not to expose support for things we can't confirm are working properly via test vectors. There are two ways we can expand this:
- Find a pre-existing test vector set and use it
- Build one based on code that demonstrates multiple implementations agreeing on output. Examples of this can be seen in our docs (https://cryptography.io/en/latest/development/test-vectors/#custom-symmetric-vectors).

Is there a specific use case you have that requires key lengths outside the currently supported set?

I was replacing some preexisting code that was using PyCrypto with a 160-bit ARC4 key length.

I don't see any test vectors obvious in the PyCrypto codebase but it could be used to generate some and compared against golang then added to the ARC4 vectors in this library. Golang has a few test vectors (https://github.com/golang/go/blob/master/src/crypto/rc4/rc4_test.go) but nowhere near exhaustive of the possible key lengths.

It looks like the simplest solution for this case is to avoid boiling the ocean and just generate some 160-bit test vectors (although the vector creation scripts could be structured to generate vectors for arbitrary key sizes). If you'd like to submit a PR for that I'll be happy to review it and we can potentially have this in the next release.

Not for nothing, but I'd _strongly_ recommend moving to remove RC4 from
your application, it has a myriad of known weaknesses at this point.

On Fri, Nov 27, 2015 at 9:23 PM, Paul Kehrer notifications@github.com
wrote:

> It looks like the simplest solution for this case is to avoid boiling the
> ocean and just generate some 160-bit test vectors (although the vector
> creation scripts could be structured to generate vectors for arbitrary key
> sizes). If you'd like to submit a PR for that I'll be happy to review it
> and we can potentially have this in the next release.
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/pyca/cryptography/issues/2494#issuecomment-160241574.

## 

"I disapprove of what you say, but I will defend to the death your right to
say it." -- Evelyn Beatrice Hall (summarizing Voltaire)
"The people's good is the highest law." -- Cicero
GPG Key fingerprint: 125F 5C67 DFE9 4084

Definitely -- it's worth noting that our support of ARC4 in cryptography is intended for legacy conversions and that once that is accomplished time should be budgeted to migrate to a modern cipher construction if possible.


Resolving patch:
diff --git a/docs/development/custom-vectors/arc4/generate_arc4.py b/docs/development/custom-vectors/arc4/generate_arc4.py
new file mode 100644
--- /dev/null
+++ b/docs/development/custom-vectors/arc4/generate_arc4.py
@@ -0,0 +1,98 @@
+# This file is dual licensed under the terms of the Apache License, Version
+# 2.0, and the BSD License. See the LICENSE file in the root of this repository
+# for complete details.
+
+from __future__ import absolute_import, division, print_function
+
+import binascii
+
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives import ciphers
+from cryptography.hazmat.primitives.ciphers import algorithms
+
+
+_RFC6229_KEY_MATERIALS = [
+    (True,
+     8 * '0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20'),
+    (False,
+     8 * '1ada31d5cf688221c109163908ebe51debb46227c6cc8b37641910833222772a')
+]
+
+
+_RFC6229_OFFSETS = [
+    0,
+    16,
+    240,
+    256,
+    496,
+    512,
+    752,
+    768,
+    1008,
+    1024,
+    1520,
+    1536,
+    2032,
+    2048,
+    3056,
+    3072,
+    4080,
+    4096
+]
+
+
+_SIZES_TO_GENERATE = [
+    160
+]
+
+
+def _key_for_size(size, keyinfo):
+    msb, key = keyinfo
+    if msb:
+        return key[:size // 4]
+    else:
+        return key[-size // 4:]
+
+
+def _build_vectors():
+    count = 0
+    output = []
+    key = None
+    plaintext = binascii.unhexlify(32 * '0')
+    for size in _SIZES_TO_GENERATE:
+        for keyinfo in _RFC6229_KEY_MATERIALS:
+            key = _key_for_size(size, keyinfo)
+            cipher = ciphers.Cipher(
+                algorithms.ARC4(binascii.unhexlify(key)),
+                None,
+                default_backend())
+            encryptor = cipher.encryptor()
+            current_offset = 0
+            for offset in _RFC6229_OFFSETS:
+                if offset % 16 != 0:
+                    raise ValueError(
+                        "Offset {} is not evenly divisible by 16"
+                        .format(offset))
+                while current_offset < offset:
+                    encryptor.update(plaintext)
+                    current_offset += len(plaintext)
+                output.append("\nCOUNT = {}".format(count))
+                count += 1
+                output.append("KEY = {}".format(key))
+                output.append("OFFSET = {}".format(offset))
+                output.append("PLAINTEXT = {}".format(
+                    binascii.hexlify(plaintext)))
+                output.append("CIPHERTEXT = {}".format(
+                    binascii.hexlify(encryptor.update(plaintext))))
+                current_offset += len(plaintext)
+            assert not encryptor.finalize()
+    return "\n".join(output)
+
+
+def _write_file(data, filename):
+    with open(filename, 'w') as f:
+        f.write(data)
+
+
+if __name__ == '__main__':
+    _write_file(_build_vectors(), 'arc4.txt')
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
@@ -101,7 +101,7 @@ def key_size(self):
 @utils.register_interface(CipherAlgorithm)
 class ARC4(object):
     name = "RC4"
-    key_sizes = frozenset([40, 56, 64, 80, 128, 192, 256])
+    key_sizes = frozenset([40, 56, 64, 80, 128, 160, 192, 256])
 
     def __init__(self, key):
         self.key = _verify_key_size(self, key)


Test patch:
diff --git a/tests/hazmat/primitives/test_arc4.py b/tests/hazmat/primitives/test_arc4.py
--- a/tests/hazmat/primitives/test_arc4.py
+++ b/tests/hazmat/primitives/test_arc4.py
@@ -35,6 +35,7 @@ class TestARC4(object):
             "rfc-6229-128.txt",
             "rfc-6229-192.txt",
             "rfc-6229-256.txt",
+            "arc4.txt"
         ],
         lambda key, **kwargs: algorithms.ARC4(binascii.unhexlify(key)),
     )