Merge branch 'master' into arc4-support

* master:
  Typo
  Be really explicit about what's good and bad
  Mention return types.
  Consistently use e.g.
  Module documentation.
  Single space.
  Add a new Mode interface to document mode.name and start on some prose docs for interfaces.
  Proper name for the iv thing.
  Actually note the properties for cipher modes types on their ABCs.

Conflicts:
	docs/hazmat/primitives/symmetric-encryption.rst
diff --git a/cryptography/hazmat/bindings/openssl/backend.py b/cryptography/hazmat/bindings/openssl/backend.py
index 0c3d22d..1cb886d 100644
--- a/cryptography/hazmat/bindings/openssl/backend.py
+++ b/cryptography/hazmat/bindings/openssl/backend.py
@@ -21,7 +21,7 @@
 from cryptography.exceptions import UnsupportedAlgorithm
 from cryptography.hazmat.primitives import interfaces
 from cryptography.hazmat.primitives.ciphers.algorithms import (
-    AES, Blowfish, Camellia, CAST5, TripleDES,
+    AES, Blowfish, Camellia, CAST5, TripleDES, ARC4,
 )
 from cryptography.hazmat.primitives.ciphers.modes import (
     CBC, CTR, ECB, OFB, CFB
@@ -254,6 +254,11 @@
             ECB,
             GetCipherByName("cast5-ecb")
         )
+        self.register_cipher_adapter(
+            ARC4,
+            type(None),
+            GetCipherByName("rc4")
+        )
 
     def create_encrypt_ctx(self, cipher, mode):
         return _CipherContext(self._backend, cipher, mode,
diff --git a/cryptography/hazmat/primitives/ciphers/algorithms.py b/cryptography/hazmat/primitives/ciphers/algorithms.py
index 8046bd2..cbfaceb 100644
--- a/cryptography/hazmat/primitives/ciphers/algorithms.py
+++ b/cryptography/hazmat/primitives/ciphers/algorithms.py
@@ -116,3 +116,22 @@
     @property
     def key_size(self):
         return len(self.key) * 8
+
+
+class ARC4(object):
+    name = "RC4"
+    key_sizes = frozenset([40, 56, 64, 80, 128, 192, 256])
+
+    def __init__(self, key):
+        super(ARC4, self).__init__()
+        self.key = key
+
+        # Verify that the key size matches the expected key size
+        if self.key_size not in self.key_sizes:
+            raise ValueError("Invalid key size ({0}) for {1}".format(
+                self.key_size, self.name
+            ))
+
+    @property
+    def key_size(self):
+        return len(self.key) * 8
diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst
index 5f1a64a..9d18ce5 100644
--- a/docs/hazmat/primitives/symmetric-encryption.rst
+++ b/docs/hazmat/primitives/symmetric-encryption.rst
@@ -149,6 +149,16 @@
     :param bytes key: The secret key, 32-448 bits in length (in increments of
                       8).  This must be kept secret.
 
+.. class:: ARC4(key)
+
+    ARC4 (Alleged RC4) is a stream cipher with serious weaknesses in its
+    initial stream output. Its use is strongly discouraged. ARC4 does not use
+    mode constructions.
+
+    :param bytes key: The secret key, ``40``, ``56``, ``64``, ``80``, ``128``,
+                      ``192``, or ``256`` bits in length.  This must be kept
+                      secret.
+
 
 .. _symmetric-encryption-modes:
 
diff --git a/tests/hazmat/primitives/test_arc4.py b/tests/hazmat/primitives/test_arc4.py
new file mode 100644
index 0000000..cf0c5eb
--- /dev/null
+++ b/tests/hazmat/primitives/test_arc4.py
@@ -0,0 +1,43 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import, division, print_function
+
+import binascii
+import os
+
+from cryptography.hazmat.primitives.ciphers import algorithms
+
+from .utils import generate_stream_encryption_test
+from ...utils import load_nist_vectors_from_file
+
+
+class TestARC4(object):
+    test_thing = generate_stream_encryption_test(
+        lambda path: load_nist_vectors_from_file(path, "ENCRYPT"),
+        os.path.join("ciphers", "ARC4"),
+        [
+            "rfc-6229-40.txt",
+            "rfc-6229-56.txt",
+            "rfc-6229-64.txt",
+            "rfc-6229-80.txt",
+            "rfc-6229-128.txt",
+            "rfc-6229-192.txt",
+            "rfc-6229-256.txt",
+        ],
+        lambda key: algorithms.ARC4(binascii.unhexlify((key))),
+        only_if=lambda backend: backend.ciphers.supported(
+            algorithms.ARC4("\x00" * 16), None
+        ),
+        skip_message="Does not support ARC4",
+    )
diff --git a/tests/hazmat/primitives/test_ciphers.py b/tests/hazmat/primitives/test_ciphers.py
index dfafab3..653f7ce 100644
--- a/tests/hazmat/primitives/test_ciphers.py
+++ b/tests/hazmat/primitives/test_ciphers.py
@@ -18,7 +18,7 @@
 import pytest
 
 from cryptography.hazmat.primitives.ciphers.algorithms import (
-    AES, Camellia, TripleDES, Blowfish, CAST5
+    AES, Camellia, TripleDES, Blowfish, CAST5, ARC4
 )
 
 
@@ -91,3 +91,22 @@
     def test_invalid_key_size(self):
         with pytest.raises(ValueError):
             CAST5(binascii.unhexlify(b"0" * 34))
+
+
+class TestARC4(object):
+    @pytest.mark.parametrize(("key", "keysize"), [
+        (b"0" * 10, 40),
+        (b"0" * 14, 56),
+        (b"0" * 16, 64),
+        (b"0" * 20, 80),
+        (b"0" * 32, 128),
+        (b"0" * 48, 192),
+        (b"0" * 64, 256),
+    ])
+    def test_key_size(self, key, keysize):
+        cipher = ARC4(binascii.unhexlify(key))
+        assert cipher.key_size == keysize
+
+    def test_invalid_key_size(self):
+        with pytest.raises(ValueError):
+            ARC4(binascii.unhexlify(b"0" * 34))
diff --git a/tests/hazmat/primitives/test_utils.py b/tests/hazmat/primitives/test_utils.py
index d7247e6..cee0b20 100644
--- a/tests/hazmat/primitives/test_utils.py
+++ b/tests/hazmat/primitives/test_utils.py
@@ -2,7 +2,7 @@
 
 from .utils import (
     base_hash_test, encrypt_test, hash_test, long_string_hash_test,
-    base_hmac_test, hmac_test
+    base_hmac_test, hmac_test, stream_encryption_test
 )
 
 
@@ -70,3 +70,14 @@
                 skip_message="message!"
             )
         assert exc_info.value.args[0] == "message!"
+
+
+class TestStreamEncryptionTest(object):
+    def test_skips_if_only_if_returns_false(self):
+        with pytest.raises(pytest.skip.Exception) as exc_info:
+            stream_encryption_test(
+                None, None, None,
+                only_if=lambda backend: False,
+                skip_message="message!"
+            )
+        assert exc_info.value.args[0] == "message!"
diff --git a/tests/hazmat/primitives/utils.py b/tests/hazmat/primitives/utils.py
index e6e97d1..90c15b1 100644
--- a/tests/hazmat/primitives/utils.py
+++ b/tests/hazmat/primitives/utils.py
@@ -49,6 +49,45 @@
     assert actual_plaintext == binascii.unhexlify(plaintext)
 
 
+def generate_stream_encryption_test(param_loader, path, file_names,
+                                    cipher_factory, only_if=None,
+                                    skip_message=None):
+    def test_stream_encryption(self):
+        for backend in _ALL_BACKENDS:
+            for file_name in file_names:
+                for params in param_loader(os.path.join(path, file_name)):
+                    yield (
+                        stream_encryption_test,
+                        backend,
+                        cipher_factory,
+                        params,
+                        only_if,
+                        skip_message
+                    )
+    return test_stream_encryption
+
+
+def stream_encryption_test(backend, cipher_factory, params, only_if,
+                           skip_message):
+    if not only_if(backend):
+        pytest.skip(skip_message)
+    plaintext = params.pop("plaintext")
+    ciphertext = params.pop("ciphertext")
+    offset = params.pop("offset")
+    cipher = Cipher(cipher_factory(**params), None, backend)
+    encryptor = cipher.encryptor()
+    # throw away offset bytes
+    encryptor.update(b"\x00" * int(offset))
+    actual_ciphertext = encryptor.update(binascii.unhexlify(plaintext))
+    actual_ciphertext += encryptor.finalize()
+    assert actual_ciphertext == binascii.unhexlify(ciphertext)
+    decryptor = cipher.decryptor()
+    decryptor.update(b"\x00" * int(offset))
+    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,
                        only_if=None, skip_message=None):
     def test_hash(self):