Reduce the surface of the primitive hash interface.  Add more interfaces, drop direct hashlib compatibility.
diff --git a/cryptography/hazmat/primitives/hashes.py b/cryptography/hazmat/primitives/hashes.py
index 3ccb59d..97e9bf4 100644
--- a/cryptography/hazmat/primitives/hashes.py
+++ b/cryptography/hazmat/primitives/hashes.py
@@ -13,89 +13,95 @@
 
 from __future__ import absolute_import, division, print_function
 
-import abc
-
-import binascii
-
 import six
 
+from cryptography.hazmat.primitives import interfaces
 
-class BaseHash(six.with_metaclass(abc.ABCMeta)):
-    def __init__(self, data=None, backend=None, ctx=None):
+
+@interfaces.register(interfaces.HashContext)
+class Hash(object):
+    def __init__(self, algorithm, backend=None, ctx=None):
+        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)
+            self._ctx = self._backend.hashes.create_ctx(self.algorithm)
         else:
             self._ctx = None
 
-        if data is not None:
-            self.update(data)
-
     def update(self, data):
         if isinstance(data, six.text_type):
             raise TypeError("Unicode-objects must be encoded before hashing")
         self._backend.hashes.update_ctx(self._ctx, data)
 
     def copy(self):
-        return self.__class__(backend=self._backend, ctx=self._copy_ctx())
+        return self.__class__(self.algorithm, backend=self._backend,
+                              ctx=self._copy_ctx())
 
-    def digest(self):
-        return self._backend.hashes.finalize_ctx(self._copy_ctx(),
-                                                 self.digest_size)
-
-    def hexdigest(self):
-        return str(binascii.hexlify(self.digest()).decode("ascii"))
+    def finalize(self):
+        return self._backend.hashes.finalize_ctx(self._ctx,
+                                                 self.algorithm.digest_size)
 
     def _copy_ctx(self):
         return self._backend.hashes.copy_ctx(self._ctx)
 
 
-class SHA1(BaseHash):
+@interfaces.register(interfaces.HashAlgorithm)
+class SHA1(object):
     name = "sha1"
     digest_size = 20
     block_size = 64
 
 
-class SHA224(BaseHash):
+@interfaces.register(interfaces.HashAlgorithm)
+class SHA224(object):
     name = "sha224"
     digest_size = 28
     block_size = 64
 
 
-class SHA256(BaseHash):
+@interfaces.register(interfaces.HashAlgorithm)
+class SHA256(object):
     name = "sha256"
     digest_size = 32
     block_size = 64
 
 
-class SHA384(BaseHash):
+@interfaces.register(interfaces.HashAlgorithm)
+class SHA384(object):
     name = "sha384"
     digest_size = 48
     block_size = 128
 
 
-class SHA512(BaseHash):
+@interfaces.register(interfaces.HashAlgorithm)
+class SHA512(object):
     name = "sha512"
     digest_size = 64
     block_size = 128
 
 
-class RIPEMD160(BaseHash):
+@interfaces.register(interfaces.HashAlgorithm)
+class RIPEMD160(object):
     name = "ripemd160"
     digest_size = 20
     block_size = 64
 
 
-class Whirlpool(BaseHash):
+@interfaces.register(interfaces.HashAlgorithm)
+class Whirlpool(object):
     name = "whirlpool"
     digest_size = 64
     block_size = 64
 
 
-class MD5(BaseHash):
+@interfaces.register(interfaces.HashAlgorithm)
+class MD5(object):
     name = "md5"
     digest_size = 16
     block_size = 64
diff --git a/cryptography/hazmat/primitives/interfaces.py b/cryptography/hazmat/primitives/interfaces.py
index 217490f..ebf5e31 100644
--- a/cryptography/hazmat/primitives/interfaces.py
+++ b/cryptography/hazmat/primitives/interfaces.py
@@ -59,3 +59,49 @@
         """
         finalize return bytes
         """
+
+
+class HashAlgorithm(six.with_metaclass(abc.ABCMeta)):
+    @abc.abstractproperty
+    def name(self):
+        """
+        A string naming this algorithm. (ex. sha256, md5)
+        """
+
+    @abc.abstractproperty
+    def digest_size(self):
+        """
+        The size of the resulting digest in bytes.
+        """
+
+    @abc.abstractproperty
+    def block_size(self):
+        """
+        The internal block size of the hash algorithm in bytes.
+        """
+
+
+class HashContext(six.with_metaclass(abc.ABCMeta)):
+    @abc.abstractproperty
+    def algorithm(self):
+        """
+        A HashAlgorithm that will be used by this context.
+        """
+
+    @abc.abstractmethod
+    def update(self, data):
+        """
+        hash data as bytes
+        """
+
+    @abc.abstractmethod
+    def finalize(self):
+        """
+        finalize this copy of the hash and return the digest as bytes.
+        """
+
+    @abc.abstractmethod
+    def copy(self):
+        """
+        return a HashContext that is a copy of the current context.
+        """
diff --git a/tests/hazmat/primitives/test_hashes.py b/tests/hazmat/primitives/test_hashes.py
index 797fe4f..c7a18b8 100644
--- a/tests/hazmat/primitives/test_hashes.py
+++ b/tests/hazmat/primitives/test_hashes.py
@@ -27,21 +27,17 @@
 
 class TestBaseHash(object):
     def test_base_hash_reject_unicode(self, backend):
-        m = hashes.SHA1(backend=backend)
+        m = hashes.Hash(hashes.SHA1, backend=backend)
         with pytest.raises(TypeError):
             m.update(six.u("\u00FC"))
 
-    def test_base_hash_hexdigest_string_type(self, backend):
-        m = hashes.SHA1(backend=backend, data=b"")
-        assert isinstance(m.hexdigest(), str)
-
 
 class TestCopyHash(object):
     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()
-        h = hashes.SHA1(backend=pretend_backend, ctx=pretend_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
 
@@ -51,7 +47,7 @@
         """
         This test assumes the presence of SHA1 in the default backend.
         """
-        h = hashes.SHA1()
+        h = hashes.Hash(hashes.SHA1)
         assert h._backend is _default_backend
 
 
@@ -90,7 +86,7 @@
         hashes.SHA384,
         digest_size=48,
         block_size=128,
-        only_if=lambda backend: backend.hashes.supported(hashes.SHA384),
+         only_if=lambda backend: backend.hashes.supported(hashes.SHA384),
         skip_message="Does not support SHA384",
     )
 
@@ -120,7 +116,7 @@
         hashes.Whirlpool,
         digest_size=64,
         block_size=64,
-        only_if=lambda backend: backend.hashes.supported(hashes.Whirlpool),
+         only_if=lambda backend: backend.hashes.supported(hashes.Whirlpool),
         skip_message="Does not support Whirlpool",
     )
 
diff --git a/tests/hazmat/primitives/utils.py b/tests/hazmat/primitives/utils.py
index c51fef5..c02d192 100644
--- a/tests/hazmat/primitives/utils.py
+++ b/tests/hazmat/primitives/utils.py
@@ -4,6 +4,7 @@
 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.block import BlockCipher
 
@@ -70,21 +71,19 @@
         pytest.skip(skip_message)
     msg = params[0]
     md = params[1]
-    m = hash_cls(backend=backend)
+    m = hashes.Hash(hash_cls, backend=backend)
     m.update(binascii.unhexlify(msg))
-    assert m.hexdigest() == md.replace(" ", "").lower()
-    digst = hash_cls(backend=backend, data=binascii.unhexlify(msg)).hexdigest()
-    assert digst == md.replace(" ", "").lower()
+    assert m.finalize() == binascii.unhexlify(md.replace(" ", "").lower())
 
 
-def generate_base_hash_test(hash_cls, digest_size, block_size,
+def generate_base_hash_test(algorithm, digest_size, block_size,
                             only_if=None, skip_message=None):
     def test_base_hash(self):
         for backend in _ALL_BACKENDS:
             yield (
                 base_hash_test,
                 backend,
-                hash_cls,
+                algorithm,
                 digest_size,
                 block_size,
                 only_if,
@@ -93,13 +92,14 @@
     return test_base_hash
 
 
-def base_hash_test(backend, digestmod, digest_size, block_size, only_if,
+def base_hash_test(backend, algorithm, digest_size, block_size, only_if,
                    skip_message):
     if only_if is not None and not only_if(backend):
         pytest.skip(skip_message)
-    m = digestmod(backend=backend)
-    assert m.digest_size == digest_size
-    assert m.block_size == block_size
+
+    m = hashes.Hash(algorithm, backend=backend)
+    assert m.algorithm.digest_size == digest_size
+    assert m.algorithm.block_size == block_size
     m_copy = m.copy()
     assert m != m_copy
     assert m._ctx != m_copy._ctx
@@ -120,12 +120,12 @@
     return test_long_string_hash
 
 
-def long_string_hash_test(backend, hash_factory, md, only_if, skip_message):
+def long_string_hash_test(backend, algorithm, md, only_if, skip_message):
     if only_if is not None and not only_if(backend):
         pytest.skip(skip_message)
-    m = hash_factory(backend=backend)
+    m = hashes.Hash(algorithm, backend=backend)
     m.update(b"a" * 1000000)
-    assert m.hexdigest() == md.lower()
+    assert m.finalize() == binascii.unhexlify(md.lower())
 
 
 def generate_hmac_test(param_loader, path, file_names, digestmod,