Merge pull request #234 from alex/padding-fixes

Made PKCS7 unpadding more constant time
diff --git a/.gitignore b/.gitignore
index 5d187cf..9b8c49b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@
 __pycache__/
 _build/
 .tox/
+.cache/
 *.egg-info/
 .coverage
 cffi-*.egg/
diff --git a/.travis/install.sh b/.travis/install.sh
index fdd7190..4aa3979 100755
--- a/.travis/install.sh
+++ b/.travis/install.sh
@@ -5,24 +5,8 @@
 
 if [[ "${OPENSSL}" == "0.9.8" ]]; then
     sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu/ lucid main"
-fi
-
-if [[ "${TOX_ENV}" == "pypy" ]]; then
-    sudo add-apt-repository -y ppa:pypy/ppa
-fi
-
-sudo apt-get -y update
-
-if [[ "${OPENSSL}" == "0.9.8" ]]; then
+    sudo apt-get -y update
     sudo apt-get install -y --force-yes libssl-dev/lucid
 fi
 
-if [[ "${TOX_ENV}" == "pypy" ]]; then
-    sudo apt-get install -y pypy
-
-    # This is required because we need to get rid of the Travis installed PyPy
-    # or it'll take precedence over the PPA installed one.
-    sudo rm -rf /usr/local/pypy/bin
-fi
-
 pip install tox coveralls
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index 9f63250..b47f77e 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -13,3 +13,11 @@
 ``docs/contributing.rst``, or online at:
 
 https://cryptography.io/en/latest/contributing/
+
+Security issues
+---------------
+
+To report a security issue, please follow the special `security reporting
+guidelines`_, do not report them in the public issue tracker.
+
+.. _`security reporting guidelines`: https://cryptography.io/en/latest/security/
diff --git a/cryptography/exceptions.py b/cryptography/exceptions.py
index 391bed8..e9d8819 100644
--- a/cryptography/exceptions.py
+++ b/cryptography/exceptions.py
@@ -14,3 +14,19 @@
 
 class UnsupportedAlgorithm(Exception):
     pass
+
+
+class AlreadyFinalized(Exception):
+    pass
+
+
+class AlreadyUpdated(Exception):
+    pass
+
+
+class NotYetFinalized(Exception):
+    pass
+
+
+class InvalidTag(Exception):
+    pass
diff --git a/cryptography/hazmat/bindings/__init__.py b/cryptography/hazmat/bindings/__init__.py
index eb82899..bd15819 100644
--- a/cryptography/hazmat/bindings/__init__.py
+++ b/cryptography/hazmat/bindings/__init__.py
@@ -14,7 +14,10 @@
 from cryptography.hazmat.bindings import openssl
 
 
-_default_backend = openssl.backend
 _ALL_BACKENDS = [
     openssl.backend
 ]
+
+
+def default_backend():
+    return openssl.backend
diff --git a/cryptography/hazmat/bindings/interfaces.py b/cryptography/hazmat/bindings/interfaces.py
new file mode 100644
index 0000000..912476b
--- /dev/null
+++ b/cryptography/hazmat/bindings/interfaces.py
@@ -0,0 +1,66 @@
+# 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 abc
+
+import six
+
+
+class CipherBackend(six.with_metaclass(abc.ABCMeta)):
+    @abc.abstractmethod
+    def cipher_supported(self, cipher, mode):
+        """
+        Return True if the given cipher and mode are supported.
+        """
+
+    @abc.abstractmethod
+    def register_cipher_adapter(self, cipher, mode, adapter):
+        """
+        Register an adapter for a cipher and mode to a backend specific object.
+        """
+
+    @abc.abstractmethod
+    def create_symmetric_encryption_ctx(self, cipher, mode):
+        """
+        Get a CipherContext that can be used for encryption.
+        """
+
+    @abc.abstractmethod
+    def create_symmetric_decryption_ctx(self, cipher, mode):
+        """
+        Get a CipherContext that can be used for decryption.
+        """
+
+
+class HashBackend(six.with_metaclass(abc.ABCMeta)):
+    @abc.abstractmethod
+    def hash_supported(self, algorithm):
+        """
+        Return True if the hash algorithm is supported by this backend.
+        """
+
+    @abc.abstractmethod
+    def create_hash_ctx(self, algorithm):
+        """
+        Create a HashContext for calculating a message digest.
+        """
+
+
+class HMACBackend(six.with_metaclass(abc.ABCMeta)):
+    @abc.abstractmethod
+    def create_hmac_ctx(self, key, algorithm):
+        """
+        Create a HashContext for calculating a message authentication code.
+        """
diff --git a/cryptography/hazmat/bindings/openssl/backend.py b/cryptography/hazmat/bindings/openssl/backend.py
index 0c3d22d..6ab4dc2 100644
--- a/cryptography/hazmat/bindings/openssl/backend.py
+++ b/cryptography/hazmat/bindings/openssl/backend.py
@@ -18,16 +18,23 @@
 
 import cffi
 
-from cryptography.exceptions import UnsupportedAlgorithm
+from cryptography import utils
+from cryptography.exceptions import UnsupportedAlgorithm, InvalidTag
+from cryptography.hazmat.bindings.interfaces import (
+    CipherBackend, HashBackend, HMACBackend
+)
 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
+    CBC, CTR, ECB, OFB, CFB, GCM,
 )
 
 
+@utils.register_interface(CipherBackend)
+@utils.register_interface(HashBackend)
+@utils.register_interface(HMACBackend)
 class Backend(object):
     """
     OpenSSL API wrapper.
@@ -63,9 +70,8 @@
     def __init__(self):
         self._ensure_ffi_initialized()
 
-        self.ciphers = Ciphers(self)
-        self.hashes = Hashes(self)
-        self.hmacs = HMACs(self)
+        self._cipher_registry = {}
+        self._register_default_ciphers()
 
     @classmethod
     def _ensure_ffi_initialized(cls):
@@ -123,102 +129,23 @@
         """
         return self.ffi.string(self.lib.OPENSSL_VERSION_TEXT).decode("ascii")
 
+    def create_hmac_ctx(self, key, algorithm):
+        return _HMACContext(self, key, algorithm)
 
-class GetCipherByName(object):
-    def __init__(self, fmt):
-        self._fmt = fmt
+    def hash_supported(self, algorithm):
+        digest = self.lib.EVP_get_digestbyname(algorithm.name.encode("ascii"))
+        return digest != self.ffi.NULL
 
-    def __call__(self, backend, cipher, mode):
-        cipher_name = self._fmt.format(cipher=cipher, mode=mode).lower()
-        return backend.lib.EVP_get_cipherbyname(cipher_name.encode("ascii"))
+    def create_hash_ctx(self, algorithm):
+        return _HashContext(self, algorithm)
 
-
-@interfaces.register(interfaces.CipherContext)
-class _CipherContext(object):
-    _ENCRYPT = 1
-    _DECRYPT = 0
-
-    def __init__(self, backend, cipher, mode, operation):
-        self._backend = backend
-
-        ctx = self._backend.lib.EVP_CIPHER_CTX_new()
-        ctx = self._backend.ffi.gc(ctx, self._backend.lib.EVP_CIPHER_CTX_free)
-
-        registry = self._backend.ciphers._cipher_registry
-        try:
-            adapter = registry[type(cipher), type(mode)]
-        except KeyError:
-            raise UnsupportedAlgorithm
-
-        evp_cipher = adapter(self._backend, cipher, mode)
-        if evp_cipher == self._backend.ffi.NULL:
-            raise UnsupportedAlgorithm
-
-        if isinstance(mode, interfaces.ModeWithInitializationVector):
-            iv_nonce = mode.initialization_vector
-        elif isinstance(mode, interfaces.ModeWithNonce):
-            iv_nonce = mode.nonce
-        else:
-            iv_nonce = self._backend.ffi.NULL
-        # begin init with cipher and operation type
-        res = self._backend.lib.EVP_CipherInit_ex(ctx, evp_cipher,
-                                                  self._backend.ffi.NULL,
-                                                  self._backend.ffi.NULL,
-                                                  self._backend.ffi.NULL,
-                                                  operation)
-        assert res != 0
-        # set the key length to handle variable key ciphers
-        res = self._backend.lib.EVP_CIPHER_CTX_set_key_length(
-            ctx, len(cipher.key)
-        )
-        assert res != 0
-        # pass key/iv
-        res = self._backend.lib.EVP_CipherInit_ex(ctx, self._backend.ffi.NULL,
-                                                  self._backend.ffi.NULL,
-                                                  cipher.key,
-                                                  iv_nonce,
-                                                  operation)
-        assert res != 0
-        # We purposely disable padding here as it's handled higher up in the
-        # API.
-        self._backend.lib.EVP_CIPHER_CTX_set_padding(ctx, 0)
-        self._ctx = ctx
-
-    def update(self, data):
-        block_size = self._backend.lib.EVP_CIPHER_CTX_block_size(self._ctx)
-        buf = self._backend.ffi.new("unsigned char[]",
-                                    len(data) + block_size - 1)
-        outlen = self._backend.ffi.new("int *")
-        res = self._backend.lib.EVP_CipherUpdate(self._ctx, buf, outlen, data,
-                                                 len(data))
-        assert res != 0
-        return self._backend.ffi.buffer(buf)[:outlen[0]]
-
-    def finalize(self):
-        block_size = self._backend.lib.EVP_CIPHER_CTX_block_size(self._ctx)
-        buf = self._backend.ffi.new("unsigned char[]", block_size)
-        outlen = self._backend.ffi.new("int *")
-        res = self._backend.lib.EVP_CipherFinal_ex(self._ctx, buf, outlen)
-        assert res != 0
-        res = self._backend.lib.EVP_CIPHER_CTX_cleanup(self._ctx)
-        assert res == 1
-        return self._backend.ffi.buffer(buf)[:outlen[0]]
-
-
-class Ciphers(object):
-    def __init__(self, backend):
-        super(Ciphers, self).__init__()
-        self._backend = backend
-        self._cipher_registry = {}
-        self._register_default_ciphers()
-
-    def supported(self, cipher, mode):
+    def cipher_supported(self, cipher, mode):
         try:
             adapter = self._cipher_registry[type(cipher), type(mode)]
         except KeyError:
             return False
-        evp_cipher = adapter(self._backend, cipher, mode)
-        return self._backend.ffi.NULL != evp_cipher
+        evp_cipher = adapter(self, cipher, mode)
+        return self.ffi.NULL != evp_cipher
 
     def register_cipher_adapter(self, cipher_cls, mode_cls, adapter):
         if (cipher_cls, mode_cls) in self._cipher_registry:
@@ -254,97 +181,275 @@
             ECB,
             GetCipherByName("cast5-ecb")
         )
+        self.register_cipher_adapter(
+            ARC4,
+            type(None),
+            GetCipherByName("rc4")
+        )
+        self.register_cipher_adapter(
+            AES,
+            GCM,
+            GetCipherByName("{cipher.name}-{cipher.key_size}-{mode.name}")
+        )
 
-    def create_encrypt_ctx(self, cipher, mode):
-        return _CipherContext(self._backend, cipher, mode,
-                              _CipherContext._ENCRYPT)
+    def create_symmetric_encryption_ctx(self, cipher, mode):
+        return _CipherContext(self, cipher, mode, _CipherContext._ENCRYPT)
 
-    def create_decrypt_ctx(self, cipher, mode):
-        return _CipherContext(self._backend, cipher, mode,
-                              _CipherContext._DECRYPT)
+    def create_symmetric_decryption_ctx(self, cipher, mode):
+        return _CipherContext(self, cipher, mode, _CipherContext._DECRYPT)
+
+    def _handle_error(self, mode):
+        code = self.lib.ERR_get_error()
+        if not code and isinstance(mode, GCM):
+            raise InvalidTag
+        assert code != 0
+        lib = self.lib.ERR_GET_LIB(code)
+        func = self.lib.ERR_GET_FUNC(code)
+        reason = self.lib.ERR_GET_REASON(code)
+        return self._handle_error_code(lib, func, reason)
+
+    def _handle_error_code(self, lib, func, reason):
+        if lib == self.lib.ERR_LIB_EVP:
+            if func == self.lib.EVP_F_EVP_ENCRYPTFINAL_EX:
+                if reason == self.lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH:
+                    raise ValueError(
+                        "The length of the provided data is not a multiple of "
+                        "the block length"
+                    )
+            elif func == self.lib.EVP_F_EVP_DECRYPTFINAL_EX:
+                if reason == self.lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH:
+                    raise ValueError(
+                        "The length of the provided data is not a multiple of "
+                        "the block length"
+                    )
+
+        raise SystemError(
+            "Unknown error code from OpenSSL, you should probably file a bug."
+        )
 
 
-class Hashes(object):
-    def __init__(self, backend):
-        super(Hashes, self).__init__()
+class GetCipherByName(object):
+    def __init__(self, fmt):
+        self._fmt = fmt
+
+    def __call__(self, backend, cipher, mode):
+        cipher_name = self._fmt.format(cipher=cipher, mode=mode).lower()
+        return backend.lib.EVP_get_cipherbyname(cipher_name.encode("ascii"))
+
+
+@utils.register_interface(interfaces.CipherContext)
+@utils.register_interface(interfaces.AEADCipherContext)
+@utils.register_interface(interfaces.AEADEncryptionContext)
+class _CipherContext(object):
+    _ENCRYPT = 1
+    _DECRYPT = 0
+
+    def __init__(self, backend, cipher, mode, operation):
+        self._backend = backend
+        self._cipher = cipher
+        self._mode = mode
+        self._operation = operation
+        self._tag = None
+
+        ctx = self._backend.lib.EVP_CIPHER_CTX_new()
+        ctx = self._backend.ffi.gc(ctx, self._backend.lib.EVP_CIPHER_CTX_free)
+
+        registry = self._backend._cipher_registry
+        try:
+            adapter = registry[type(cipher), type(mode)]
+        except KeyError:
+            raise UnsupportedAlgorithm
+
+        evp_cipher = adapter(self._backend, cipher, mode)
+        if evp_cipher == self._backend.ffi.NULL:
+            raise UnsupportedAlgorithm
+
+        if isinstance(mode, interfaces.ModeWithInitializationVector):
+            iv_nonce = mode.initialization_vector
+        elif isinstance(mode, interfaces.ModeWithNonce):
+            iv_nonce = mode.nonce
+        else:
+            iv_nonce = self._backend.ffi.NULL
+        # begin init with cipher and operation type
+        res = self._backend.lib.EVP_CipherInit_ex(ctx, evp_cipher,
+                                                  self._backend.ffi.NULL,
+                                                  self._backend.ffi.NULL,
+                                                  self._backend.ffi.NULL,
+                                                  operation)
+        assert res != 0
+        # set the key length to handle variable key ciphers
+        res = self._backend.lib.EVP_CIPHER_CTX_set_key_length(
+            ctx, len(cipher.key)
+        )
+        assert res != 0
+        if isinstance(mode, GCM):
+            res = self._backend.lib.EVP_CIPHER_CTX_ctrl(
+                ctx, self._backend.lib.Cryptography_EVP_CTRL_GCM_SET_IVLEN,
+                len(iv_nonce), self._backend.ffi.NULL
+            )
+            assert res != 0
+            if operation == self._DECRYPT:
+                if not mode.tag:
+                    raise ValueError("Authentication tag must be supplied "
+                                     "when decrypting")
+                res = self._backend.lib.EVP_CIPHER_CTX_ctrl(
+                    ctx, self._backend.lib.Cryptography_EVP_CTRL_GCM_SET_TAG,
+                    len(mode.tag), mode.tag
+                )
+                assert res != 0
+            else:
+                if mode.tag:
+                    raise ValueError("Authentication tag must be None when "
+                                     "encrypting")
+
+        # pass key/iv
+        res = self._backend.lib.EVP_CipherInit_ex(ctx, self._backend.ffi.NULL,
+                                                  self._backend.ffi.NULL,
+                                                  cipher.key,
+                                                  iv_nonce,
+                                                  operation)
+        assert res != 0
+        # We purposely disable padding here as it's handled higher up in the
+        # API.
+        self._backend.lib.EVP_CIPHER_CTX_set_padding(ctx, 0)
+        self._ctx = ctx
+
+    def update(self, data):
+        buf = self._backend.ffi.new("unsigned char[]",
+                                    len(data) + self._cipher.block_size - 1)
+        outlen = self._backend.ffi.new("int *")
+        res = self._backend.lib.EVP_CipherUpdate(self._ctx, buf, outlen, data,
+                                                 len(data))
+        assert res != 0
+        return self._backend.ffi.buffer(buf)[:outlen[0]]
+
+    def finalize(self):
+        buf = self._backend.ffi.new("unsigned char[]", self._cipher.block_size)
+        outlen = self._backend.ffi.new("int *")
+        res = self._backend.lib.EVP_CipherFinal_ex(self._ctx, buf, outlen)
+        if res == 0:
+            self._backend._handle_error(self._mode)
+
+        if (isinstance(self._mode, GCM) and
+           self._operation == self._ENCRYPT):
+            block_byte_size = self._cipher.block_size // 8
+            tag_buf = self._backend.ffi.new("unsigned char[]", block_byte_size)
+            res = self._backend.lib.EVP_CIPHER_CTX_ctrl(
+                self._ctx, self._backend.lib.Cryptography_EVP_CTRL_GCM_GET_TAG,
+                block_byte_size, tag_buf
+            )
+            assert res != 0
+            self._tag = self._backend.ffi.buffer(tag_buf)[:]
+
+        res = self._backend.lib.EVP_CIPHER_CTX_cleanup(self._ctx)
+        assert res == 1
+        return self._backend.ffi.buffer(buf)[:outlen[0]]
+
+    def authenticate_additional_data(self, data):
+        outlen = self._backend.ffi.new("int *")
+        res = self._backend.lib.EVP_CipherUpdate(
+            self._ctx, self._backend.ffi.NULL, outlen, data, len(data)
+        )
+        assert res != 0
+
+    @property
+    def tag(self):
+        return self._tag
+
+
+@utils.register_interface(interfaces.HashContext)
+class _HashContext(object):
+    def __init__(self, backend, algorithm, ctx=None):
+        self.algorithm = algorithm
+
         self._backend = backend
 
-    def supported(self, hash_cls):
-        return (self._backend.ffi.NULL !=
-                self._backend.lib.EVP_get_digestbyname(
-                    hash_cls.name.encode("ascii")))
+        if ctx is None:
+            ctx = self._backend.lib.EVP_MD_CTX_create()
+            ctx = self._backend.ffi.gc(ctx,
+                                       self._backend.lib.EVP_MD_CTX_destroy)
+            evp_md = self._backend.lib.EVP_get_digestbyname(
+                algorithm.name.encode("ascii"))
+            assert evp_md != self._backend.ffi.NULL
+            res = self._backend.lib.EVP_DigestInit_ex(ctx, evp_md,
+                                                      self._backend.ffi.NULL)
+            assert res != 0
 
-    def create_ctx(self, hashobject):
-        ctx = self._backend.lib.EVP_MD_CTX_create()
-        ctx = self._backend.ffi.gc(ctx, self._backend.lib.EVP_MD_CTX_destroy)
-        evp_md = self._backend.lib.EVP_get_digestbyname(
-            hashobject.name.encode("ascii"))
-        assert evp_md != self._backend.ffi.NULL
-        res = self._backend.lib.EVP_DigestInit_ex(ctx, evp_md,
-                                                  self._backend.ffi.NULL)
-        assert res != 0
-        return ctx
+        self._ctx = ctx
 
-    def update_ctx(self, ctx, data):
-        res = self._backend.lib.EVP_DigestUpdate(ctx, data, len(data))
-        assert res != 0
-
-    def finalize_ctx(self, ctx, digest_size):
-        buf = self._backend.ffi.new("unsigned char[]", digest_size)
-        res = self._backend.lib.EVP_DigestFinal_ex(ctx, buf,
-                                                   self._backend.ffi.NULL)
-        assert res != 0
-        res = self._backend.lib.EVP_MD_CTX_cleanup(ctx)
-        assert res == 1
-        return self._backend.ffi.buffer(buf)[:digest_size]
-
-    def copy_ctx(self, ctx):
+    def copy(self):
         copied_ctx = self._backend.lib.EVP_MD_CTX_create()
         copied_ctx = self._backend.ffi.gc(copied_ctx,
                                           self._backend.lib.EVP_MD_CTX_destroy)
-        res = self._backend.lib.EVP_MD_CTX_copy_ex(copied_ctx, ctx)
+        res = self._backend.lib.EVP_MD_CTX_copy_ex(copied_ctx, self._ctx)
         assert res != 0
-        return copied_ctx
+        return _HashContext(self._backend, self.algorithm, ctx=copied_ctx)
+
+    def update(self, data):
+        res = self._backend.lib.EVP_DigestUpdate(self._ctx, data, len(data))
+        assert res != 0
+
+    def finalize(self):
+        buf = self._backend.ffi.new("unsigned char[]",
+                                    self.algorithm.digest_size)
+        res = self._backend.lib.EVP_DigestFinal_ex(self._ctx, buf,
+                                                   self._backend.ffi.NULL)
+        assert res != 0
+        res = self._backend.lib.EVP_MD_CTX_cleanup(self._ctx)
+        assert res == 1
+        return self._backend.ffi.buffer(buf)[:]
 
 
-class HMACs(object):
-    def __init__(self, backend):
-        super(HMACs, self).__init__()
+@utils.register_interface(interfaces.HashContext)
+class _HMACContext(object):
+    def __init__(self, backend, key, algorithm, ctx=None):
+        self.algorithm = algorithm
         self._backend = backend
 
-    def create_ctx(self, key, hash_cls):
-        ctx = self._backend.ffi.new("HMAC_CTX *")
-        self._backend.lib.HMAC_CTX_init(ctx)
-        ctx = self._backend.ffi.gc(ctx, self._backend.lib.HMAC_CTX_cleanup)
-        evp_md = self._backend.lib.EVP_get_digestbyname(
-            hash_cls.name.encode('ascii'))
-        assert evp_md != self._backend.ffi.NULL
-        res = self._backend.lib.Cryptography_HMAC_Init_ex(
-            ctx, key, len(key), evp_md, self._backend.ffi.NULL
-        )
-        assert res != 0
-        return ctx
+        if ctx is None:
+            ctx = self._backend.ffi.new("HMAC_CTX *")
+            self._backend.lib.HMAC_CTX_init(ctx)
+            ctx = self._backend.ffi.gc(ctx, self._backend.lib.HMAC_CTX_cleanup)
+            evp_md = self._backend.lib.EVP_get_digestbyname(
+                algorithm.name.encode('ascii'))
+            assert evp_md != self._backend.ffi.NULL
+            res = self._backend.lib.Cryptography_HMAC_Init_ex(
+                ctx, key, len(key), evp_md, self._backend.ffi.NULL
+            )
+            assert res != 0
 
-    def update_ctx(self, ctx, data):
-        res = self._backend.lib.Cryptography_HMAC_Update(ctx, data, len(data))
-        assert res != 0
+        self._ctx = ctx
+        self._key = key
 
-    def finalize_ctx(self, ctx, digest_size):
-        buf = self._backend.ffi.new("unsigned char[]", digest_size)
-        buflen = self._backend.ffi.new("unsigned int *", digest_size)
-        res = self._backend.lib.Cryptography_HMAC_Final(ctx, buf, buflen)
-        assert res != 0
-        self._backend.lib.HMAC_CTX_cleanup(ctx)
-        return self._backend.ffi.buffer(buf)[:digest_size]
-
-    def copy_ctx(self, ctx):
+    def copy(self):
         copied_ctx = self._backend.ffi.new("HMAC_CTX *")
         self._backend.lib.HMAC_CTX_init(copied_ctx)
-        copied_ctx = self._backend.ffi.gc(copied_ctx,
-                                          self._backend.lib.HMAC_CTX_cleanup)
-        res = self._backend.lib.Cryptography_HMAC_CTX_copy(copied_ctx, ctx)
+        copied_ctx = self._backend.ffi.gc(
+            copied_ctx, self._backend.lib.HMAC_CTX_cleanup
+        )
+        res = self._backend.lib.Cryptography_HMAC_CTX_copy(
+            copied_ctx, self._ctx
+        )
         assert res != 0
-        return copied_ctx
+        return _HMACContext(
+            self._backend, self._key, self.algorithm, ctx=copied_ctx
+        )
+
+    def update(self, data):
+        res = self._backend.lib.Cryptography_HMAC_Update(
+            self._ctx, data, len(data)
+        )
+        assert res != 0
+
+    def finalize(self):
+        buf = self._backend.ffi.new("unsigned char[]",
+                                    self.algorithm.digest_size)
+        buflen = self._backend.ffi.new("unsigned int *",
+                                       self.algorithm.digest_size)
+        res = self._backend.lib.Cryptography_HMAC_Final(self._ctx, buf, buflen)
+        assert res != 0
+        self._backend.lib.HMAC_CTX_cleanup(self._ctx)
+        return self._backend.ffi.buffer(buf)[:]
 
 
 backend = Backend()
diff --git a/cryptography/hazmat/bindings/openssl/bignum.py b/cryptography/hazmat/bindings/openssl/bignum.py
index fcfadff..1b0fe5a 100644
--- a/cryptography/hazmat/bindings/openssl/bignum.py
+++ b/cryptography/hazmat/bindings/openssl/bignum.py
@@ -28,6 +28,9 @@
 
 char *BN_bn2hex(const BIGNUM *);
 int BN_hex2bn(BIGNUM **, const char *);
+int BN_dec2bn(BIGNUM **, const char *);
+
+int BN_num_bits(const BIGNUM *);
 """
 
 MACROS = """
diff --git a/cryptography/hazmat/bindings/openssl/engine.py b/cryptography/hazmat/bindings/openssl/engine.py
index b76befc..1f37766 100644
--- a/cryptography/hazmat/bindings/openssl/engine.py
+++ b/cryptography/hazmat/bindings/openssl/engine.py
@@ -36,6 +36,16 @@
 int ENGINE_ctrl_cmd_string(ENGINE *, const char *, const char *, int);
 int ENGINE_set_default(ENGINE *, unsigned int);
 int ENGINE_register_complete(ENGINE *);
+
+int ENGINE_set_default_RSA(ENGINE *);
+int ENGINE_set_default_string(ENGINE *, const char *);
+int ENGINE_set_default_DSA(ENGINE *);
+int ENGINE_set_default_ECDH(ENGINE *);
+int ENGINE_set_default_ECDSA(ENGINE *);
+int ENGINE_set_default_DH(ENGINE *);
+int ENGINE_set_default_RAND(ENGINE *);
+int ENGINE_set_default_ciphers(ENGINE *);
+int ENGINE_set_default_digests(ENGINE *);
 """
 
 MACROS = """
diff --git a/cryptography/hazmat/bindings/openssl/err.py b/cryptography/hazmat/bindings/openssl/err.py
index 6a36dee..f31c240 100644
--- a/cryptography/hazmat/bindings/openssl/err.py
+++ b/cryptography/hazmat/bindings/openssl/err.py
@@ -21,6 +21,20 @@
     const char *string;
 };
 typedef struct ERR_string_data_st ERR_STRING_DATA;
+
+static const int ERR_LIB_EVP;
+static const int ERR_LIB_PEM;
+
+static const int EVP_F_EVP_ENCRYPTFINAL_EX;
+static const int EVP_F_EVP_DECRYPTFINAL_EX;
+
+static const int EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH;
+
+static const int PEM_F_PEM_READ_BIO_PRIVATEKEY;
+static const int PEM_F_D2I_PKCS8PRIVATEKEY_BIO;
+
+static const int PEM_R_BAD_PASSWORD_READ;
+static const int ASN1_R_BAD_PASSWORD_READ;
 """
 
 FUNCTIONS = """
diff --git a/cryptography/hazmat/bindings/openssl/evp.py b/cryptography/hazmat/bindings/openssl/evp.py
index da54f89..8cb4461 100644
--- a/cryptography/hazmat/bindings/openssl/evp.py
+++ b/cryptography/hazmat/bindings/openssl/evp.py
@@ -16,10 +16,13 @@
 """
 
 TYPES = """
+typedef ... EVP_CIPHER;
 typedef struct {
+    const EVP_CIPHER *cipher;
+    ENGINE *engine;
+    int encrypt;
     ...;
 } EVP_CIPHER_CTX;
-typedef ... EVP_CIPHER;
 typedef ... EVP_MD;
 typedef struct env_md_ctx_st EVP_MD_CTX;
 
diff --git a/cryptography/hazmat/bindings/openssl/pem.py b/cryptography/hazmat/bindings/openssl/pem.py
index 00f0dc3..cef7839 100644
--- a/cryptography/hazmat/bindings/openssl/pem.py
+++ b/cryptography/hazmat/bindings/openssl/pem.py
@@ -29,6 +29,15 @@
 EVP_PKEY *PEM_read_bio_PrivateKey(BIO *, EVP_PKEY **, pem_password_cb *,
                                   void *);
 
+int PEM_write_bio_PKCS8PrivateKey(BIO *, EVP_PKEY *, const EVP_CIPHER *,
+                                  char *, int, pem_password_cb *, void *);
+
+int i2d_PKCS8PrivateKey_bio(BIO *, EVP_PKEY *, const EVP_CIPHER *,
+                            char *, int, pem_password_cb *, void *);
+
+EVP_PKEY *d2i_PKCS8PrivateKey_bio(BIO *, EVP_PKEY **, pem_password_cb *,
+                                  void *);
+
 int PEM_write_bio_X509_REQ(BIO *, X509_REQ *);
 
 X509_REQ *PEM_read_bio_X509_REQ(BIO *, X509_REQ **, pem_password_cb *, void *);
diff --git a/cryptography/hazmat/bindings/openssl/rsa.py b/cryptography/hazmat/bindings/openssl/rsa.py
index 21ed5d6..ad0d37b 100644
--- a/cryptography/hazmat/bindings/openssl/rsa.py
+++ b/cryptography/hazmat/bindings/openssl/rsa.py
@@ -16,15 +16,40 @@
 """
 
 TYPES = """
-typedef ... RSA;
+typedef struct rsa_st {
+    BIGNUM *n;
+    BIGNUM *e;
+    BIGNUM *d;
+    BIGNUM *p;
+    BIGNUM *q;
+    BIGNUM *dmp1;
+    BIGNUM *dmq1;
+    BIGNUM *iqmp;
+    ...;
+} RSA;
 typedef ... BN_GENCB;
+static const int RSA_PKCS1_PADDING;
+static const int RSA_SSLV23_PADDING;
+static const int RSA_NO_PADDING;
+static const int RSA_PKCS1_OAEP_PADDING;
+static const int RSA_X931_PADDING;
 """
 
 FUNCTIONS = """
 RSA *RSA_new();
 void RSA_free(RSA *);
+int RSA_size(const RSA *);
 int RSA_generate_key_ex(RSA *, int, BIGNUM *, BN_GENCB *);
 int RSA_check_key(const RSA *);
+RSA *RSAPublicKey_dup(RSA *);
+int RSA_public_encrypt(int, const unsigned char *, unsigned char *,
+                       RSA *, int);
+int RSA_private_encrypt(int, const unsigned char *, unsigned char *,
+                        RSA *, int);
+int RSA_public_decrypt(int, const unsigned char *, unsigned char *,
+                       RSA *, int);
+int RSA_private_decrypt(int, const unsigned char *, unsigned char *,
+                        RSA *, int);
 """
 
 MACROS = """
diff --git a/cryptography/hazmat/bindings/openssl/ssl.py b/cryptography/hazmat/bindings/openssl/ssl.py
index 58a64f0..0461130 100644
--- a/cryptography/hazmat/bindings/openssl/ssl.py
+++ b/cryptography/hazmat/bindings/openssl/ssl.py
@@ -16,13 +16,200 @@
 """
 
 TYPES = """
+static const int SSL_FILETYPE_PEM;
+static const int SSL_FILETYPE_ASN1;
+static const int SSL_ERROR_NONE;
+static const int SSL_ERROR_ZERO_RETURN;
+static const int SSL_ERROR_WANT_READ;
+static const int SSL_ERROR_WANT_WRITE;
+static const int SSL_ERROR_WANT_X509_LOOKUP;
+static const int SSL_ERROR_SYSCALL;
+static const int SSL_ERROR_SSL;
+static const int SSL_SENT_SHUTDOWN;
+static const int SSL_RECEIVED_SHUTDOWN;
+static const int SSL_OP_NO_SSLv2;
+static const int SSL_OP_NO_SSLv3;
+static const int SSL_OP_NO_TLSv1;
+static const int SSL_OP_SINGLE_DH_USE;
+static const int SSL_OP_EPHEMERAL_RSA;
+static const int SSL_OP_MICROSOFT_SESS_ID_BUG;
+static const int SSL_OP_NETSCAPE_CHALLENGE_BUG;
+static const int SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG;
+static const int SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG;
+static const int SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER;
+static const int SSL_OP_MSIE_SSLV2_RSA_PADDING;
+static const int SSL_OP_SSLEAY_080_CLIENT_DH_BUG;
+static const int SSL_OP_TLS_D5_BUG;
+static const int SSL_OP_TLS_BLOCK_PADDING_BUG;
+static const int SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS;
+static const int SSL_OP_CIPHER_SERVER_PREFERENCE;
+static const int SSL_OP_TLS_ROLLBACK_BUG;
+static const int SSL_OP_PKCS1_CHECK_1;
+static const int SSL_OP_PKCS1_CHECK_2;
+static const int SSL_OP_NETSCAPE_CA_DN_BUG;
+static const int SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG;
+static const int SSL_OP_NO_QUERY_MTU;
+static const int SSL_OP_COOKIE_EXCHANGE;
+static const int SSL_OP_NO_TICKET;
+static const int SSL_OP_ALL;
+static const int SSL_VERIFY_PEER;
+static const int SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
+static const int SSL_VERIFY_CLIENT_ONCE;
+static const int SSL_VERIFY_NONE;
+static const int SSL_SESS_CACHE_OFF;
+static const int SSL_SESS_CACHE_CLIENT;
+static const int SSL_SESS_CACHE_SERVER;
+static const int SSL_SESS_CACHE_BOTH;
+static const int SSL_SESS_CACHE_NO_AUTO_CLEAR;
+static const int SSL_SESS_CACHE_NO_INTERNAL_LOOKUP;
+static const int SSL_SESS_CACHE_NO_INTERNAL_STORE;
+static const int SSL_SESS_CACHE_NO_INTERNAL;
+static const int SSL_ST_CONNECT;
+static const int SSL_ST_ACCEPT;
+static const int SSL_ST_MASK;
+static const int SSL_ST_INIT;
+static const int SSL_ST_BEFORE;
+static const int SSL_ST_OK;
+static const int SSL_ST_RENEGOTIATE;
+static const int SSL_CB_LOOP;
+static const int SSL_CB_EXIT;
+static const int SSL_CB_READ;
+static const int SSL_CB_WRITE;
+static const int SSL_CB_ALERT;
+static const int SSL_CB_READ_ALERT;
+static const int SSL_CB_WRITE_ALERT;
+static const int SSL_CB_ACCEPT_LOOP;
+static const int SSL_CB_ACCEPT_EXIT;
+static const int SSL_CB_CONNECT_LOOP;
+static const int SSL_CB_CONNECT_EXIT;
+static const int SSL_CB_HANDSHAKE_START;
+static const int SSL_CB_HANDSHAKE_DONE;
+static const int SSL_MODE_ENABLE_PARTIAL_WRITE;
+static const int SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER;
+static const int SSL_MODE_AUTO_RETRY;
+static const int SSL3_RANDOM_SIZE;
+typedef ... X509_STORE_CTX;
+static const int X509_V_OK;
+typedef ... SSL_METHOD;
+typedef ... SSL_CTX;
+
+typedef struct {
+    int master_key_length;
+    unsigned char master_key[...];
+    ...;
+} SSL_SESSION;
+
+typedef struct {
+    unsigned char server_random[...];
+    unsigned char client_random[...];
+    ...;
+} SSL3_STATE;
+
+typedef struct {
+    SSL3_STATE *s3;
+    SSL_SESSION *session;
+    ...;
+} SSL;
+
+static const int TLSEXT_NAMETYPE_host_name;
 """
 
 FUNCTIONS = """
 void SSL_load_error_strings();
+
+int SSL_library_init();
+
+/*  SSL */
+SSL_CTX *SSL_set_SSL_CTX(SSL *, SSL_CTX *);
+SSL_SESSION *SSL_get1_session(SSL *);
+int SSL_set_session(SSL *, SSL_SESSION *);
+int SSL_get_verify_mode(const SSL *);
+void SSL_set_verify_depth(SSL *, int);
+int SSL_get_verify_depth(const SSL *);
+SSL *SSL_new(SSL_CTX *);
+void SSL_free(SSL *);
+int SSL_set_fd(SSL *, int);
+void SSL_set_bio(SSL *, BIO *, BIO *);
+void SSL_set_connect_state(SSL *);
+void SSL_set_accept_state(SSL *);
+void SSL_set_shutdown(SSL *, int);
+int SSL_get_shutdown(const SSL *);
+int SSL_pending(const SSL *);
+int SSL_write(SSL *, const void *, int);
+int SSL_read(SSL *, void *, int);
+X509 *SSL_get_peer_certificate(const SSL *);
+int SSL_get_error(const SSL *, int);
+int SSL_do_handshake(SSL *);
+int SSL_shutdown(SSL *);
+const char *SSL_get_cipher_list(const SSL *, int);
+
+/*  context */
+void SSL_CTX_free(SSL_CTX *);
+long SSL_CTX_set_timeout(SSL_CTX *, long);
+int SSL_CTX_set_default_verify_paths(SSL_CTX *);
+void SSL_CTX_set_verify_depth(SSL_CTX *, int);
+int SSL_CTX_get_verify_mode(const SSL_CTX *);
+int SSL_CTX_get_verify_depth(const SSL_CTX *);
+int SSL_CTX_set_cipher_list(SSL_CTX *, const char *);
+int SSL_CTX_load_verify_locations(SSL_CTX *, const char *, const char *);
+void SSL_CTX_set_default_passwd_cb(SSL_CTX *, pem_password_cb *);
+void SSL_CTX_set_default_passwd_cb_userdata(SSL_CTX *, void *);
+int SSL_CTX_use_certificate(SSL_CTX *, X509 *);
+int SSL_CTX_use_certificate_file(SSL_CTX *, const char *, int);
+int SSL_CTX_use_certificate_chain_file(SSL_CTX *, const char *);
+int SSL_CTX_use_PrivateKey(SSL_CTX *, EVP_PKEY *);
+int SSL_CTX_use_PrivateKey_file(SSL_CTX *, const char *, int);
+void SSL_CTX_set_cert_store(SSL_CTX *, X509_STORE *);
+X509_STORE *SSL_CTX_get_cert_store(const SSL_CTX *);
+int SSL_CTX_add_client_CA(SSL_CTX *, X509 *);
+
+/*  X509_STORE_CTX */
+int X509_STORE_CTX_get_error(X509_STORE_CTX *);
+void X509_STORE_CTX_set_error(X509_STORE_CTX *, int);
+int X509_STORE_CTX_get_error_depth(X509_STORE_CTX *);
+X509 *X509_STORE_CTX_get_current_cert(X509_STORE_CTX *);
+
+/*  SSL_SESSION */
+void SSL_SESSION_free(SSL_SESSION *);
 """
 
-MACROS = """
+MACROS = MACROS = """
+long SSL_set_mode(SSL *, long);
+long SSL_get_mode(SSL *);
+
+long SSL_set_options(SSL *, long);
+long SSL_get_options(SSL *);
+
+int SSL_want_read(const SSL *);
+int SSL_want_write(const SSL *);
+
+int SSL_total_renegotiations(const SSL *);
+
+long SSL_CTX_set_options(SSL_CTX *, long);
+long SSL_CTX_get_options(SSL_CTX *);
+long SSL_CTX_set_mode(SSL_CTX *, long);
+long SSL_CTX_get_mode(SSL_CTX *);
+long SSL_CTX_set_session_cache_mode(SSL_CTX *, long);
+long SSL_CTX_get_session_cache_mode(SSL_CTX *);
+long SSL_CTX_set_tmp_dh(SSL_CTX *, DH *);
+long SSL_CTX_add_extra_chain_cert(SSL_CTX *, X509 *);
+
+/*- These aren't macros these functions are all const X on openssl > 1.0.x -*/
+
+/*  methods */
+const SSL_METHOD *SSLv3_method();
+const SSL_METHOD *SSLv3_server_method();
+const SSL_METHOD *SSLv3_client_method();
+const SSL_METHOD *TLSv1_method();
+const SSL_METHOD *TLSv1_server_method();
+const SSL_METHOD *TLSv1_client_method();
+const SSL_METHOD *SSLv23_method();
+const SSL_METHOD *SSLv23_server_method();
+const SSL_METHOD *SSLv23_client_method();
+
+/*- These aren't macros these arguments are all const X on openssl > 1.0.x -*/
+SSL_CTX *SSL_CTX_new(const SSL_METHOD *);
+long SSL_CTX_get_timeout(const SSL_CTX *);
 """
 
 CUSTOMIZATIONS = """
diff --git a/cryptography/hazmat/primitives/ciphers/algorithms.py b/cryptography/hazmat/primitives/ciphers/algorithms.py
index 8046bd2..a206b27 100644
--- a/cryptography/hazmat/primitives/ciphers/algorithms.py
+++ b/cryptography/hazmat/primitives/ciphers/algorithms.py
@@ -13,105 +13,101 @@
 
 from __future__ import absolute_import, division, print_function
 
+from cryptography import utils
+from cryptography.hazmat.primitives import interfaces
 
+
+def _verify_key_size(algorithm, key):
+    # Verify that the key size matches the expected key size
+    if len(key) * 8 not in algorithm.key_sizes:
+        raise ValueError("Invalid key size ({0}) for {1}".format(
+            len(key) * 8, algorithm.name
+        ))
+    return key
+
+
+@utils.register_interface(interfaces.CipherAlgorithm)
 class AES(object):
     name = "AES"
     block_size = 128
     key_sizes = frozenset([128, 192, 256])
 
     def __init__(self, key):
-        super(AES, 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
-            ))
+        self.key = _verify_key_size(self, key)
 
     @property
     def key_size(self):
         return len(self.key) * 8
 
 
+@utils.register_interface(interfaces.CipherAlgorithm)
 class Camellia(object):
     name = "camellia"
     block_size = 128
     key_sizes = frozenset([128, 192, 256])
 
     def __init__(self, key):
-        super(Camellia, 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
-            ))
+        self.key = _verify_key_size(self, key)
 
     @property
     def key_size(self):
         return len(self.key) * 8
 
 
+@utils.register_interface(interfaces.CipherAlgorithm)
 class TripleDES(object):
     name = "3DES"
     block_size = 64
     key_sizes = frozenset([64, 128, 192])
 
     def __init__(self, key):
-        super(TripleDES, self).__init__()
         if len(key) == 8:
             key += key + key
         elif len(key) == 16:
             key += key[:8]
-        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
-            ))
+        self.key = _verify_key_size(self, key)
 
     @property
     def key_size(self):
         return len(self.key) * 8
 
 
+@utils.register_interface(interfaces.CipherAlgorithm)
 class Blowfish(object):
     name = "Blowfish"
     block_size = 64
     key_sizes = frozenset(range(32, 449, 8))
 
     def __init__(self, key):
-        super(Blowfish, 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
-            ))
+        self.key = _verify_key_size(self, key)
 
     @property
     def key_size(self):
         return len(self.key) * 8
 
 
+@utils.register_interface(interfaces.CipherAlgorithm)
 class CAST5(object):
     name = "CAST5"
     block_size = 64
-    key_sizes = frozenset([40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128])
+    key_sizes = frozenset(range(40, 129, 8))
 
     def __init__(self, key):
-        super(CAST5, self).__init__()
-        self.key = key
+        self.key = _verify_key_size(self, 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
+
+
+@utils.register_interface(interfaces.CipherAlgorithm)
+class ARC4(object):
+    name = "RC4"
+    block_size = 1
+    key_sizes = frozenset([40, 56, 64, 80, 128, 192, 256])
+
+    def __init__(self, key):
+        self.key = _verify_key_size(self, key)
 
     @property
     def key_size(self):
diff --git a/cryptography/hazmat/primitives/ciphers/base.py b/cryptography/hazmat/primitives/ciphers/base.py
index 1599308..b8615cb 100644
--- a/cryptography/hazmat/primitives/ciphers/base.py
+++ b/cryptography/hazmat/primitives/ciphers/base.py
@@ -13,46 +13,97 @@
 
 from __future__ import absolute_import, division, print_function
 
+from cryptography import utils
+from cryptography.exceptions import (
+    AlreadyFinalized, NotYetFinalized, AlreadyUpdated,
+)
 from cryptography.hazmat.primitives import interfaces
 
 
 class Cipher(object):
-    def __init__(self, algorithm, mode, backend=None):
-        super(Cipher, self).__init__()
-
-        if backend is None:
-            from cryptography.hazmat.bindings import (
-                _default_backend as backend,
-            )
+    def __init__(self, algorithm, mode, backend):
+        if not isinstance(algorithm, interfaces.CipherAlgorithm):
+            raise TypeError("Expected interface of interfaces.CipherAlgorithm")
 
         self.algorithm = algorithm
         self.mode = mode
         self._backend = backend
 
     def encryptor(self):
-        return _CipherContext(
-            self._backend.ciphers.create_encrypt_ctx(self.algorithm,
-                                                     self.mode))
+        ctx = self._backend.create_symmetric_encryption_ctx(
+            self.algorithm, self.mode
+        )
+        return self._wrap_ctx(ctx, True)
 
     def decryptor(self):
-        return _CipherContext(
-            self._backend.ciphers.create_decrypt_ctx(self.algorithm,
-                                                     self.mode))
+        ctx = self._backend.create_symmetric_decryption_ctx(
+            self.algorithm, self.mode
+        )
+        return self._wrap_ctx(ctx, False)
+
+    def _wrap_ctx(self, ctx, encrypt):
+        if isinstance(self.mode, interfaces.ModeWithAuthenticationTag):
+            if encrypt:
+                return _AEADEncryptionContext(ctx)
+            else:
+                return _AEADCipherContext(ctx)
+        else:
+            return _CipherContext(ctx)
 
 
-@interfaces.register(interfaces.CipherContext)
+@utils.register_interface(interfaces.CipherContext)
 class _CipherContext(object):
     def __init__(self, ctx):
         self._ctx = ctx
 
     def update(self, data):
         if self._ctx is None:
-            raise ValueError("Context was already finalized")
+            raise AlreadyFinalized("Context was already finalized")
         return self._ctx.update(data)
 
     def finalize(self):
         if self._ctx is None:
-            raise ValueError("Context was already finalized")
+            raise AlreadyFinalized("Context was already finalized")
         data = self._ctx.finalize()
         self._ctx = None
         return data
+
+
+@utils.register_interface(interfaces.AEADCipherContext)
+@utils.register_interface(interfaces.CipherContext)
+class _AEADCipherContext(object):
+    def __init__(self, ctx):
+        self._ctx = ctx
+        self._tag = None
+        self._updated = False
+
+    def update(self, data):
+        if self._ctx is None:
+            raise AlreadyFinalized("Context was already finalized")
+        self._updated = True
+        return self._ctx.update(data)
+
+    def finalize(self):
+        if self._ctx is None:
+            raise AlreadyFinalized("Context was already finalized")
+        data = self._ctx.finalize()
+        self._tag = self._ctx.tag
+        self._ctx = None
+        return data
+
+    def authenticate_additional_data(self, data):
+        if self._ctx is None:
+            raise AlreadyFinalized("Context was already finalized")
+        if self._updated:
+            raise AlreadyUpdated("Update has been called on this context")
+        self._ctx.authenticate_additional_data(data)
+
+
+@utils.register_interface(interfaces.AEADEncryptionContext)
+class _AEADEncryptionContext(_AEADCipherContext):
+    @property
+    def tag(self):
+        if self._ctx is not None:
+            raise NotYetFinalized("You must finalize encryption before "
+                                  "getting the tag")
+        return self._tag
diff --git a/cryptography/hazmat/primitives/ciphers/modes.py b/cryptography/hazmat/primitives/ciphers/modes.py
index e54872a..e1c7018 100644
--- a/cryptography/hazmat/primitives/ciphers/modes.py
+++ b/cryptography/hazmat/primitives/ciphers/modes.py
@@ -13,49 +13,57 @@
 
 from __future__ import absolute_import, division, print_function
 
+from cryptography import utils
 from cryptography.hazmat.primitives import interfaces
 
 
-@interfaces.register(interfaces.Mode)
-@interfaces.register(interfaces.ModeWithInitializationVector)
+@utils.register_interface(interfaces.Mode)
+@utils.register_interface(interfaces.ModeWithInitializationVector)
 class CBC(object):
     name = "CBC"
 
     def __init__(self, initialization_vector):
-        super(CBC, self).__init__()
         self.initialization_vector = initialization_vector
 
 
-@interfaces.register(interfaces.Mode)
+@utils.register_interface(interfaces.Mode)
 class ECB(object):
     name = "ECB"
 
 
-@interfaces.register(interfaces.Mode)
-@interfaces.register(interfaces.ModeWithInitializationVector)
+@utils.register_interface(interfaces.Mode)
+@utils.register_interface(interfaces.ModeWithInitializationVector)
 class OFB(object):
     name = "OFB"
 
     def __init__(self, initialization_vector):
-        super(OFB, self).__init__()
         self.initialization_vector = initialization_vector
 
 
-@interfaces.register(interfaces.Mode)
-@interfaces.register(interfaces.ModeWithInitializationVector)
+@utils.register_interface(interfaces.Mode)
+@utils.register_interface(interfaces.ModeWithInitializationVector)
 class CFB(object):
     name = "CFB"
 
     def __init__(self, initialization_vector):
-        super(CFB, self).__init__()
         self.initialization_vector = initialization_vector
 
 
-@interfaces.register(interfaces.Mode)
-@interfaces.register(interfaces.ModeWithNonce)
+@utils.register_interface(interfaces.Mode)
+@utils.register_interface(interfaces.ModeWithNonce)
 class CTR(object):
     name = "CTR"
 
     def __init__(self, nonce):
-        super(CTR, self).__init__()
         self.nonce = nonce
+
+
+@utils.register_interface(interfaces.Mode)
+@utils.register_interface(interfaces.ModeWithInitializationVector)
+@utils.register_interface(interfaces.ModeWithAuthenticationTag)
+class GCM(object):
+    name = "GCM"
+
+    def __init__(self, initialization_vector, tag=None):
+        self.initialization_vector = initialization_vector
+        self.tag = tag
diff --git a/cryptography/hazmat/primitives/hashes.py b/cryptography/hazmat/primitives/hashes.py
index bdad5e1..bee188b 100644
--- a/cryptography/hazmat/primitives/hashes.py
+++ b/cryptography/hazmat/primitives/hashes.py
@@ -15,91 +15,97 @@
 
 import six
 
+from cryptography import utils
+from cryptography.exceptions import AlreadyFinalized
 from cryptography.hazmat.primitives import interfaces
 
 
-@interfaces.register(interfaces.HashContext)
+@utils.register_interface(interfaces.HashContext)
 class Hash(object):
-    def __init__(self, algorithm, backend=None, ctx=None):
+    def __init__(self, algorithm, backend, ctx=None):
         if not isinstance(algorithm, interfaces.HashAlgorithm):
             raise TypeError("Expected instance of interfaces.HashAlgorithm.")
         self.algorithm = algorithm
 
-        if backend is None:
-            from cryptography.hazmat.bindings import _default_backend
-            backend = _default_backend
-
         self._backend = backend
 
         if ctx is None:
-            self._ctx = self._backend.hashes.create_ctx(self.algorithm)
+            self._ctx = self._backend.create_hash_ctx(self.algorithm)
         else:
-            self._ctx = None
+            self._ctx = ctx
 
     def update(self, data):
+        if self._ctx is None:
+            raise AlreadyFinalized("Context was already finalized")
         if isinstance(data, six.text_type):
             raise TypeError("Unicode-objects must be encoded before hashing")
-        self._backend.hashes.update_ctx(self._ctx, data)
+        self._ctx.update(data)
 
     def copy(self):
-        return self.__class__(self.algorithm, backend=self._backend,
-                              ctx=self._backend.hashes.copy_ctx(self._ctx))
+        if self._ctx is None:
+            raise AlreadyFinalized("Context was already finalized")
+        return Hash(
+            self.algorithm, backend=self._backend, ctx=self._ctx.copy()
+        )
 
     def finalize(self):
-        return self._backend.hashes.finalize_ctx(self._ctx,
-                                                 self.algorithm.digest_size)
+        if self._ctx is None:
+            raise AlreadyFinalized("Context was already finalized")
+        digest = self._ctx.finalize()
+        self._ctx = None
+        return digest
 
 
-@interfaces.register(interfaces.HashAlgorithm)
+@utils.register_interface(interfaces.HashAlgorithm)
 class SHA1(object):
     name = "sha1"
     digest_size = 20
     block_size = 64
 
 
-@interfaces.register(interfaces.HashAlgorithm)
+@utils.register_interface(interfaces.HashAlgorithm)
 class SHA224(object):
     name = "sha224"
     digest_size = 28
     block_size = 64
 
 
-@interfaces.register(interfaces.HashAlgorithm)
+@utils.register_interface(interfaces.HashAlgorithm)
 class SHA256(object):
     name = "sha256"
     digest_size = 32
     block_size = 64
 
 
-@interfaces.register(interfaces.HashAlgorithm)
+@utils.register_interface(interfaces.HashAlgorithm)
 class SHA384(object):
     name = "sha384"
     digest_size = 48
     block_size = 128
 
 
-@interfaces.register(interfaces.HashAlgorithm)
+@utils.register_interface(interfaces.HashAlgorithm)
 class SHA512(object):
     name = "sha512"
     digest_size = 64
     block_size = 128
 
 
-@interfaces.register(interfaces.HashAlgorithm)
+@utils.register_interface(interfaces.HashAlgorithm)
 class RIPEMD160(object):
     name = "ripemd160"
     digest_size = 20
     block_size = 64
 
 
-@interfaces.register(interfaces.HashAlgorithm)
+@utils.register_interface(interfaces.HashAlgorithm)
 class Whirlpool(object):
     name = "whirlpool"
     digest_size = 64
     block_size = 64
 
 
-@interfaces.register(interfaces.HashAlgorithm)
+@utils.register_interface(interfaces.HashAlgorithm)
 class MD5(object):
     name = "md5"
     digest_size = 16
diff --git a/cryptography/hazmat/primitives/hmac.py b/cryptography/hazmat/primitives/hmac.py
index 1457ed7..618bccc 100644
--- a/cryptography/hazmat/primitives/hmac.py
+++ b/cryptography/hazmat/primitives/hmac.py
@@ -15,37 +15,45 @@
 
 import six
 
+from cryptography import utils
+from cryptography.exceptions import AlreadyFinalized
 from cryptography.hazmat.primitives import interfaces
 
 
-@interfaces.register(interfaces.HashContext)
+@utils.register_interface(interfaces.HashContext)
 class HMAC(object):
-    def __init__(self, key, algorithm, ctx=None, backend=None):
-        super(HMAC, self).__init__()
+    def __init__(self, key, algorithm, backend, ctx=None):
         if not isinstance(algorithm, interfaces.HashAlgorithm):
             raise TypeError("Expected instance of interfaces.HashAlgorithm.")
         self.algorithm = algorithm
 
-        if backend is None:
-            from cryptography.hazmat.bindings import _default_backend
-            backend = _default_backend
-
         self._backend = backend
         self._key = key
         if ctx is None:
-            self._ctx = self._backend.hmacs.create_ctx(key, self.algorithm)
+            self._ctx = self._backend.create_hmac_ctx(key, self.algorithm)
         else:
             self._ctx = ctx
 
     def update(self, msg):
+        if self._ctx is None:
+            raise AlreadyFinalized("Context was already finalized")
         if isinstance(msg, six.text_type):
             raise TypeError("Unicode-objects must be encoded before hashing")
-        self._backend.hmacs.update_ctx(self._ctx, msg)
+        self._ctx.update(msg)
 
     def copy(self):
-        return self.__class__(self._key, self.algorithm, backend=self._backend,
-                              ctx=self._backend.hmacs.copy_ctx(self._ctx))
+        if self._ctx is None:
+            raise AlreadyFinalized("Context was already finalized")
+        return HMAC(
+            self._key,
+            self.algorithm,
+            backend=self._backend,
+            ctx=self._ctx.copy()
+        )
 
     def finalize(self):
-        return self._backend.hmacs.finalize_ctx(self._ctx,
-                                                self.algorithm.digest_size)
+        if self._ctx is None:
+            raise AlreadyFinalized("Context was already finalized")
+        digest = self._ctx.finalize()
+        self._ctx = None
+        return digest
diff --git a/cryptography/hazmat/primitives/interfaces.py b/cryptography/hazmat/primitives/interfaces.py
index 67dbe6f..e3f4f58 100644
--- a/cryptography/hazmat/primitives/interfaces.py
+++ b/cryptography/hazmat/primitives/interfaces.py
@@ -18,11 +18,18 @@
 import six
 
 
-def register(iface):
-    def register_decorator(klass):
-        iface.register(klass)
-        return klass
-    return register_decorator
+class CipherAlgorithm(six.with_metaclass(abc.ABCMeta)):
+    @abc.abstractproperty
+    def name(self):
+        """
+        A string naming this mode.  (e.g. AES, Camellia)
+        """
+
+    @abc.abstractproperty
+    def key_size(self):
+        """
+        The size of the key being used as an integer in bits.  (e.g. 128, 256)
+        """
 
 
 class Mode(six.with_metaclass(abc.ABCMeta)):
@@ -49,6 +56,14 @@
         """
 
 
+class ModeWithAuthenticationTag(six.with_metaclass(abc.ABCMeta)):
+    @abc.abstractproperty
+    def tag(self):
+        """
+        The value of the tag supplied to the constructor of this mode.
+        """
+
+
 class CipherContext(six.with_metaclass(abc.ABCMeta)):
     @abc.abstractmethod
     def update(self, data):
@@ -63,6 +78,22 @@
         """
 
 
+class AEADCipherContext(six.with_metaclass(abc.ABCMeta)):
+    @abc.abstractmethod
+    def authenticate_additional_data(self, data):
+        """
+        authenticate_additional_data takes bytes and returns nothing.
+        """
+
+
+class AEADEncryptionContext(six.with_metaclass(abc.ABCMeta)):
+    @abc.abstractproperty
+    def tag(self):
+        """
+        Returns tag bytes after finalizing encryption.
+        """
+
+
 class PaddingContext(six.with_metaclass(abc.ABCMeta)):
     @abc.abstractmethod
     def update(self, data):
diff --git a/cryptography/hazmat/primitives/padding.py b/cryptography/hazmat/primitives/padding.py
index 6849d14..cfa90db 100644
--- a/cryptography/hazmat/primitives/padding.py
+++ b/cryptography/hazmat/primitives/padding.py
@@ -15,6 +15,7 @@
 
 import six
 
+from cryptography import utils
 from cryptography.hazmat.primitives import interfaces
 
 
@@ -64,7 +65,6 @@
 
 class PKCS7(object):
     def __init__(self, block_size):
-        super(PKCS7, self).__init__()
         if not (0 <= block_size < 256):
             raise ValueError("block_size must be in range(0, 256)")
 
@@ -80,10 +80,9 @@
         return _PKCS7UnpaddingContext(self.block_size)
 
 
-@interfaces.register(interfaces.PaddingContext)
+@utils.register_interface(interfaces.PaddingContext)
 class _PKCS7PaddingContext(object):
     def __init__(self, block_size):
-        super(_PKCS7PaddingContext, self).__init__()
         self.block_size = block_size
         # TODO: O(n ** 2) complexity for repeated concatentation, we should use
         # zero-buffer (#193)
@@ -115,10 +114,9 @@
         return result
 
 
-@interfaces.register(interfaces.PaddingContext)
+@utils.register_interface(interfaces.PaddingContext)
 class _PKCS7UnpaddingContext(object):
     def __init__(self, block_size):
-        super(_PKCS7UnpaddingContext, self).__init__()
         self.block_size = block_size
         # TODO: O(n ** 2) complexity for repeated concatentation, we should use
         # zero-buffer (#193)
diff --git a/cryptography/utils.py b/cryptography/utils.py
new file mode 100644
index 0000000..e697d51
--- /dev/null
+++ b/cryptography/utils.py
@@ -0,0 +1,21 @@
+# 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
+
+
+def register_interface(iface):
+    def register_decorator(klass):
+        iface.register(klass)
+        return klass
+    return register_decorator
diff --git a/docs/architecture.rst b/docs/architecture.rst
index 4cf639c..5ca2c25 100644
--- a/docs/architecture.rst
+++ b/docs/architecture.rst
@@ -1,11 +1,6 @@
 Architecture
 ============
 
-.. warning::
-
-    Because ``cryptography`` is so young, much of this document is
-    aspirational, rather than documentation.
-
 ``cryptography`` has three different layers:
 
 * ``cryptography``: This package contains higher level recipes, for example
diff --git a/docs/contributing.rst b/docs/contributing.rst
index 3b30184..4647818 100644
--- a/docs/contributing.rst
+++ b/docs/contributing.rst
@@ -122,6 +122,18 @@
 * No blank line at the end.
 * Use Sphinx parameter/attribute documentation `syntax`_.
 
+Because of the inherent challenges in implementing correct cryptographic
+systems, we want to make our documentation point people in the right directions
+as much as possible. To that end:
+
+* When documenting a generic interface, use a strong algorithm in examples.
+  (e.g. when showing a hashing example, don't use
+  :class:`cryptography.hazmat.primitives.hashes.MD5`)
+* When giving prescriptive advice, always provide references and supporting
+  material.
+* When there is real disagreement between cryptographic experts, represent both
+  sides of the argument and describe the trade-offs clearly.
+
 When documenting a new module in the ``hazmat`` package, its documentation
 should begin with the "Hazardous Materials" warning:
 
@@ -129,6 +141,9 @@
 
     .. hazmat::
 
+When referring to a hypothetical individual (such as "a person receiving an
+encrypted message") use gender neutral pronouns (they/them/their).
+
 Development Environment
 -----------------------
 
diff --git a/docs/cryptography-docs.py b/docs/cryptography-docs.py
index 4ed5526..ea7e8ee 100644
--- a/docs/cryptography-docs.py
+++ b/docs/cryptography-docs.py
@@ -31,10 +31,14 @@
     pass
 
 
-def visit_hazmat_node(self, node):
+def html_visit_hazmat_node(self, node):
     return self.visit_admonition(node, "danger")
 
 
+def latex_visit_hazmat_node(self, node):
+    return self.visit_admonition(node)
+
+
 def depart_hazmat_node(self, node):
     return self.depart_admonition(node)
 
@@ -42,6 +46,7 @@
 def setup(app):
     app.add_node(
         Hazmat,
-        html=(visit_hazmat_node, depart_hazmat_node)
+        html=(html_visit_hazmat_node, depart_hazmat_node),
+        latex=(latex_visit_hazmat_node, depart_hazmat_node),
     )
     app.add_directive("hazmat", HazmatDirective)
diff --git a/docs/exceptions.rst b/docs/exceptions.rst
index 6ac11b3..087066b 100644
--- a/docs/exceptions.rst
+++ b/docs/exceptions.rst
@@ -3,6 +3,23 @@
 
 .. currentmodule:: cryptography.exceptions
 
+.. class:: AlreadyFinalized
+
+    This is raised when a context is used after being finalized.
+
+
+.. class:: NotYetFinalized
+
+    This is raised when the AEAD tag property is accessed on a context
+    before it is finalized.
+
+
+.. class:: AlreadyUpdated
+
+    This is raised when additional data is added to a context after update
+    has already been called.
+
+
 .. class:: UnsupportedAlgorithm
 
     This is raised when a backend doesn't support the requested algorithm (or
diff --git a/docs/glossary.rst b/docs/glossary.rst
index e4fc828..63e0a6c 100644
--- a/docs/glossary.rst
+++ b/docs/glossary.rst
@@ -27,4 +27,15 @@
 
     asymmetric cryptography
         Cryptographic operations where encryption and decryption use different
-        keys. There are seperate encryption and decryption keys.
+        keys. There are separate encryption and decryption keys.
+
+    authentication
+        The process of verifying that a message was created by a specific
+        individual (or program). Like encryption, authentication can be either
+        symmetric or asymmetric. Authentication is necessary for effective
+        encryption.
+
+    Ciphertext indistinguishability
+        This is a property of encryption systems whereby two encrypted messages
+        aren't distinguishable without knowing the encryption key. This is
+        considered a basic, necessary property for a working encryption system.
diff --git a/docs/hazmat/bindings/index.rst b/docs/hazmat/bindings/index.rst
index 19e0399..746f459 100644
--- a/docs/hazmat/bindings/index.rst
+++ b/docs/hazmat/bindings/index.rst
@@ -7,3 +7,28 @@
     :maxdepth: 1
 
     openssl
+    interfaces
+
+
+Getting a Backend Provider
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. currentmodule:: cryptography.hazmat.bindings
+
+``cryptography`` aims to support multiple backends to ensure it can provide
+the widest number of supported cryptographic algorithms as well as supporting
+platform specific implementations.
+
+You can get the default backend by calling
+:func:`~default_backend`.
+
+The default backend will change over time as we implement new backends and
+the libraries we use in those backends changes.
+
+
+.. function:: default_backend()
+
+    :returns: An object that provides at least
+        :class:`~interfaces.CipherBackend`, :class:`~interfaces.HashBackend`, and
+        :class:`~interfaces.HMACBackend`.
+
diff --git a/docs/hazmat/bindings/interfaces.rst b/docs/hazmat/bindings/interfaces.rst
new file mode 100644
index 0000000..711c82c
--- /dev/null
+++ b/docs/hazmat/bindings/interfaces.rst
@@ -0,0 +1,141 @@
+.. hazmat::
+
+Backend Interfaces
+==================
+
+.. currentmodule:: cryptography.hazmat.bindings.interfaces
+
+
+Backend implementations may provide a number of interfaces to support operations
+such as :doc:`/hazmat/primitives/symmetric-encryption`,
+:doc:`/hazmat/primitives/cryptographic-hashes`, and
+:doc:`/hazmat/primitives/hmac`.
+
+A specific ``backend`` may provide one or more of these interfaces.
+
+
+.. class:: CipherBackend
+
+    A backend which provides methods for using ciphers for encryption
+    and decryption.
+
+    .. method:: cipher_supported(cipher, mode)
+
+        Check if a ``cipher`` and ``mode`` combination is supported by
+        this backend.
+
+        :param cipher: An instance of a
+            :class:`~cryptography.hazmat.primitives.interfaces.CipherAlgorithm`
+            provider.
+        :param mode: An instance of a
+            :class:`~cryptography.hazmat.primitives.interfaces.Mode` provider.
+
+        :returns: ``True`` if the specified ``cipher`` and ``mode`` combination
+            is supported by this backend, otherwise ``False``
+
+    .. method:: register_cipher_adapter(cipher_cls, mode_cls, adapter)
+
+        Register an adapter which can be used to create a backend specific
+        object from instances of the
+        :class:`~cryptography.hazmat.primitives.interfaces.CipherAlgorithm` and
+        the :class:`~cryptography.hazmat.primitives.interfaces.Mode` primitives.
+
+        :param cipher_cls: A class whose instances provide
+            :class:`~cryptography.hazmat.primitives.interfaces.CipherAlgorithm`
+        :param mode_cls: A class whose instances provide:
+            :class:`~cryptography.hazmat.primitives.interfaces.Mode`
+        :param adapter: A ``function`` that takes 3 arguments, ``backend`` (a
+            :class:`CipherBackend` provider), ``cipher`` (a
+            :class:`~cryptography.hazmat.primitives.interfaces.CipherAlgorithm`
+            provider ), and ``mode`` (a
+            :class:`~cryptography.hazmat.primitives.interfaces.Mode` provider).
+            It returns a backend specific object which may be used to construct
+            a :class:`~cryptogrpahy.hazmat.primitives.interfaces.CipherContext`.
+
+
+    .. method:: create_symmetric_encryption_ctx(cipher, mode)
+
+        Create a
+        :class:`~cryptogrpahy.hazmat.primitives.interfaces.CipherContext` that
+        can be used for encrypting data with the symmetric ``cipher`` using
+        the given ``mode``.
+
+        :param cipher: An instance of a
+            :class:`~cryptography.hazmat.primitives.interfaces.CipherAlgorithm`
+            provider.
+        :param mode: An instance of a
+            :class:`~cryptography.hazmat.primitives.interfaces.Mode` provider.
+
+        :returns:
+            :class:`~cryptography.hazmat.primitives.interfaces.CipherContext`
+
+        :raises ValueError: When tag is not None in an AEAD mode
+
+
+    .. method:: create_symmetric_decryption_ctx(cipher, mode)
+
+        Create a
+        :class:`~cryptogrpahy.hazmat.primitives.interfaces.CipherContext` that
+        can be used for decrypting data with the symmetric ``cipher`` using
+        the given ``mode``.
+
+        :param cipher: An instance of a
+            :class:`~cryptography.hazmat.primitives.interfaces.CipherAlgorithm`
+            provider.
+        :param mode: An instance of a
+            :class:`~cryptography.hazmat.primitives.interfaces.Mode` provider.
+
+        :returns:
+            :class:`~cryptography.hazmat.primitives.interfaces.CipherContext`
+
+        :raises ValueError: When tag is None in an AEAD mode
+
+
+.. class:: HashBackend
+
+    A backend with methods for using cryptographic hash functions.
+
+    .. method:: hash_supported(algorithm)
+
+        Check if the specified ``algorithm`` is supported by this backend.
+
+        :param algorithm: An instance of a
+            :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm`
+            provider.
+
+        :returns: ``True`` if the specified ``algorithm`` is supported by this
+            backend, otherwise ``False``.
+
+
+    .. method:: create_hash_ctx(algorithm)
+
+        Create a
+        :class:`~cryptogrpahy.hazmat.primitives.interfaces.HashContext` that
+        uses the specified ``algorithm`` to calculate a message digest.
+
+        :param algorithm: An instance of a
+            :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm`
+            provider.
+
+        :returns:
+            :class:`~cryptography.hazmat.primitives.interfaces.HashContext`
+
+
+.. class:: HMACBackend
+
+    A backend with methods for using cryptographic hash functions as message
+    authentication codes.
+
+    .. method:: create_hmac_ctx(algorithm)
+
+        Create a
+        :class:`~cryptogrpahy.hazmat.primitives.interfaces.HashContext` that
+        uses the specified ``algorithm`` to calculate a hash-based message
+        authentication code.
+
+        :param algorithm: An instance of a
+            :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm`
+            provider.
+
+        :returns:
+            :class:`~cryptography.hazmat.primitives.interfaces.HashContext`
diff --git a/docs/hazmat/primitives/cryptographic-hashes.rst b/docs/hazmat/primitives/cryptographic-hashes.rst
index 20fa23c..312d7e6 100644
--- a/docs/hazmat/primitives/cryptographic-hashes.rst
+++ b/docs/hazmat/primitives/cryptographic-hashes.rst
@@ -5,7 +5,7 @@
 
 .. currentmodule:: cryptography.hazmat.primitives.hashes
 
-.. class:: Hash(algorithm)
+.. class:: Hash(algorithm, backend)
 
     A cryptographic hash function takes an arbitrary block of data and
     calculates a fixed-size bit string (a digest), such that different data
@@ -20,30 +20,56 @@
 
     .. doctest::
 
+        >>> from cryptography.hazmat.bindings import default_backend
         >>> from cryptography.hazmat.primitives import hashes
-        >>> digest = hashes.Hash(hashes.SHA256())
+        >>> digest = hashes.Hash(hashes.SHA256(), backend=default_backend())
         >>> digest.update(b"abc")
         >>> digest.update(b"123")
         >>> digest.finalize()
         'l\xa1=R\xcap\xc8\x83\xe0\xf0\xbb\x10\x1eBZ\x89\xe8bM\xe5\x1d\xb2\xd29%\x93\xafj\x84\x11\x80\x90'
 
+    Keep in mind that attacks against cryptographic hashes only get stronger
+    with time, and that often algorithms that were once thought to be strong,
+    become broken. Because of this it's important to include a plan for
+    upgrading the hash algorithm you use over time. For more information, see
+    `Lifetimes of cryptographic hash functions`_.
+
+    :param algorithm: A
+        :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm`
+        provider such as those described in
+        :ref:`below <cryptographic-hash-algorithms>`.
+    :param backend: A
+        :class:`~cryptography.hazmat.bindings.interfaces.HashBackend`
+        provider.
+
     .. method:: update(data)
 
         :param bytes data: The bytes you wish to hash.
+        :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize`
 
     .. method:: copy()
 
-        :return: a new instance of this object with a copied internal state.
+        Copy this :class:`Hash` instance, usually so that we may call
+        :meth:`finalize` and get an intermediate digest value while we continue
+        to call :meth:`update` on the original.
+
+        :return: A new instance of :class:`Hash` which can be updated
+            and finalized independently of the original instance.
+        :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize`
 
     .. method:: finalize()
 
         Finalize the current context and return the message digest as bytes.
 
-        Once ``finalize`` is called this object can no longer be used.
+        Once ``finalize`` is called this object can no longer be used and
+        :meth:`update`, :meth:`copy`, and :meth:`finalize` will raise
+        :class:`~cryptography.exceptions.AlreadyFinalized`.
 
         :return bytes: The message digest as bytes.
 
 
+.. _cryptographic-hash-algorithms:
+
 SHA-1
 ~~~~~
 
@@ -109,3 +135,6 @@
 
     MD5 is a deprecated cryptographic hash function. It has a 128-bit message
     digest and has practical known collision attacks.
+
+
+.. _`Lifetimes of cryptographic hash functions`: http://valerieaurora.org/hash.html
diff --git a/docs/hazmat/primitives/hmac.rst b/docs/hazmat/primitives/hmac.rst
index bd1a493..db5e98d 100644
--- a/docs/hazmat/primitives/hmac.rst
+++ b/docs/hazmat/primitives/hmac.rst
@@ -15,7 +15,7 @@
 secret key. You can use an HMAC to verify integrity as well as authenticate a
 message.
 
-.. class:: HMAC(key, algorithm)
+.. class:: HMAC(key, algorithm, backend)
 
     HMAC objects take a ``key`` and a provider of
     :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm`.
@@ -27,24 +27,45 @@
 
     .. doctest::
 
+        >>> from cryptography.hazmat.bindings import default_backend
         >>> from cryptography.hazmat.primitives import hashes, hmac
-        >>> h = hmac.HMAC(key, hashes.SHA256())
+        >>> h = hmac.HMAC(key, hashes.SHA256(), backend=default_backend())
         >>> h.update(b"message to hash")
         >>> h.finalize()
         '#F\xdaI\x8b"e\xc4\xf1\xbb\x9a\x8fc\xff\xf5\xdex.\xbc\xcd/+\x8a\x86\x1d\x84\'\xc3\xa6\x1d\xd8J'
 
+
+    :param key: Secret key as ``bytes``.
+    :param algorithm: A
+        :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm`
+        provider such as those described in
+        :ref:`Cryptographic Hashes <cryptographic-hash-algorithms>`.
+    :param backend: A
+        :class:`~cryptography.hazmat.bindings.interfaces.HMACBackend`
+        provider.
+
     .. method:: update(msg)
 
         :param bytes msg: The bytes to hash and authenticate.
+        :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize`
 
     .. method:: copy()
 
-        :return: a new instance of this object with a copied internal state.
+        Copy this :class:`HMAC` instance, usually so that we may call
+        :meth:`finalize` and get an intermediate digest value while we continue
+        to call :meth:`update` on the original.
+
+        :return: A new instance of :class:`HMAC` which can be updated
+            and finalized independently of the original instance.
+        :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize`
 
     .. method:: finalize()
 
         Finalize the current context and return the message digest as bytes.
 
-        Once ``finalize`` is called this object can no longer be used.
+        Once ``finalize`` is called this object can no longer be used and
+        :meth:`update`, :meth:`copy`, and :meth:`finalize` will raise
+        :class:`~cryptography.exceptions.AlreadyFinalized`.
 
         :return bytes: The message digest as bytes.
+        :raises cryptography.exceptions.AlreadyFinalized:
diff --git a/docs/hazmat/primitives/interfaces.rst b/docs/hazmat/primitives/interfaces.rst
index 7068316..11cff51 100644
--- a/docs/hazmat/primitives/interfaces.rst
+++ b/docs/hazmat/primitives/interfaces.rst
@@ -12,11 +12,33 @@
 .. _`Abstract Base Classes`: http://docs.python.org/3.2/library/abc.html
 
 
-Cipher Modes
-~~~~~~~~~~~~
+Symmetric Ciphers
+~~~~~~~~~~~~~~~~~
 
 .. currentmodule:: cryptography.hazmat.primitives.interfaces
 
+
+.. class:: CipherAlgorithm
+
+    A named symmetric encryption algorithm.
+
+    .. attribute:: name
+
+        :type: str
+
+        The standard name for the mode, for example, "AES", "Camellia", or
+        "Blowfish".
+
+    .. attribute:: key_size
+
+        :type: int
+
+        The number of bits in the key being used.
+
+
+Cipher Modes
+------------
+
 Interfaces used by the symmetric cipher modes described in
 :ref:`Symmetric Encryption Modes <symmetric-encryption-modes>`.
 
diff --git a/docs/hazmat/primitives/padding.rst b/docs/hazmat/primitives/padding.rst
index aebb4d4..4d79ac8 100644
--- a/docs/hazmat/primitives/padding.rst
+++ b/docs/hazmat/primitives/padding.rst
@@ -25,8 +25,14 @@
         >>> padder = padding.PKCS7(128).padder()
         >>> padder.update(b"1111111111")
         ''
-        >>> padder.finalize()
+        >>> padded_data = padder.finalize()
+        >>> padded_data
         '1111111111\x06\x06\x06\x06\x06\x06'
+        >>> unpadder = padding.PKCS7(128).unpadder()
+        >>> unpadder.update(padded_data)
+        ''
+        >>> unpadder.finalize()
+        '1111111111'
 
     :param block_size: The size of the block in bits that the data is being
                        padded to.
diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst
index 5542e83..2f39017 100644
--- a/docs/hazmat/primitives/symmetric-encryption.rst
+++ b/docs/hazmat/primitives/symmetric-encryption.rst
@@ -12,17 +12,20 @@
     key = binascii.unhexlify(b"0" * 32)
     iv = binascii.unhexlify(b"0" * 32)
 
+    from cryptography.hazmat.bindings import default_backend
+    backend = default_backend()
+
 
 Symmetric encryption is a way to encrypt (hide the plaintext value) material
-where the encrypter and decrypter both use the same key. Note that symmetric
+where the sender and receiver both use the same key. Note that symmetric
 encryption is **not** sufficient for most applications, because it only
 provides secrecy (an attacker can't see the message) but not authenticity (an
 attacker can create bogus messages and force the application to decrypt them).
-For this reason it is *strongly* reccomended to combine encryption with a
+For this reason it is *strongly* recommended to combine encryption with a
 message authentication code, such as :doc:`HMAC </hazmat/primitives/hmac>`, in
 an "encrypt-then-MAC" formulation as `described by Colin Percival`_.
 
-.. class:: Cipher(algorithm, mode)
+.. class:: Cipher(algorithm, mode, backend)
 
     Cipher objects combine an algorithm (such as
     :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES`) with a
@@ -34,15 +37,23 @@
     .. doctest::
 
         >>> from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
-        >>> cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
+        >>> cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=backend)
         >>> encryptor = cipher.encryptor()
         >>> ct = encryptor.update(b"a secret message") + encryptor.finalize()
         >>> decryptor = cipher.decryptor()
         >>> decryptor.update(ct) + decryptor.finalize()
         'a secret message'
 
-    :param algorithms: One of the algorithms described below.
-    :param mode: One of the modes described below.
+    :param algorithms: A
+        :class:`~cryptography.hazmat.primitives.interfaces.CipherAlgorithm`
+        provider such as those described
+        :ref:`below <symmetric-encryption-algorithms>`.
+    :param mode: A :class:`~cryptography.hazmat.primitives.interfaces.Mode`
+        provider such as those described
+        :ref:`below <symmetric-encryption-modes>`.
+    :param backend: A
+        :class:`~cryptography.hazmat.bindings.interfaces.CipherBackend`
+        provider.
 
     .. method:: encryptor()
 
@@ -75,10 +86,20 @@
     everything into the context. Once that is done call ``finalize()`` to
     finish the operation and obtain the remainder of the data.
 
+    Block ciphers require that plaintext or ciphertext always be a multiple of
+    their block size, because of that **padding** is often required to make a
+    message the correct size. ``CipherContext`` will not automatically apply
+    any padding; you'll need to add your own. For block ciphers the recommended
+    padding is :class:`cryptography.hazmat.primitives.padding.PKCS7`. If you
+    are using a stream cipher mode (such as
+    :class:`cryptography.hazmat.primitives.modes.CTR`) you don't have to worry
+    about this.
+
     .. method:: update(data)
 
         :param bytes data: The data you wish to pass into the context.
         :return bytes: Returns the data that was encrypted or decrypted.
+        :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize`
 
         When the ``Cipher`` was constructed in a mode that turns it into a
         stream cipher (e.g.
@@ -89,6 +110,45 @@
     .. method:: finalize()
 
         :return bytes: Returns the remainder of the data.
+        :raises ValueError: This is raised when the data provided isn't
+                            correctly padded to be a multiple of the
+                            algorithm's block size.
+
+        Once ``finalize`` is called this object can no longer be used and
+        :meth:`update` and :meth:`finalize` will raise
+        :class:`~cryptography.exceptions.AlreadyFinalized`.
+
+.. class:: AEADCipherContext
+
+    When calling ``encryptor()`` or ``decryptor()`` on a ``Cipher`` object
+    with an AEAD mode you will receive a return object conforming to the
+    ``AEADCipherContext`` interface (in addition to the ``CipherContext``
+    interface). If it is an encryption context it will additionally be an
+    ``AEADEncryptionContext`` interface. ``AEADCipherContext`` contains an
+    additional method ``authenticate_additional_data`` for adding additional
+    authenticated but unencrypted data. You should call this before calls to
+    ``update``. When you are done call ``finalize()`` to finish the operation.
+
+    .. method:: authenticate_additional_data(data)
+
+        :param bytes data: The data you wish to authenticate but not encrypt.
+        :raises: :class:`~cryptography.exceptions.AlreadyFinalized`
+
+.. class:: AEADEncryptionContext
+
+    When creating an encryption context using ``encryptor()`` on a ``Cipher``
+    object with an AEAD mode you will receive a return object conforming to the
+    ``AEADEncryptionContext`` interface (as well as ``AEADCipherContext``).
+    This interface provides one additional attribute ``tag``. ``tag`` can only
+    be obtained after ``finalize()``.
+
+    .. attribute:: tag
+
+        :return bytes: Returns the tag value as bytes.
+        :raises: :class:`~cryptography.exceptions.NotYetFinalized` if called
+                 before the context is finalized.
+
+.. _symmetric-encryption-algorithms:
 
 Algorithms
 ~~~~~~~~~~
@@ -116,10 +176,10 @@
 
 .. class:: TripleDES(key)
 
-    Triple DES (Data Encryption Standard), sometimes refered to as 3DES, is a
-    block cipher standardized by NIST. Triple DES has known cryptoanalytic
+    Triple DES (Data Encryption Standard), sometimes referred to as 3DES, is a
+    block cipher standardized by NIST. Triple DES has known crypto-analytic
     flaws, however none of them currently enable a practical attack.
-    Nonetheless, Triples DES is not reccomended for new applications because it
+    Nonetheless, Triples DES is not recommended for new applications because it
     is incredibly slow; old applications should consider moving away from it.
 
     :param bytes key: The secret key, either ``64``, ``128``, or ``192`` bits
@@ -157,6 +217,27 @@
     :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.
+
+    .. doctest::
+
+        >>> from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
+        >>> algorithm = algorithms.ARC4(key)
+        >>> cipher = Cipher(algorithm, mode=None, backend=backend)
+        >>> encryptor = cipher.encryptor()
+        >>> ct = encryptor.update(b"a secret message")
+        >>> decryptor = cipher.decryptor()
+        >>> decryptor.update(ct)
+        'a secret message'
+
 
 .. _symmetric-encryption-modes:
 
@@ -244,6 +325,48 @@
                                         reuse an ``initialization_vector`` with
                                         a given ``key``.
 
+.. class:: GCM(initialization_vector, tag=None)
+
+    .. danger::
+
+        When using this mode you MUST not use the decrypted data until
+        :meth:`~cryptography.hazmat.primitives.interfaces.CipherContext.finalize`
+        has been called. GCM provides NO guarantees of ciphertext integrity
+        until decryption is complete.
+
+    GCM (Galois Counter Mode) is a mode of operation for block ciphers. An
+    AEAD (authenticated encryption with additional data) mode is a type of
+    block cipher mode that encrypts the message as well as authenticating it
+    (and optionally additional data that is not encrypted) simultaneously.
+    Additional means of verifying integrity (like
+    :doc:`HMAC </hazmat/primitives/hmac>`) are not necessary.
+
+    :param bytes initialization_vector: Must be random bytes. They do not need
+                                        to be kept secret (they can be included
+                                        in a transmitted message). NIST
+                                        `recommends 96-bit IV length`_ for
+                                        performance critical situations, but it
+                                        can be up to 2\ :sup:`64` - 1 bits.
+                                        Do not reuse an ``initialization_vector``
+                                        with a given ``key``.
+
+    :param bytes tag: The tag bytes to verify during decryption. When encrypting
+                      this must be None.
+
+    .. doctest::
+
+        >>> from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
+        >>> cipher = Cipher(algorithms.AES(key), modes.GCM(iv), backend)
+        >>> encryptor = cipher.encryptor()
+        >>> encryptor.authenticate_additional_data(b"authenticated but not encrypted payload")
+        >>> ct = encryptor.update(b"a secret message") + encryptor.finalize()
+        >>> tag = encryptor.tag
+        >>> cipher = Cipher(algorithms.AES(key), modes.GCM(iv, tag), backend)
+        >>> decryptor = cipher.decryptor()
+        >>> decryptor.authenticate_additional_data(b"authenticated but not encrypted payload")
+        >>> decryptor.update(ct) + decryptor.finalize()
+        'a secret message'
+
 
 Insecure Modes
 --------------
@@ -263,3 +386,4 @@
 
 
 .. _`described by Colin Percival`: http://www.daemonology.net/blog/2009-06-11-cryptographic-right-answers.html
+.. _`recommends 96-bit IV length`: http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-spec.pdf
diff --git a/docs/security.rst b/docs/security.rst
index 36c8e0f..8895970 100644
--- a/docs/security.rst
+++ b/docs/security.rst
@@ -5,7 +5,7 @@
 identified a security issue in it, please report it to
 ``alex.gaynor@gmail.com``. Message may be encrypted with PGP using key
 fingerprint ``E27D 4AA0 1651 72CB C5D2  AF2B 125F 5C67 DFE9 4084`` (this public
-key is available from most commonly-used keyservers).
+key is available from most commonly-used key servers).
 
 Once you’ve submitted an issue via email, you should receive an acknowledgment
 within 48 hours, and depending on the action to be taken, you may receive
diff --git a/tests/hazmat/bindings/test_openssl.py b/tests/hazmat/bindings/test_openssl.py
index f1493e8..1eb6f20 100644
--- a/tests/hazmat/bindings/test_openssl.py
+++ b/tests/hazmat/bindings/test_openssl.py
@@ -13,18 +13,22 @@
 
 import pytest
 
+from cryptography import utils
 from cryptography.exceptions import UnsupportedAlgorithm
+from cryptography.hazmat.bindings import default_backend
 from cryptography.hazmat.bindings.openssl.backend import backend, Backend
+from cryptography.hazmat.primitives import interfaces
 from cryptography.hazmat.primitives.ciphers import Cipher
 from cryptography.hazmat.primitives.ciphers.algorithms import AES
 from cryptography.hazmat.primitives.ciphers.modes import CBC
 
 
-class FakeMode(object):
+class DummyMode(object):
     pass
 
 
-class FakeCipher(object):
+@utils.register_interface(interfaces.CipherAlgorithm)
+class DummyCipher(object):
     pass
 
 
@@ -32,6 +36,9 @@
     def test_backend_exists(self):
         assert backend
 
+    def test_is_default(self):
+        assert backend == default_backend()
+
     def test_openssl_version_text(self):
         """
         This test checks the value of OPENSSL_VERSION_TEXT.
@@ -44,11 +51,11 @@
         assert backend.openssl_version_text().startswith("OpenSSL")
 
     def test_supports_cipher(self):
-        assert backend.ciphers.supported(None, None) is False
+        assert backend.cipher_supported(None, None) is False
 
     def test_register_duplicate_cipher_adapter(self):
         with pytest.raises(ValueError):
-            backend.ciphers.register_cipher_adapter(AES, CBC, None)
+            backend.register_cipher_adapter(AES, CBC, None)
 
     def test_instances_share_ffi(self):
         b = Backend()
@@ -57,13 +64,34 @@
 
     def test_nonexistent_cipher(self):
         b = Backend()
-        b.ciphers.register_cipher_adapter(
-            FakeCipher,
-            FakeMode,
+        b.register_cipher_adapter(
+            DummyCipher,
+            DummyMode,
             lambda backend, cipher, mode: backend.ffi.NULL
         )
         cipher = Cipher(
-            FakeCipher(), FakeMode(), backend=b,
+            DummyCipher(), DummyMode(), backend=b,
         )
         with pytest.raises(UnsupportedAlgorithm):
             cipher.encryptor()
+
+    def test_handle_unknown_error(self):
+        with pytest.raises(SystemError):
+            backend._handle_error_code(0, 0, 0)
+
+        with pytest.raises(SystemError):
+            backend._handle_error_code(backend.lib.ERR_LIB_EVP, 0, 0)
+
+        with pytest.raises(SystemError):
+            backend._handle_error_code(
+                backend.lib.ERR_LIB_EVP,
+                backend.lib.EVP_F_EVP_ENCRYPTFINAL_EX,
+                0
+            )
+
+        with pytest.raises(SystemError):
+            backend._handle_error_code(
+                backend.lib.ERR_LIB_EVP,
+                backend.lib.EVP_F_EVP_DECRYPTFINAL_EX,
+                0
+            )
diff --git a/tests/hazmat/primitives/test_3des.py b/tests/hazmat/primitives/test_3des.py
index af6bdc0..69ec9c9 100644
--- a/tests/hazmat/primitives/test_3des.py
+++ b/tests/hazmat/primitives/test_3des.py
@@ -23,12 +23,12 @@
 from cryptography.hazmat.primitives.ciphers import algorithms, modes
 
 from .utils import generate_encrypt_test
-from ...utils import load_nist_vectors_from_file
+from ...utils import load_nist_vectors
 
 
 class TestTripleDES_CBC(object):
     test_KAT = generate_encrypt_test(
-        lambda path: load_nist_vectors_from_file(path, "ENCRYPT"),
+        load_nist_vectors,
         os.path.join("ciphers", "3DES", "CBC"),
         [
             "TCBCinvperm.rsp",
@@ -42,7 +42,7 @@
     )
 
     test_MMT = generate_encrypt_test(
-        lambda path: load_nist_vectors_from_file(path, "ENCRYPT"),
+        load_nist_vectors,
         os.path.join("ciphers", "3DES", "CBC"),
         [
             "TCBCMMT1.rsp",
@@ -58,7 +58,7 @@
 
 class TestTripleDES_OFB(object):
     test_KAT = generate_encrypt_test(
-        lambda path: load_nist_vectors_from_file(path, "ENCRYPT"),
+        load_nist_vectors,
         os.path.join("ciphers", "3DES", "OFB"),
         [
             "TOFBpermop.rsp",
@@ -72,7 +72,7 @@
     )
 
     test_MMT = generate_encrypt_test(
-        lambda path: load_nist_vectors_from_file(path, "ENCRYPT"),
+        load_nist_vectors,
         os.path.join("ciphers", "3DES", "OFB"),
         [
             "TOFBMMT1.rsp",
@@ -88,7 +88,7 @@
 
 class TestTripleDES_CFB(object):
     test_KAT = generate_encrypt_test(
-        lambda path: load_nist_vectors_from_file(path, "ENCRYPT"),
+        load_nist_vectors,
         os.path.join("ciphers", "3DES", "CFB"),
         [
             "TCFB64invperm.rsp",
@@ -102,7 +102,7 @@
     )
 
     test_MMT = generate_encrypt_test(
-        lambda path: load_nist_vectors_from_file(path, "ENCRYPT"),
+        load_nist_vectors,
         os.path.join("ciphers", "3DES", "CFB"),
         [
             "TCFB64MMT1.rsp",
diff --git a/tests/hazmat/primitives/test_aes.py b/tests/hazmat/primitives/test_aes.py
index 66471fa..f7b0b9a 100644
--- a/tests/hazmat/primitives/test_aes.py
+++ b/tests/hazmat/primitives/test_aes.py
@@ -18,15 +18,15 @@
 
 from cryptography.hazmat.primitives.ciphers import algorithms, modes
 
-from .utils import generate_encrypt_test
+from .utils import generate_encrypt_test, generate_aead_test
 from ...utils import (
-    load_nist_vectors_from_file, load_openssl_vectors_from_file
+    load_nist_vectors, load_openssl_vectors,
 )
 
 
 class TestAES(object):
     test_CBC = generate_encrypt_test(
-        lambda path: load_nist_vectors_from_file(path, "ENCRYPT"),
+        load_nist_vectors,
         os.path.join("ciphers", "AES", "CBC"),
         [
             "CBCGFSbox128.rsp",
@@ -50,7 +50,7 @@
     )
 
     test_ECB = generate_encrypt_test(
-        lambda path: load_nist_vectors_from_file(path, "ENCRYPT"),
+        load_nist_vectors,
         os.path.join("ciphers", "AES", "ECB"),
         [
             "ECBGFSbox128.rsp",
@@ -74,7 +74,7 @@
     )
 
     test_OFB = generate_encrypt_test(
-        lambda path: load_nist_vectors_from_file(path, "ENCRYPT"),
+        load_nist_vectors,
         os.path.join("ciphers", "AES", "OFB"),
         [
             "OFBGFSbox128.rsp",
@@ -98,7 +98,7 @@
     )
 
     test_CFB = generate_encrypt_test(
-        lambda path: load_nist_vectors_from_file(path, "ENCRYPT"),
+        load_nist_vectors,
         os.path.join("ciphers", "AES", "CFB"),
         [
             "CFB128GFSbox128.rsp",
@@ -122,13 +122,32 @@
     )
 
     test_CTR = generate_encrypt_test(
-        load_openssl_vectors_from_file,
+        load_openssl_vectors,
         os.path.join("ciphers", "AES", "CTR"),
         ["aes-128-ctr.txt", "aes-192-ctr.txt", "aes-256-ctr.txt"],
         lambda key, iv: algorithms.AES(binascii.unhexlify(key)),
         lambda key, iv: modes.CTR(binascii.unhexlify(iv)),
-        only_if=lambda backend: backend.ciphers.supported(
+        only_if=lambda backend: backend.cipher_supported(
             algorithms.AES("\x00" * 16), modes.CTR("\x00" * 16)
         ),
         skip_message="Does not support AES CTR",
     )
+
+    test_GCM = generate_aead_test(
+        load_nist_vectors,
+        os.path.join("ciphers", "AES", "GCM"),
+        [
+            "gcmDecrypt128.rsp",
+            "gcmDecrypt192.rsp",
+            "gcmDecrypt256.rsp",
+            "gcmEncryptExtIV128.rsp",
+            "gcmEncryptExtIV192.rsp",
+            "gcmEncryptExtIV256.rsp",
+        ],
+        lambda key: algorithms.AES(key),
+        lambda iv, tag: modes.GCM(iv, tag),
+        only_if=lambda backend: backend.cipher_supported(
+            algorithms.AES("\x00" * 16), modes.GCM("\x00" * 12)
+        ),
+        skip_message="Does not support AES GCM",
+    )
diff --git a/tests/hazmat/primitives/test_arc4.py b/tests/hazmat/primitives/test_arc4.py
new file mode 100644
index 0000000..d233bec
--- /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
+
+
+class TestARC4(object):
+    test_rfc = generate_stream_encryption_test(
+        load_nist_vectors,
+        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.cipher_supported(
+            algorithms.ARC4("\x00" * 16), None
+        ),
+        skip_message="Does not support ARC4",
+    )
diff --git a/tests/hazmat/primitives/test_block.py b/tests/hazmat/primitives/test_block.py
index 28f3447..02de386 100644
--- a/tests/hazmat/primitives/test_block.py
+++ b/tests/hazmat/primitives/test_block.py
@@ -17,34 +17,47 @@
 
 import pytest
 
-from cryptography.exceptions import UnsupportedAlgorithm
+from cryptography import utils
+from cryptography.exceptions import (
+    UnsupportedAlgorithm, AlreadyFinalized,
+)
 from cryptography.hazmat.primitives import interfaces
 from cryptography.hazmat.primitives.ciphers import (
     Cipher, algorithms, modes
 )
 
+from .utils import (
+    generate_aead_exception_test, generate_aead_tag_exception_test
+)
+
+
+@utils.register_interface(interfaces.CipherAlgorithm)
+class DummyCipher(object):
+    pass
+
 
 class TestCipher(object):
-    def test_instantiate_without_backend(self):
-        Cipher(
-            algorithms.AES(binascii.unhexlify(b"0" * 32)),
-            modes.CBC(binascii.unhexlify(b"0" * 32))
-        )
-
-    def test_creates_encryptor(self):
+    def test_creates_encryptor(self, backend):
         cipher = Cipher(
             algorithms.AES(binascii.unhexlify(b"0" * 32)),
-            modes.CBC(binascii.unhexlify(b"0" * 32))
+            modes.CBC(binascii.unhexlify(b"0" * 32)),
+            backend
         )
         assert isinstance(cipher.encryptor(), interfaces.CipherContext)
 
-    def test_creates_decryptor(self):
+    def test_creates_decryptor(self, backend):
         cipher = Cipher(
             algorithms.AES(binascii.unhexlify(b"0" * 32)),
-            modes.CBC(binascii.unhexlify(b"0" * 32))
+            modes.CBC(binascii.unhexlify(b"0" * 32)),
+            backend
         )
         assert isinstance(cipher.decryptor(), interfaces.CipherContext)
 
+    def test_instantiate_with_non_algorithm(self, backend):
+        algorithm = object()
+        with pytest.raises(TypeError):
+            Cipher(algorithm, mode=None, backend=backend)
+
 
 class TestCipherContext(object):
     def test_use_after_finalize(self, backend):
@@ -56,16 +69,16 @@
         encryptor = cipher.encryptor()
         encryptor.update(b"a" * 16)
         encryptor.finalize()
-        with pytest.raises(ValueError):
+        with pytest.raises(AlreadyFinalized):
             encryptor.update(b"b" * 16)
-        with pytest.raises(ValueError):
+        with pytest.raises(AlreadyFinalized):
             encryptor.finalize()
         decryptor = cipher.decryptor()
         decryptor.update(b"a" * 16)
         decryptor.finalize()
-        with pytest.raises(ValueError):
+        with pytest.raises(AlreadyFinalized):
             decryptor.update(b"b" * 16)
-        with pytest.raises(ValueError):
+        with pytest.raises(AlreadyFinalized):
             decryptor.finalize()
 
     def test_unaligned_block_encryption(self, backend):
@@ -90,10 +103,45 @@
 
     def test_nonexistent_cipher(self, backend):
         cipher = Cipher(
-            object(), object(), backend
+            DummyCipher(), object(), backend
         )
         with pytest.raises(UnsupportedAlgorithm):
             cipher.encryptor()
 
         with pytest.raises(UnsupportedAlgorithm):
             cipher.decryptor()
+
+    def test_incorrectly_padded(self, backend):
+        cipher = Cipher(
+            algorithms.AES(b"\x00" * 16),
+            modes.CBC(b"\x00" * 16),
+            backend
+        )
+        encryptor = cipher.encryptor()
+        encryptor.update(b"1")
+        with pytest.raises(ValueError):
+            encryptor.finalize()
+
+        decryptor = cipher.decryptor()
+        decryptor.update(b"1")
+        with pytest.raises(ValueError):
+            decryptor.finalize()
+
+
+class TestAEADCipherContext(object):
+    test_aead_exceptions = generate_aead_exception_test(
+        algorithms.AES,
+        modes.GCM,
+        only_if=lambda backend: backend.cipher_supported(
+            algorithms.AES("\x00" * 16), modes.GCM("\x00" * 12)
+        ),
+        skip_message="Does not support AES GCM",
+    )
+    test_aead_tag_exceptions = generate_aead_tag_exception_test(
+        algorithms.AES,
+        modes.GCM,
+        only_if=lambda backend: backend.cipher_supported(
+            algorithms.AES("\x00" * 16), modes.GCM("\x00" * 12)
+        ),
+        skip_message="Does not support AES GCM",
+    )
diff --git a/tests/hazmat/primitives/test_blowfish.py b/tests/hazmat/primitives/test_blowfish.py
index a7f1382..d5fbed6 100644
--- a/tests/hazmat/primitives/test_blowfish.py
+++ b/tests/hazmat/primitives/test_blowfish.py
@@ -19,53 +19,53 @@
 from cryptography.hazmat.primitives.ciphers import algorithms, modes
 
 from .utils import generate_encrypt_test
-from ...utils import load_nist_vectors_from_file
+from ...utils import load_nist_vectors
 
 
 class TestBlowfish(object):
     test_ECB = generate_encrypt_test(
-        lambda path: load_nist_vectors_from_file(path, "ENCRYPT"),
+        load_nist_vectors,
         os.path.join("ciphers", "Blowfish"),
         ["bf-ecb.txt"],
         lambda key: algorithms.Blowfish(binascii.unhexlify(key)),
         lambda key: modes.ECB(),
-        only_if=lambda backend: backend.ciphers.supported(
+        only_if=lambda backend: backend.cipher_supported(
             algorithms.Blowfish("\x00" * 56), modes.ECB()
         ),
         skip_message="Does not support Blowfish ECB",
     )
 
     test_CBC = generate_encrypt_test(
-        lambda path: load_nist_vectors_from_file(path, "ENCRYPT"),
+        load_nist_vectors,
         os.path.join("ciphers", "Blowfish"),
         ["bf-cbc.txt"],
         lambda key, iv: algorithms.Blowfish(binascii.unhexlify(key)),
         lambda key, iv: modes.CBC(binascii.unhexlify(iv)),
-        only_if=lambda backend: backend.ciphers.supported(
+        only_if=lambda backend: backend.cipher_supported(
             algorithms.Blowfish("\x00" * 56), modes.CBC("\x00" * 8)
         ),
         skip_message="Does not support Blowfish CBC",
     )
 
     test_OFB = generate_encrypt_test(
-        lambda path: load_nist_vectors_from_file(path, "ENCRYPT"),
+        load_nist_vectors,
         os.path.join("ciphers", "Blowfish"),
         ["bf-ofb.txt"],
         lambda key, iv: algorithms.Blowfish(binascii.unhexlify(key)),
         lambda key, iv: modes.OFB(binascii.unhexlify(iv)),
-        only_if=lambda backend: backend.ciphers.supported(
+        only_if=lambda backend: backend.cipher_supported(
             algorithms.Blowfish("\x00" * 56), modes.OFB("\x00" * 8)
         ),
         skip_message="Does not support Blowfish OFB",
     )
 
     test_CFB = generate_encrypt_test(
-        lambda path: load_nist_vectors_from_file(path, "ENCRYPT"),
+        load_nist_vectors,
         os.path.join("ciphers", "Blowfish"),
         ["bf-cfb.txt"],
         lambda key, iv: algorithms.Blowfish(binascii.unhexlify(key)),
         lambda key, iv: modes.CFB(binascii.unhexlify(iv)),
-        only_if=lambda backend: backend.ciphers.supported(
+        only_if=lambda backend: backend.cipher_supported(
             algorithms.Blowfish("\x00" * 56), modes.CFB("\x00" * 8)
         ),
         skip_message="Does not support Blowfish CFB",
diff --git a/tests/hazmat/primitives/test_camellia.py b/tests/hazmat/primitives/test_camellia.py
index e1be5d1..a2c935d 100644
--- a/tests/hazmat/primitives/test_camellia.py
+++ b/tests/hazmat/primitives/test_camellia.py
@@ -20,13 +20,13 @@
 
 from .utils import generate_encrypt_test
 from ...utils import (
-    load_cryptrec_vectors_from_file, load_openssl_vectors_from_file
+    load_cryptrec_vectors, load_openssl_vectors
 )
 
 
 class TestCamellia(object):
     test_ECB = generate_encrypt_test(
-        load_cryptrec_vectors_from_file,
+        load_cryptrec_vectors,
         os.path.join("ciphers", "Camellia"),
         [
             "camellia-128-ecb.txt",
@@ -35,43 +35,43 @@
         ],
         lambda key: algorithms.Camellia(binascii.unhexlify((key))),
         lambda key: modes.ECB(),
-        only_if=lambda backend: backend.ciphers.supported(
+        only_if=lambda backend: backend.cipher_supported(
             algorithms.Camellia("\x00" * 16), modes.ECB()
         ),
         skip_message="Does not support Camellia ECB",
     )
 
     test_CBC = generate_encrypt_test(
-        load_openssl_vectors_from_file,
+        load_openssl_vectors,
         os.path.join("ciphers", "Camellia"),
         ["camellia-cbc.txt"],
         lambda key, iv: algorithms.Camellia(binascii.unhexlify(key)),
         lambda key, iv: modes.CBC(binascii.unhexlify(iv)),
-        only_if=lambda backend: backend.ciphers.supported(
+        only_if=lambda backend: backend.cipher_supported(
             algorithms.Camellia("\x00" * 16), modes.CBC("\x00" * 16)
         ),
         skip_message="Does not support Camellia CBC",
     )
 
     test_OFB = generate_encrypt_test(
-        load_openssl_vectors_from_file,
+        load_openssl_vectors,
         os.path.join("ciphers", "Camellia"),
         ["camellia-ofb.txt"],
         lambda key, iv: algorithms.Camellia(binascii.unhexlify(key)),
         lambda key, iv: modes.OFB(binascii.unhexlify(iv)),
-        only_if=lambda backend: backend.ciphers.supported(
+        only_if=lambda backend: backend.cipher_supported(
             algorithms.Camellia("\x00" * 16), modes.OFB("\x00" * 16)
         ),
         skip_message="Does not support Camellia OFB",
     )
 
     test_CFB = generate_encrypt_test(
-        load_openssl_vectors_from_file,
+        load_openssl_vectors,
         os.path.join("ciphers", "Camellia"),
         ["camellia-cfb.txt"],
         lambda key, iv: algorithms.Camellia(binascii.unhexlify(key)),
         lambda key, iv: modes.CFB(binascii.unhexlify(iv)),
-        only_if=lambda backend: backend.ciphers.supported(
+        only_if=lambda backend: backend.cipher_supported(
             algorithms.Camellia("\x00" * 16), modes.CFB("\x00" * 16)
         ),
         skip_message="Does not support Camellia CFB",
diff --git a/tests/hazmat/primitives/test_cast5.py b/tests/hazmat/primitives/test_cast5.py
index b298843..a283daf 100644
--- a/tests/hazmat/primitives/test_cast5.py
+++ b/tests/hazmat/primitives/test_cast5.py
@@ -19,19 +19,17 @@
 from cryptography.hazmat.primitives.ciphers import algorithms, modes
 
 from .utils import generate_encrypt_test
-from ...utils import load_nist_vectors_from_file
+from ...utils import load_nist_vectors
 
 
 class TestCAST5(object):
     test_ECB = generate_encrypt_test(
-        lambda path: load_nist_vectors_from_file(path, "ENCRYPT"),
+        load_nist_vectors,
         os.path.join("ciphers", "CAST5"),
-        [
-            "cast5-ecb.txt",
-        ],
+        ["cast5-ecb.txt"],
         lambda key: algorithms.CAST5(binascii.unhexlify((key))),
         lambda key: modes.ECB(),
-        only_if=lambda backend: backend.ciphers.supported(
+        only_if=lambda backend: backend.cipher_supported(
             algorithms.CAST5("\x00" * 16), modes.ECB()
         ),
         skip_message="Does not support CAST5 ECB",
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_hash_vectors.py b/tests/hazmat/primitives/test_hash_vectors.py
index fca839c..a865581 100644
--- a/tests/hazmat/primitives/test_hash_vectors.py
+++ b/tests/hazmat/primitives/test_hash_vectors.py
@@ -18,108 +18,108 @@
 from cryptography.hazmat.primitives import hashes
 
 from .utils import generate_hash_test, generate_long_string_hash_test
-from ...utils import load_hash_vectors_from_file
+from ...utils import load_hash_vectors
 
 
 class TestSHA1(object):
     test_SHA1 = generate_hash_test(
-        load_hash_vectors_from_file,
+        load_hash_vectors,
         os.path.join("hashes", "SHA1"),
         [
             "SHA1LongMsg.rsp",
             "SHA1ShortMsg.rsp",
         ],
         hashes.SHA1(),
-        only_if=lambda backend: backend.hashes.supported(hashes.SHA1),
+        only_if=lambda backend: backend.hash_supported(hashes.SHA1),
         skip_message="Does not support SHA1",
     )
 
 
 class TestSHA224(object):
     test_SHA224 = generate_hash_test(
-        load_hash_vectors_from_file,
+        load_hash_vectors,
         os.path.join("hashes", "SHA2"),
         [
             "SHA224LongMsg.rsp",
             "SHA224ShortMsg.rsp",
         ],
         hashes.SHA224(),
-        only_if=lambda backend: backend.hashes.supported(hashes.SHA224),
+        only_if=lambda backend: backend.hash_supported(hashes.SHA224),
         skip_message="Does not support SHA224",
     )
 
 
 class TestSHA256(object):
     test_SHA256 = generate_hash_test(
-        load_hash_vectors_from_file,
+        load_hash_vectors,
         os.path.join("hashes", "SHA2"),
         [
             "SHA256LongMsg.rsp",
             "SHA256ShortMsg.rsp",
         ],
         hashes.SHA256(),
-        only_if=lambda backend: backend.hashes.supported(hashes.SHA256),
+        only_if=lambda backend: backend.hash_supported(hashes.SHA256),
         skip_message="Does not support SHA256",
     )
 
 
 class TestSHA384(object):
     test_SHA384 = generate_hash_test(
-        load_hash_vectors_from_file,
+        load_hash_vectors,
         os.path.join("hashes", "SHA2"),
         [
             "SHA384LongMsg.rsp",
             "SHA384ShortMsg.rsp",
         ],
         hashes.SHA384(),
-        only_if=lambda backend: backend.hashes.supported(hashes.SHA384),
+        only_if=lambda backend: backend.hash_supported(hashes.SHA384),
         skip_message="Does not support SHA384",
     )
 
 
 class TestSHA512(object):
     test_SHA512 = generate_hash_test(
-        load_hash_vectors_from_file,
+        load_hash_vectors,
         os.path.join("hashes", "SHA2"),
         [
             "SHA512LongMsg.rsp",
             "SHA512ShortMsg.rsp",
         ],
         hashes.SHA512(),
-        only_if=lambda backend: backend.hashes.supported(hashes.SHA512),
+        only_if=lambda backend: backend.hash_supported(hashes.SHA512),
         skip_message="Does not support SHA512",
     )
 
 
 class TestRIPEMD160(object):
     test_RIPEMD160 = generate_hash_test(
-        load_hash_vectors_from_file,
+        load_hash_vectors,
         os.path.join("hashes", "ripemd160"),
         [
             "ripevectors.txt",
         ],
         hashes.RIPEMD160(),
-        only_if=lambda backend: backend.hashes.supported(hashes.RIPEMD160),
+        only_if=lambda backend: backend.hash_supported(hashes.RIPEMD160),
         skip_message="Does not support RIPEMD160",
     )
 
     test_RIPEMD160_long_string = generate_long_string_hash_test(
         hashes.RIPEMD160(),
         "52783243c1697bdbe16d37f97f68f08325dc1528",
-        only_if=lambda backend: backend.hashes.supported(hashes.RIPEMD160),
+        only_if=lambda backend: backend.hash_supported(hashes.RIPEMD160),
         skip_message="Does not support RIPEMD160",
     )
 
 
 class TestWhirlpool(object):
     test_whirlpool = generate_hash_test(
-        load_hash_vectors_from_file,
+        load_hash_vectors,
         os.path.join("hashes", "whirlpool"),
         [
             "iso-test-vectors.txt",
         ],
         hashes.Whirlpool(),
-        only_if=lambda backend: backend.hashes.supported(hashes.Whirlpool),
+        only_if=lambda backend: backend.hash_supported(hashes.Whirlpool),
         skip_message="Does not support Whirlpool",
     )
 
@@ -128,19 +128,19 @@
         ("0c99005beb57eff50a7cf005560ddf5d29057fd86b2"
          "0bfd62deca0f1ccea4af51fc15490eddc47af32bb2b"
          "66c34ff9ad8c6008ad677f77126953b226e4ed8b01"),
-        only_if=lambda backend: backend.hashes.supported(hashes.Whirlpool),
+        only_if=lambda backend: backend.hash_supported(hashes.Whirlpool),
         skip_message="Does not support Whirlpool",
     )
 
 
 class TestMD5(object):
     test_md5 = generate_hash_test(
-        load_hash_vectors_from_file,
+        load_hash_vectors,
         os.path.join("hashes", "MD5"),
         [
             "rfc-1321.txt",
         ],
         hashes.MD5(),
-        only_if=lambda backend: backend.hashes.supported(hashes.MD5),
+        only_if=lambda backend: backend.hash_supported(hashes.MD5),
         skip_message="Does not support MD5",
     )
diff --git a/tests/hazmat/primitives/test_hashes.py b/tests/hazmat/primitives/test_hashes.py
index 07ab248..ff42e8f 100644
--- a/tests/hazmat/primitives/test_hashes.py
+++ b/tests/hazmat/primitives/test_hashes.py
@@ -19,7 +19,7 @@
 
 import six
 
-from cryptography.hazmat.bindings import _default_backend
+from cryptography.exceptions import AlreadyFinalized
 from cryptography.hazmat.primitives import hashes
 
 from .utils import generate_base_hash_test
@@ -32,24 +32,30 @@
             m.update(six.u("\u00FC"))
 
     def test_copy_backend_object(self):
-        pretend_hashes = pretend.stub(copy_ctx=lambda a: "copiedctx")
-        pretend_backend = pretend.stub(hashes=pretend_hashes)
-        pretend_ctx = pretend.stub()
+        pretend_backend = pretend.stub()
+        copied_ctx = pretend.stub()
+        pretend_ctx = pretend.stub(copy=lambda: copied_ctx)
         h = hashes.Hash(hashes.SHA1(), backend=pretend_backend,
                         ctx=pretend_ctx)
         assert h._backend is pretend_backend
         assert h.copy()._backend is h._backend
 
-    def test_default_backend_creation(self):
-        """
-        This test assumes the presence of SHA1 in the default backend.
-        """
-        h = hashes.Hash(hashes.SHA1())
-        assert h._backend is _default_backend
-
-    def test_hash_algorithm_instance(self):
+    def test_hash_algorithm_instance(self, backend):
         with pytest.raises(TypeError):
-            hashes.Hash(hashes.SHA1)
+            hashes.Hash(hashes.SHA1, backend=backend)
+
+    def test_raises_after_finalize(self, backend):
+        h = hashes.Hash(hashes.SHA1(), backend=backend)
+        h.finalize()
+
+        with pytest.raises(AlreadyFinalized):
+            h.update(b"foo")
+
+        with pytest.raises(AlreadyFinalized):
+            h.copy()
+
+        with pytest.raises(AlreadyFinalized):
+            h.finalize()
 
 
 class TestSHA1(object):
@@ -57,7 +63,7 @@
         hashes.SHA1(),
         digest_size=20,
         block_size=64,
-        only_if=lambda backend: backend.hashes.supported(hashes.SHA1),
+        only_if=lambda backend: backend.hash_supported(hashes.SHA1),
         skip_message="Does not support SHA1",
     )
 
@@ -67,7 +73,7 @@
         hashes.SHA224(),
         digest_size=28,
         block_size=64,
-        only_if=lambda backend: backend.hashes.supported(hashes.SHA224),
+        only_if=lambda backend: backend.hash_supported(hashes.SHA224),
         skip_message="Does not support SHA224",
     )
 
@@ -77,7 +83,7 @@
         hashes.SHA256(),
         digest_size=32,
         block_size=64,
-        only_if=lambda backend: backend.hashes.supported(hashes.SHA256),
+        only_if=lambda backend: backend.hash_supported(hashes.SHA256),
         skip_message="Does not support SHA256",
     )
 
@@ -87,7 +93,7 @@
         hashes.SHA384(),
         digest_size=48,
         block_size=128,
-        only_if=lambda backend: backend.hashes.supported(hashes.SHA384),
+        only_if=lambda backend: backend.hash_supported(hashes.SHA384),
         skip_message="Does not support SHA384",
     )
 
@@ -97,7 +103,7 @@
         hashes.SHA512(),
         digest_size=64,
         block_size=128,
-        only_if=lambda backend: backend.hashes.supported(hashes.SHA512),
+        only_if=lambda backend: backend.hash_supported(hashes.SHA512),
         skip_message="Does not support SHA512",
     )
 
@@ -107,7 +113,7 @@
         hashes.RIPEMD160(),
         digest_size=20,
         block_size=64,
-        only_if=lambda backend: backend.hashes.supported(hashes.RIPEMD160),
+        only_if=lambda backend: backend.hash_supported(hashes.RIPEMD160),
         skip_message="Does not support RIPEMD160",
     )
 
@@ -117,7 +123,7 @@
         hashes.Whirlpool(),
         digest_size=64,
         block_size=64,
-        only_if=lambda backend: backend.hashes.supported(hashes.Whirlpool),
+        only_if=lambda backend: backend.hash_supported(hashes.Whirlpool),
         skip_message="Does not support Whirlpool",
     )
 
@@ -127,6 +133,6 @@
         hashes.MD5(),
         digest_size=16,
         block_size=64,
-        only_if=lambda backend: backend.hashes.supported(hashes.MD5),
+        only_if=lambda backend: backend.hash_supported(hashes.MD5),
         skip_message="Does not support MD5",
     )
diff --git a/tests/hazmat/primitives/test_hmac.py b/tests/hazmat/primitives/test_hmac.py
index a44838c..992bcb1 100644
--- a/tests/hazmat/primitives/test_hmac.py
+++ b/tests/hazmat/primitives/test_hmac.py
@@ -19,6 +19,7 @@
 
 import six
 
+from cryptography.exceptions import AlreadyFinalized
 from cryptography.hazmat.primitives import hashes, hmac
 
 from .utils import generate_base_hmac_test
@@ -27,7 +28,7 @@
 class TestHMAC(object):
     test_copy = generate_base_hmac_test(
         hashes.MD5(),
-        only_if=lambda backend: backend.hashes.supported(hashes.MD5),
+        only_if=lambda backend: backend.hash_supported(hashes.MD5),
         skip_message="Does not support MD5",
     )
 
@@ -37,14 +38,28 @@
             h.update(six.u("\u00FC"))
 
     def test_copy_backend_object(self):
-        pretend_hmac = pretend.stub(copy_ctx=lambda a: True)
+        pretend_hmac = pretend.stub()
         pretend_backend = pretend.stub(hmacs=pretend_hmac)
-        pretend_ctx = pretend.stub()
+        copied_ctx = pretend.stub()
+        pretend_ctx = pretend.stub(copy=lambda: copied_ctx)
         h = hmac.HMAC(b"key", hashes.SHA1(), backend=pretend_backend,
                       ctx=pretend_ctx)
         assert h._backend is pretend_backend
         assert h.copy()._backend is pretend_backend
 
-    def test_hmac_algorithm_instance(self):
+    def test_hmac_algorithm_instance(self, backend):
         with pytest.raises(TypeError):
-            hmac.HMAC(b"key", hashes.SHA1)
+            hmac.HMAC(b"key", hashes.SHA1, backend=backend)
+
+    def test_raises_after_finalize(self, backend):
+        h = hmac.HMAC(b"key", hashes.SHA1(), backend=backend)
+        h.finalize()
+
+        with pytest.raises(AlreadyFinalized):
+            h.update(b"foo")
+
+        with pytest.raises(AlreadyFinalized):
+            h.copy()
+
+        with pytest.raises(AlreadyFinalized):
+            h.finalize()
diff --git a/tests/hazmat/primitives/test_hmac_vectors.py b/tests/hazmat/primitives/test_hmac_vectors.py
index 52d592b..7d0f156 100644
--- a/tests/hazmat/primitives/test_hmac_vectors.py
+++ b/tests/hazmat/primitives/test_hmac_vectors.py
@@ -16,95 +16,95 @@
 from cryptography.hazmat.primitives import hashes
 
 from .utils import generate_hmac_test
-from ...utils import load_hash_vectors_from_file
+from ...utils import load_hash_vectors
 
 
 class TestHMAC_MD5(object):
     test_hmac_md5 = generate_hmac_test(
-        load_hash_vectors_from_file,
+        load_hash_vectors,
         "HMAC",
         [
             "rfc-2202-md5.txt",
         ],
         hashes.MD5(),
-        only_if=lambda backend: backend.hashes.supported(hashes.MD5),
+        only_if=lambda backend: backend.hash_supported(hashes.MD5),
         skip_message="Does not support MD5",
     )
 
 
 class TestHMAC_SHA1(object):
     test_hmac_sha1 = generate_hmac_test(
-        load_hash_vectors_from_file,
+        load_hash_vectors,
         "HMAC",
         [
             "rfc-2202-sha1.txt",
         ],
         hashes.SHA1(),
-        only_if=lambda backend: backend.hashes.supported(hashes.SHA1),
+        only_if=lambda backend: backend.hash_supported(hashes.SHA1),
         skip_message="Does not support SHA1",
     )
 
 
 class TestHMAC_SHA224(object):
     test_hmac_sha224 = generate_hmac_test(
-        load_hash_vectors_from_file,
+        load_hash_vectors,
         "HMAC",
         [
             "rfc-4231-sha224.txt",
         ],
         hashes.SHA224(),
-        only_if=lambda backend: backend.hashes.supported(hashes.SHA224),
+        only_if=lambda backend: backend.hash_supported(hashes.SHA224),
         skip_message="Does not support SHA224",
     )
 
 
 class TestHMAC_SHA256(object):
     test_hmac_sha256 = generate_hmac_test(
-        load_hash_vectors_from_file,
+        load_hash_vectors,
         "HMAC",
         [
             "rfc-4231-sha256.txt",
         ],
         hashes.SHA256(),
-        only_if=lambda backend: backend.hashes.supported(hashes.SHA256),
+        only_if=lambda backend: backend.hash_supported(hashes.SHA256),
         skip_message="Does not support SHA256",
     )
 
 
 class TestHMAC_SHA384(object):
     test_hmac_sha384 = generate_hmac_test(
-        load_hash_vectors_from_file,
+        load_hash_vectors,
         "HMAC",
         [
             "rfc-4231-sha384.txt",
         ],
         hashes.SHA384(),
-        only_if=lambda backend: backend.hashes.supported(hashes.SHA384),
+        only_if=lambda backend: backend.hash_supported(hashes.SHA384),
         skip_message="Does not support SHA384",
     )
 
 
 class TestHMAC_SHA512(object):
     test_hmac_sha512 = generate_hmac_test(
-        load_hash_vectors_from_file,
+        load_hash_vectors,
         "HMAC",
         [
             "rfc-4231-sha512.txt",
         ],
         hashes.SHA512(),
-        only_if=lambda backend: backend.hashes.supported(hashes.SHA512),
+        only_if=lambda backend: backend.hash_supported(hashes.SHA512),
         skip_message="Does not support SHA512",
     )
 
 
 class TestHMAC_RIPEMD160(object):
     test_hmac_ripemd160 = generate_hmac_test(
-        load_hash_vectors_from_file,
+        load_hash_vectors,
         "HMAC",
         [
             "rfc-2286-ripemd160.txt",
         ],
         hashes.RIPEMD160(),
-        only_if=lambda backend: backend.hashes.supported(hashes.RIPEMD160),
+        only_if=lambda backend: backend.hash_supported(hashes.RIPEMD160),
         skip_message="Does not support RIPEMD160",
     )
diff --git a/tests/hazmat/primitives/test_utils.py b/tests/hazmat/primitives/test_utils.py
index d7247e6..c39364c 100644
--- a/tests/hazmat/primitives/test_utils.py
+++ b/tests/hazmat/primitives/test_utils.py
@@ -2,7 +2,8 @@
 
 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, aead_test,
+    aead_exception_test, aead_tag_exception_test,
 )
 
 
@@ -17,6 +18,39 @@
         assert exc_info.value.args[0] == "message!"
 
 
+class TestAEADTest(object):
+    def test_skips_if_only_if_returns_false(self):
+        with pytest.raises(pytest.skip.Exception) as exc_info:
+            aead_test(
+                None, None, None, None,
+                only_if=lambda backend: False,
+                skip_message="message!"
+            )
+        assert exc_info.value.args[0] == "message!"
+
+
+class TestAEADExceptionTest(object):
+    def test_skips_if_only_if_returns_false(self):
+        with pytest.raises(pytest.skip.Exception) as exc_info:
+            aead_exception_test(
+                None, None, None,
+                only_if=lambda backend: False,
+                skip_message="message!"
+            )
+        assert exc_info.value.args[0] == "message!"
+
+
+class TestAEADTagExceptionTest(object):
+    def test_skips_if_only_if_returns_false(self):
+        with pytest.raises(pytest.skip.Exception) as exc_info:
+            aead_tag_exception_test(
+                None, None, None,
+                only_if=lambda backend: False,
+                skip_message="message!"
+            )
+        assert exc_info.value.args[0] == "message!"
+
+
 class TestHashTest(object):
     def test_skips_if_only_if_returns_false(self):
         with pytest.raises(pytest.skip.Exception) as exc_info:
@@ -70,3 +104,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..705983a 100644
--- a/tests/hazmat/primitives/utils.py
+++ b/tests/hazmat/primitives/utils.py
@@ -4,9 +4,13 @@
 import pytest
 
 from cryptography.hazmat.bindings import _ALL_BACKENDS
-from cryptography.hazmat.primitives import hashes
-from cryptography.hazmat.primitives import hmac
+from cryptography.hazmat.primitives import hashes, hmac
 from cryptography.hazmat.primitives.ciphers import Cipher
+from cryptography.exceptions import (
+    AlreadyFinalized, NotYetFinalized, AlreadyUpdated, InvalidTag,
+)
+
+from ...utils import load_vectors_from_file
 
 
 def generate_encrypt_test(param_loader, path, file_names, cipher_factory,
@@ -15,7 +19,10 @@
     def test_encryption(self):
         for backend in _ALL_BACKENDS:
             for file_name in file_names:
-                for params in param_loader(os.path.join(path, file_name)):
+                for params in load_vectors_from_file(
+                    os.path.join(path, file_name),
+                    param_loader
+                ):
                     yield (
                         encrypt_test,
                         backend,
@@ -37,7 +44,7 @@
     cipher = Cipher(
         cipher_factory(**params),
         mode_factory(**params),
-        backend
+        backend=backend
     )
     encryptor = cipher.encryptor()
     actual_ciphertext = encryptor.update(binascii.unhexlify(plaintext))
@@ -49,12 +56,123 @@
     assert actual_plaintext == binascii.unhexlify(plaintext)
 
 
+def generate_aead_test(param_loader, path, file_names, cipher_factory,
+                       mode_factory, only_if, skip_message):
+    def test_aead(self):
+        for backend in _ALL_BACKENDS:
+            for file_name in file_names:
+                for params in load_vectors_from_file(
+                    os.path.join(path, file_name),
+                    param_loader
+                ):
+                    yield (
+                        aead_test,
+                        backend,
+                        cipher_factory,
+                        mode_factory,
+                        params,
+                        only_if,
+                        skip_message
+                    )
+    return test_aead
+
+
+def aead_test(backend, cipher_factory, mode_factory, params, only_if,
+              skip_message):
+    if not only_if(backend):
+        pytest.skip(skip_message)
+    if params.get("pt") is not None:
+        plaintext = params.pop("pt")
+    ciphertext = params.pop("ct")
+    aad = params.pop("aad")
+    if params.get("fail") is True:
+        cipher = Cipher(
+            cipher_factory(binascii.unhexlify(params["key"])),
+            mode_factory(binascii.unhexlify(params["iv"]),
+                         binascii.unhexlify(params["tag"])),
+            backend
+        )
+        decryptor = cipher.decryptor()
+        decryptor.authenticate_additional_data(binascii.unhexlify(aad))
+        actual_plaintext = decryptor.update(binascii.unhexlify(ciphertext))
+        with pytest.raises(InvalidTag):
+            decryptor.finalize()
+    else:
+        cipher = Cipher(
+            cipher_factory(binascii.unhexlify(params["key"])),
+            mode_factory(binascii.unhexlify(params["iv"]), None),
+            backend
+        )
+        encryptor = cipher.encryptor()
+        encryptor.authenticate_additional_data(binascii.unhexlify(aad))
+        actual_ciphertext = encryptor.update(binascii.unhexlify(plaintext))
+        actual_ciphertext += encryptor.finalize()
+        tag_len = len(params["tag"])
+        assert binascii.hexlify(encryptor.tag)[:tag_len] == params["tag"]
+        cipher = Cipher(
+            cipher_factory(binascii.unhexlify(params["key"])),
+            mode_factory(binascii.unhexlify(params["iv"]),
+                         binascii.unhexlify(params["tag"])),
+            backend
+        )
+        decryptor = cipher.decryptor()
+        decryptor.authenticate_additional_data(binascii.unhexlify(aad))
+        actual_plaintext = decryptor.update(binascii.unhexlify(ciphertext))
+        actual_plaintext += decryptor.finalize()
+        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 load_vectors_from_file(
+                    os.path.join(path, file_name),
+                    param_loader
+                ):
+                    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=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):
         for backend in _ALL_BACKENDS:
             for file_name in file_names:
-                for params in param_loader(os.path.join(path, file_name)):
+                for params in load_vectors_from_file(
+                    os.path.join(path, file_name),
+                    param_loader
+                ):
                     yield (
                         hash_test,
                         backend,
@@ -105,6 +223,12 @@
     assert m != m_copy
     assert m._ctx != m_copy._ctx
 
+    m.update(b"abc")
+    copy = m.copy()
+    copy.update(b"123")
+    m.update(b"123")
+    assert copy.finalize() == m.finalize()
+
 
 def generate_long_string_hash_test(hash_factory, md, only_if=None,
                                    skip_message=None):
@@ -134,7 +258,10 @@
     def test_hmac(self):
         for backend in _ALL_BACKENDS:
             for file_name in file_names:
-                for params in param_loader(os.path.join(path, file_name)):
+                for params in load_vectors_from_file(
+                    os.path.join(path, file_name),
+                    param_loader
+                ):
                     yield (
                         hmac_test,
                         backend,
@@ -152,7 +279,7 @@
     msg = params[0]
     md = params[1]
     key = params[2]
-    h = hmac.HMAC(binascii.unhexlify(key), algorithm)
+    h = hmac.HMAC(binascii.unhexlify(key), algorithm, backend=backend)
     h.update(binascii.unhexlify(msg))
     assert h.finalize() == binascii.unhexlify(md.encode("ascii"))
 
@@ -174,7 +301,90 @@
     if only_if is not None and not only_if(backend):
         pytest.skip(skip_message)
     key = b"ab"
-    h = hmac.HMAC(binascii.unhexlify(key), algorithm)
+    h = hmac.HMAC(binascii.unhexlify(key), algorithm, backend=backend)
     h_copy = h.copy()
     assert h != h_copy
     assert h._ctx != h_copy._ctx
+
+
+def generate_aead_exception_test(cipher_factory, mode_factory,
+                                 only_if, skip_message):
+    def test_aead_exception(self):
+        for backend in _ALL_BACKENDS:
+            yield (
+                aead_exception_test,
+                backend,
+                cipher_factory,
+                mode_factory,
+                only_if,
+                skip_message
+            )
+    return test_aead_exception
+
+
+def aead_exception_test(backend, cipher_factory, mode_factory,
+                        only_if, skip_message):
+    if not only_if(backend):
+        pytest.skip(skip_message)
+    cipher = Cipher(
+        cipher_factory(binascii.unhexlify(b"0" * 32)),
+        mode_factory(binascii.unhexlify(b"0" * 24)),
+        backend
+    )
+    encryptor = cipher.encryptor()
+    encryptor.update(b"a" * 16)
+    with pytest.raises(NotYetFinalized):
+        encryptor.tag
+    with pytest.raises(AlreadyUpdated):
+        encryptor.authenticate_additional_data(b"b" * 16)
+    encryptor.finalize()
+    with pytest.raises(AlreadyFinalized):
+        encryptor.authenticate_additional_data(b"b" * 16)
+    with pytest.raises(AlreadyFinalized):
+        encryptor.update(b"b" * 16)
+    with pytest.raises(AlreadyFinalized):
+        encryptor.finalize()
+    cipher = Cipher(
+        cipher_factory(binascii.unhexlify(b"0" * 32)),
+        mode_factory(binascii.unhexlify(b"0" * 24), b"0" * 16),
+        backend
+    )
+    decryptor = cipher.decryptor()
+    decryptor.update(b"a" * 16)
+    with pytest.raises(AttributeError):
+        decryptor.tag
+
+
+def generate_aead_tag_exception_test(cipher_factory, mode_factory,
+                                     only_if, skip_message):
+    def test_aead_tag_exception(self):
+        for backend in _ALL_BACKENDS:
+            yield (
+                aead_tag_exception_test,
+                backend,
+                cipher_factory,
+                mode_factory,
+                only_if,
+                skip_message
+            )
+    return test_aead_tag_exception
+
+
+def aead_tag_exception_test(backend, cipher_factory, mode_factory,
+                            only_if, skip_message):
+    if not only_if(backend):
+        pytest.skip(skip_message)
+    cipher = Cipher(
+        cipher_factory(binascii.unhexlify(b"0" * 32)),
+        mode_factory(binascii.unhexlify(b"0" * 24)),
+        backend
+    )
+    with pytest.raises(ValueError):
+        cipher.decryptor()
+    cipher = Cipher(
+        cipher_factory(binascii.unhexlify(b"0" * 32)),
+        mode_factory(binascii.unhexlify(b"0" * 24), b"0" * 16),
+        backend
+    )
+    with pytest.raises(ValueError):
+        cipher.encryptor()
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 0692c8d..5c58fd7 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -17,14 +17,12 @@
 import pytest
 
 from .utils import (
-    load_nist_vectors, load_nist_vectors_from_file, load_cryptrec_vectors,
-    load_cryptrec_vectors_from_file, load_openssl_vectors,
-    load_openssl_vectors_from_file, load_hash_vectors,
-    load_hash_vectors_from_file
+    load_nist_vectors, load_vectors_from_file, load_cryptrec_vectors,
+    load_openssl_vectors, load_hash_vectors,
 )
 
 
-def test_load_nist_vectors_encrypt():
+def test_load_nist_vectors():
     vector_data = textwrap.dedent("""
     # CAVS 11.1
     # Config info for aes_values
@@ -62,81 +60,7 @@
     PLAINTEXT = 9798c4640bad75c7c3227db910174e72
     """).splitlines()
 
-    assert load_nist_vectors(vector_data, "ENCRYPT") == [
-        {
-            "key": b"00000000000000000000000000000000",
-            "iv": b"00000000000000000000000000000000",
-            "plaintext": b"f34481ec3cc627bacd5dc3fb08f273e6",
-            "ciphertext": b"0336763e966d92595a567cc9ce537f5e",
-        },
-        {
-            "key": b"00000000000000000000000000000000",
-            "iv": b"00000000000000000000000000000000",
-            "plaintext": b"9798c4640bad75c7c3227db910174e72",
-            "ciphertext": b"a9a1631bf4996954ebc093957b234589",
-        },
-    ]
-
-
-def test_load_nist_vectors_decrypt():
-    vector_data = textwrap.dedent("""
-    # CAVS 11.1
-    # Config info for aes_values
-    # AESVS GFSbox test data for CBC
-    # State : Encrypt and Decrypt
-    # Key Length : 128
-    # Generated on Fri Apr 22 15:11:33 2011
-
-    [ENCRYPT]
-
-    COUNT = 0
-    KEY = 00000000000000000000000000000000
-    IV = 00000000000000000000000000000000
-    PLAINTEXT = f34481ec3cc627bacd5dc3fb08f273e6
-    CIPHERTEXT = 0336763e966d92595a567cc9ce537f5e
-
-    COUNT = 1
-    KEY = 00000000000000000000000000000000
-    IV = 00000000000000000000000000000000
-    PLAINTEXT = 9798c4640bad75c7c3227db910174e72
-    CIPHERTEXT = a9a1631bf4996954ebc093957b234589
-
-    [DECRYPT]
-
-    COUNT = 0
-    KEY = 00000000000000000000000000000000
-    IV = 00000000000000000000000000000000
-    CIPHERTEXT = 0336763e966d92595a567cc9ce537f5e
-    PLAINTEXT = f34481ec3cc627bacd5dc3fb08f273e6
-
-    COUNT = 1
-    KEY = 00000000000000000000000000000000
-    IV = 00000000000000000000000000000000
-    CIPHERTEXT = a9a1631bf4996954ebc093957b234589
-    PLAINTEXT = 9798c4640bad75c7c3227db910174e72
-    """).splitlines()
-
-    assert load_nist_vectors(vector_data, "DECRYPT") == [
-        {
-            "key": b"00000000000000000000000000000000",
-            "iv": b"00000000000000000000000000000000",
-            "plaintext": b"f34481ec3cc627bacd5dc3fb08f273e6",
-            "ciphertext": b"0336763e966d92595a567cc9ce537f5e",
-        },
-        {
-            "key": b"00000000000000000000000000000000",
-            "iv": b"00000000000000000000000000000000",
-            "plaintext": b"9798c4640bad75c7c3227db910174e72",
-            "ciphertext": b"a9a1631bf4996954ebc093957b234589",
-        },
-    ]
-
-
-def test_load_nist_vectors_from_file_encrypt():
-    assert load_nist_vectors_from_file(
-        os.path.join("ciphers", "AES", "CBC", "CBCGFSbox128.rsp"),
-        "ENCRYPT"
-    ) == [
+    assert load_nist_vectors(vector_data) == [
         {
             "key": b"00000000000000000000000000000000",
             "iv": b"00000000000000000000000000000000",
@@ -152,44 +76,6 @@
         {
             "key": b"00000000000000000000000000000000",
             "iv": b"00000000000000000000000000000000",
-            "plaintext": b"96ab5c2ff612d9dfaae8c31f30c42168",
-            "ciphertext": b"ff4f8391a6a40ca5b25d23bedd44a597",
-        },
-        {
-            "key": b"00000000000000000000000000000000",
-            "iv": b"00000000000000000000000000000000",
-            "plaintext": b"6a118a874519e64e9963798a503f1d35",
-            "ciphertext": b"dc43be40be0e53712f7e2bf5ca707209",
-        },
-        {
-            "key": b"00000000000000000000000000000000",
-            "iv": b"00000000000000000000000000000000",
-            "plaintext": b"cb9fceec81286ca3e989bd979b0cb284",
-            "ciphertext": b"92beedab1895a94faa69b632e5cc47ce",
-        },
-        {
-            "key": b"00000000000000000000000000000000",
-            "iv": b"00000000000000000000000000000000",
-            "plaintext": b"b26aeb1874e47ca8358ff22378f09144",
-            "ciphertext": b"459264f4798f6a78bacb89c15ed3d601",
-        },
-        {
-            "key": b"00000000000000000000000000000000",
-            "iv": b"00000000000000000000000000000000",
-            "plaintext": b"58c8e00b2631686d54eab84b91f0aca1",
-            "ciphertext": b"08a4e2efec8a8e3312ca7460b9040bbf",
-        },
-    ]
-
-
-def test_load_nist_vectors_from_file_decrypt():
-    assert load_nist_vectors_from_file(
-        os.path.join("ciphers", "AES", "CBC", "CBCGFSbox128.rsp"),
-        "DECRYPT",
-    ) == [
-        {
-            "key": b"00000000000000000000000000000000",
-            "iv": b"00000000000000000000000000000000",
             "plaintext": b"f34481ec3cc627bacd5dc3fb08f273e6",
             "ciphertext": b"0336763e966d92595a567cc9ce537f5e",
         },
@@ -199,36 +85,6 @@
             "plaintext": b"9798c4640bad75c7c3227db910174e72",
             "ciphertext": b"a9a1631bf4996954ebc093957b234589",
         },
-        {
-            "key": b"00000000000000000000000000000000",
-            "iv": b"00000000000000000000000000000000",
-            "plaintext": b"96ab5c2ff612d9dfaae8c31f30c42168",
-            "ciphertext": b"ff4f8391a6a40ca5b25d23bedd44a597",
-        },
-        {
-            "key": b"00000000000000000000000000000000",
-            "iv": b"00000000000000000000000000000000",
-            "plaintext": b"6a118a874519e64e9963798a503f1d35",
-            "ciphertext": b"dc43be40be0e53712f7e2bf5ca707209",
-        },
-        {
-            "key": b"00000000000000000000000000000000",
-            "iv": b"00000000000000000000000000000000",
-            "plaintext": b"cb9fceec81286ca3e989bd979b0cb284",
-            "ciphertext": b"92beedab1895a94faa69b632e5cc47ce",
-        },
-        {
-            "key": b"00000000000000000000000000000000",
-            "iv": b"00000000000000000000000000000000",
-            "plaintext": b"b26aeb1874e47ca8358ff22378f09144",
-            "ciphertext": b"459264f4798f6a78bacb89c15ed3d601",
-        },
-        {
-            "key": b"00000000000000000000000000000000",
-            "iv": b"00000000000000000000000000000000",
-            "plaintext": b"58c8e00b2631686d54eab84b91f0aca1",
-            "ciphertext": b"08a4e2efec8a8e3312ca7460b9040bbf",
-        },
     ]
 
 
@@ -286,20 +142,6 @@
         load_cryptrec_vectors(vector_data)
 
 
-def test_load_cryptrec_vectors_from_file_encrypt():
-    test_set = load_cryptrec_vectors_from_file(
-        os.path.join("ciphers", "Camellia", "camellia-128-ecb.txt"),
-    )
-    assert test_set[0] == (
-        {
-            "key": b"00000000000000000000000000000000",
-            "plaintext": b"80000000000000000000000000000000",
-            "ciphertext": b"07923A39EB0A817D1C4D87BDB82D1F1C",
-        }
-    )
-    assert len(test_set) == 1280
-
-
 def test_load_openssl_vectors():
     vector_data = textwrap.dedent(
         """
@@ -351,39 +193,6 @@
     ]
 
 
-def test_load_openssl_vectors_from_file():
-    test_list = load_openssl_vectors_from_file(
-        os.path.join("ciphers", "Camellia", "camellia-ofb.txt")
-    )
-    assert len(test_list) == 24
-    assert test_list[:4] == [
-        {
-            "key": b"2B7E151628AED2A6ABF7158809CF4F3C",
-            "iv": b"000102030405060708090A0B0C0D0E0F",
-            "plaintext": b"6BC1BEE22E409F96E93D7E117393172A",
-            "ciphertext": b"14F7646187817EB586599146B82BD719",
-        },
-        {
-            "key": b"2B7E151628AED2A6ABF7158809CF4F3C",
-            "iv": b"50FE67CC996D32B6DA0937E99BAFEC60",
-            "plaintext": b"AE2D8A571E03AC9C9EB76FAC45AF8E51",
-            "ciphertext": b"25623DB569CA51E01482649977E28D84",
-        },
-        {
-            "key": b"2B7E151628AED2A6ABF7158809CF4F3C",
-            "iv": b"D9A4DADA0892239F6B8B3D7680E15674",
-            "plaintext": b"30C81C46A35CE411E5FBC1191A0A52EF",
-            "ciphertext": b"C776634A60729DC657D12B9FCA801E98",
-        },
-        {
-            "key": b"2B7E151628AED2A6ABF7158809CF4F3C",
-            "iv": b"A78819583F0308E7A6BF36B1386ABF23",
-            "plaintext": b"F69F2445DF4F9B17AD2B417BE66C3710",
-            "ciphertext": b"D776379BE0E50825E681DA1A4C980E8E",
-        },
-    ]
-
-
 def test_load_hash_vectors():
     vector_data = textwrap.dedent("""
 
@@ -442,14 +251,170 @@
         load_hash_vectors(vector_data)
 
 
-def test_load_hash_vectors_from_file():
-    test_list = load_hash_vectors_from_file(
-        os.path.join("hashes", "MD5", "rfc-1321.txt")
+def test_load_vectors_from_file():
+    vectors = load_vectors_from_file(
+        os.path.join("ciphers", "Blowfish", "bf-cfb.txt"),
+        load_nist_vectors,
     )
-    assert len(test_list) == 7
-    assert test_list[:4] == [
-        (b"", "d41d8cd98f00b204e9800998ecf8427e"),
-        (b"61", "0cc175b9c0f1b6a831c399e269772661"),
-        (b"616263", "900150983cd24fb0d6963f7d28e17f72"),
-        (b"6d65737361676520646967657374", "f96b697d7cb7938d525a2f31aaf161d0"),
+    assert vectors == [
+        {
+            "key": b"0123456789ABCDEFF0E1D2C3B4A59687",
+            "iv": b"FEDCBA9876543210",
+            "plaintext": (
+                b"37363534333231204E6F77206973207468652074696D6520666F722000"
+            ),
+            "ciphertext": (
+                b"E73214A2822139CAF26ECF6D2EB9E76E3DA3DE04D1517200519D57A6C3"
+            ),
+        }
+    ]
+
+
+def test_load_nist_gcm_vectors():
+    vector_data = textwrap.dedent("""
+        [Keylen = 128]
+        [IVlen = 96]
+        [PTlen = 0]
+        [AADlen = 0]
+        [Taglen = 128]
+
+        Count = 0
+        Key = 11754cd72aec309bf52f7687212e8957
+        IV = 3c819d9a9bed087615030b65
+        PT =
+        AAD =
+        CT =
+        Tag = 250327c674aaf477aef2675748cf6971
+
+        Count = 1
+        Key = 272f16edb81a7abbea887357a58c1917
+        IV = 794ec588176c703d3d2a7a07
+        PT =
+        AAD =
+        CT =
+        Tag = b6e6f197168f5049aeda32dafbdaeb
+
+        Count = 2
+        Key = a49a5e26a2f8cb63d05546c2a62f5343
+        IV = 907763b19b9b4ab6bd4f0281
+        CT =
+        AAD =
+        Tag = a2be08210d8c470a8df6e8fbd79ec5cf
+        FAIL
+
+        Count = 3
+        Key = 5c1155084cc0ede76b3bc22e9f7574ef
+        IV = 9549e4ba69a61cad7856efc1
+        PT = d1448fa852b84408e2dad8381f363de7
+        AAD = e98e9d9c618e46fef32660976f854ee3
+        CT = f78b60ca125218493bea1c50a2e12ef4
+        Tag = d72da7f5c6cf0bca7242c71835809449
+
+        [Keylen = 128]
+        [IVlen = 96]
+        [PTlen = 0]
+        [AADlen = 0]
+        [Taglen = 120]
+
+        Count = 0
+        Key = eac258e99c55e6ae8ef1da26640613d7
+        IV = 4e8df20faaf2c8eebe922902
+        CT =
+        AAD =
+        Tag = e39aeaebe86aa309a4d062d6274339
+        PT =
+
+        Count = 1
+        Key = 3726cf02fcc6b8639a5497652c94350d
+        IV = 55fef82cde693ce76efcc193
+        CT =
+        AAD =
+        Tag = 3d68111a81ed22d2ef5bccac4fc27f
+        FAIL
+
+        Count = 2
+        Key = f202299d5fd74f03b12d2119a6c4c038
+        IV = eec51e7958c3f20a1bb71815
+        CT =
+        AAD =
+        Tag = a81886b3fb26e51fca87b267e1e157
+        FAIL
+
+        Count = 3
+        Key = fd52925f39546b4c55ffb6b20c59898c
+        IV = f5cf3227444afd905a5f6dba
+        CT =
+        AAD =
+        Tag = 1665b0f1a0b456e1664cfd3de08ccd
+        PT =
+
+        [Keylen = 128]
+        [IVlen = 8]
+        [PTlen = 104]
+        [AADlen = 0]
+        [Taglen = 128]
+
+        Count = 0
+        Key = 58fab7632bcf10d2bcee58520bf37414
+        IV = 3c
+        CT = 15c4db4cbb451211179d57017f
+        AAD =
+        Tag = eae841d4355feeb3f786bc86625f1e5b
+        FAIL
+    """).splitlines()
+    assert load_nist_vectors(vector_data) == [
+        {'aad': b'',
+         'pt': b'',
+         'iv': b'3c819d9a9bed087615030b65',
+         'tag': b'250327c674aaf477aef2675748cf6971',
+         'key': b'11754cd72aec309bf52f7687212e8957',
+         'ct': b''},
+        {'aad': b'',
+         'pt': b'',
+         'iv': b'794ec588176c703d3d2a7a07',
+         'tag': b'b6e6f197168f5049aeda32dafbdaeb',
+         'key': b'272f16edb81a7abbea887357a58c1917',
+         'ct': b''},
+        {'aad': b'',
+         'iv': b'907763b19b9b4ab6bd4f0281',
+         'tag': b'a2be08210d8c470a8df6e8fbd79ec5cf',
+         'key': b'a49a5e26a2f8cb63d05546c2a62f5343',
+         'ct': b'',
+         'fail': True},
+        {'aad': b'e98e9d9c618e46fef32660976f854ee3',
+         'pt': b'd1448fa852b84408e2dad8381f363de7',
+         'iv': b'9549e4ba69a61cad7856efc1',
+         'tag': b'd72da7f5c6cf0bca7242c71835809449',
+         'key': b'5c1155084cc0ede76b3bc22e9f7574ef',
+         'ct': b'f78b60ca125218493bea1c50a2e12ef4'},
+        {'aad': b'',
+         'pt': b'',
+         'iv': b'4e8df20faaf2c8eebe922902',
+         'tag': b'e39aeaebe86aa309a4d062d6274339',
+         'key': b'eac258e99c55e6ae8ef1da26640613d7',
+         'ct': b''},
+        {'aad': b'',
+         'iv': b'55fef82cde693ce76efcc193',
+         'tag': b'3d68111a81ed22d2ef5bccac4fc27f',
+         'key': b'3726cf02fcc6b8639a5497652c94350d',
+         'ct': b'',
+         'fail': True},
+        {'aad': b'',
+         'iv': b'eec51e7958c3f20a1bb71815',
+         'tag': b'a81886b3fb26e51fca87b267e1e157',
+         'key': b'f202299d5fd74f03b12d2119a6c4c038',
+         'ct': b'',
+         'fail': True},
+        {'aad': b'',
+         'pt': b'',
+         'iv': b'f5cf3227444afd905a5f6dba',
+         'tag': b'1665b0f1a0b456e1664cfd3de08ccd',
+         'key': b'fd52925f39546b4c55ffb6b20c59898c',
+         'ct': b''},
+        {'aad': b'',
+         'iv': b'3c',
+         'tag': b'eae841d4355feeb3f786bc86625f1e5b',
+         'key': b'58fab7632bcf10d2bcee58520bf37414',
+         'ct': b'15c4db4cbb451211179d57017f',
+         'fail': True},
     ]
diff --git a/tests/utils.py b/tests/utils.py
index 99ba2e2..94f97d5 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -14,58 +14,44 @@
 import os.path
 
 
-def load_nist_vectors(vector_data, op):
-    section, count, data = None, None, {}
-
-    for line in vector_data:
-        line = line.strip()
-
-        # Blank lines are ignored
-        if not line:
-            continue
-
-        # Lines starting with # are comments
-        if line.startswith("#"):
-            continue
-
-        # Look for section headers
-        if line.startswith("[") and line.endswith("]"):
-            section = line[1:-1]
-            data[section] = {}
-            continue
-
-        # Build our data using a simple Key = Value format
-        name, value = line.split(" = ")
-
-        # COUNT is a special token that indicates a new block of data
-        if name.upper() == "COUNT":
-            count = value
-            data[section][count] = {}
-        # For all other tokens we simply want the name, value stored in
-        # the dictionary
-        else:
-            data[section][count][name.lower()] = value.encode("ascii")
-
-    # We want to test only for a particular operation, we sort them for the
-    # benefit of the tests of this function.
-    return [v for k, v in sorted(data[op].items(), key=lambda kv: kv[0])]
-
-
-def load_nist_vectors_from_file(filename, op):
+def load_vectors_from_file(filename, loader):
     base = os.path.join(
         os.path.dirname(__file__), "hazmat", "primitives", "vectors",
     )
     with open(os.path.join(base, filename), "r") as vector_file:
-        return load_nist_vectors(vector_file, op)
+        return loader(vector_file)
 
 
-def load_cryptrec_vectors_from_file(filename):
-    base = os.path.join(
-        os.path.dirname(__file__),
-        "hazmat", "primitives", "vectors",
-    )
-    with open(os.path.join(base, filename), "r") as vector_file:
-        return load_cryptrec_vectors(vector_file)
+def load_nist_vectors(vector_data):
+    test_data = None
+    data = []
+
+    for line in vector_data:
+        line = line.strip()
+
+        # Blank lines, comments, and section headers are ignored
+        if not line or line.startswith("#") or (line.startswith("[")
+                                                and line.endswith("]")):
+            continue
+
+        if line.strip() == "FAIL":
+            test_data["fail"] = True
+            continue
+
+        # Build our data using a simple Key = Value format
+        name, value = [c.strip() for c in line.split("=")]
+
+        # COUNT is a special token that indicates a new block of data
+        if name.upper() == "COUNT":
+            test_data = {}
+            data.append(test_data)
+            continue
+        # For all other tokens we simply want the name, value stored in
+        # the dictionary
+        else:
+            test_data[name.lower()] = value.encode("ascii")
+
+    return data
 
 
 def load_cryptrec_vectors(vector_data):
@@ -96,15 +82,6 @@
     return cryptrec_list
 
 
-def load_openssl_vectors_from_file(filename):
-    base = os.path.join(
-        os.path.dirname(__file__),
-        "hazmat", "primitives", "vectors",
-    )
-    with open(os.path.join(base, filename), "r") as vector_file:
-        return load_openssl_vectors(vector_file)
-
-
 def load_openssl_vectors(vector_data):
     vectors = []
 
@@ -166,11 +143,3 @@
         else:
             raise ValueError("Unknown line in hash vector")
     return vectors
-
-
-def load_hash_vectors_from_file(filename):
-    base = os.path.join(
-        os.path.dirname(__file__), "hazmat", "primitives", "vectors"
-    )
-    with open(os.path.join(base, filename), "r") as vector_file:
-        return load_hash_vectors(vector_file)
diff --git a/tox.ini b/tox.ini
index 257275c..56603da 100644
--- a/tox.ini
+++ b/tox.ini
@@ -7,7 +7,7 @@
     coverage
     pretend
 commands =
-    coverage run --source=cryptography/,tests/ -m pytest
+    coverage run --source=cryptography/,tests/ -m pytest --strict
     coverage report -m
 
 [testenv:docs]