Merge pull request #146 from dstufft/use-cryptography-io

Use the https://cryptography.io domain name
diff --git a/cryptography/bindings/openssl/api.py b/cryptography/bindings/openssl/api.py
index 3c2cf2e..f5e042e 100644
--- a/cryptography/bindings/openssl/api.py
+++ b/cryptography/bindings/openssl/api.py
@@ -99,10 +99,8 @@
                 self.lib.EVP_get_cipherbyname(ciphername.encode("ascii")))
 
     def create_block_cipher_context(self, cipher, mode):
-        ctx = self.ffi.new("EVP_CIPHER_CTX *")
-        res = self.lib.EVP_CIPHER_CTX_init(ctx)
-        assert res != 0
-        ctx = self.ffi.gc(ctx, self.lib.EVP_CIPHER_CTX_cleanup)
+        ctx = self.lib.EVP_CIPHER_CTX_new()
+        ctx = self.ffi.gc(ctx, self.lib.EVP_CIPHER_CTX_free)
         # TODO: compute name using a better algorithm
         ciphername = "{0}-{1}-{2}".format(
             cipher.name, cipher.key_size, mode.name
@@ -144,7 +142,7 @@
         res = self.lib.EVP_EncryptFinal_ex(ctx, buf, outlen)
         assert res != 0
         res = self.lib.EVP_CIPHER_CTX_cleanup(ctx)
-        assert res != 0
+        assert res == 1
         return self.ffi.buffer(buf)[:outlen[0]]
 
     def supports_hash(self, hash_cls):
@@ -168,6 +166,8 @@
         buf = self.ffi.new("unsigned char[]", digest_size)
         res = self.lib.EVP_DigestFinal_ex(ctx, buf, self.ffi.NULL)
         assert res != 0
+        res = self.lib.EVP_MD_CTX_cleanup(ctx)
+        assert res == 1
         return self.ffi.buffer(buf)[:digest_size]
 
     def copy_hash_context(self, ctx):
diff --git a/cryptography/bindings/openssl/evp.py b/cryptography/bindings/openssl/evp.py
index 2015990..2bb5b0f 100644
--- a/cryptography/bindings/openssl/evp.py
+++ b/cryptography/bindings/openssl/evp.py
@@ -45,6 +45,8 @@
 const EVP_CIPHER *EVP_CIPHER_CTX_cipher(const EVP_CIPHER_CTX *);
 int EVP_CIPHER_block_size(const EVP_CIPHER *);
 void EVP_CIPHER_CTX_init(EVP_CIPHER_CTX *);
+EVP_CIPHER_CTX *EVP_CIPHER_CTX_new();
+void EVP_CIPHER_CTX_free(EVP_CIPHER_CTX *);
 
 EVP_MD_CTX *EVP_MD_CTX_create();
 int EVP_MD_CTX_copy_ex(EVP_MD_CTX *, const EVP_MD_CTX *);
diff --git a/cryptography/primitives/hashes.py b/cryptography/primitives/hashes.py
index affca56..e8c1f92 100644
--- a/cryptography/primitives/hashes.py
+++ b/cryptography/primitives/hashes.py
@@ -13,20 +13,24 @@
 
 from __future__ import absolute_import, division, print_function
 
+import abc
+
 import binascii
 
+import six
+
 from cryptography.bindings import _default_api
 
 
-class BaseHash(object):
+class BaseHash(six.with_metaclass(abc.ABCMeta)):
     def __init__(self, api=None, ctx=None):
         if api is None:
             api = _default_api
         self._api = api
         self._ctx = self._api.create_hash_context(self) if ctx is None else ctx
 
-    def update(self, string):
-        self._api.update_hash_context(self._ctx, string)
+    def update(self, data):
+        self._api.update_hash_context(self._ctx, data)
 
     def copy(self):
         return self.__class__(ctx=self._copy_ctx())
@@ -76,3 +80,15 @@
     name = "ripemd160"
     digest_size = 20
     block_size = 64
+
+
+class Whirlpool(BaseHash):
+    name = "whirlpool"
+    digest_size = 64
+    block_size = 64
+
+
+class MD5(BaseHash):
+    name = "md5"
+    digest_size = 16
+    block_size = 64
diff --git a/docs/primitives/cryptographic-hashes.rst b/docs/primitives/cryptographic-hashes.rst
new file mode 100644
index 0000000..d4dde04
--- /dev/null
+++ b/docs/primitives/cryptographic-hashes.rst
@@ -0,0 +1,88 @@
+Message Digests
+===============
+
+.. class:: cryptography.primitives.hashes.BaseHash
+
+   Abstract base class that implements a common interface for all hash
+   algorithms that follow here.
+
+    .. method:: update(data)
+
+        :param bytes data: The bytes you wish to hash.
+
+    .. method:: copy()
+
+        :return: a new instance of this object with a copied internal state.
+
+    .. method:: digest()
+
+        :return bytes: The message digest as bytes.
+
+    .. method:: hexdigest()
+
+        :return str: The message digest as hex.
+
+SHA-1
+~~~~~
+
+.. attention::
+
+    NIST has deprecated SHA-1 in favor of the SHA-2 variants. New applications
+    are strongly suggested to use SHA-2 over SHA-1.
+
+.. class:: cryptography.primitives.hashes.SHA1()
+
+    SHA-1 is a cryptographic hash function standardized by NIST. It has a
+    160-bit message digest.
+
+SHA-2 Family
+~~~~~~~~~~~~
+
+.. class:: cryptography.primitives.hashes.SHA224()
+
+    SHA-224 is a cryptographic hash function from the SHA-2 family and
+    standardized by NIST. It has a 224-bit message digest.
+
+.. class:: cryptography.primitives.hashes.SHA256()
+
+    SHA-256 is a cryptographic hash function from the SHA-2 family and
+    standardized by NIST. It has a 256-bit message digest.
+
+.. class:: cryptography.primitives.hashes.SHA384()
+
+    SHA-384 is a cryptographic hash function from the SHA-2 family and
+    standardized by NIST. It has a 384-bit message digest.
+
+.. class:: cryptography.primitives.hashes.SHA512()
+
+    SHA-512 is a cryptographic hash function from the SHA-2 family and
+    standardized by NIST. It has a 512-bit message digest.
+
+RIPEMD160
+~~~~~~~~~
+
+.. class:: cryptography.primitives.hashes.RIPEMD160()
+
+    RIPEMD160 is a cryptographic hash function that is part of ISO/IEC
+    10118-3:2004. It has a 160-bit message digest.
+
+Whirlpool
+~~~~~~~~~
+
+.. class:: cryptography.primitives.hashes.Whirlpool()
+
+    Whirlpool is a cryptographic hash function that is part of ISO/IEC
+    10118-3:2004. It has a 512-bit message digest.
+
+MD5
+~~~
+
+.. warning::
+
+    MD5 is a deprecated hash algorithm that has practical known collision
+    attacks. You are strongly discouraged from using it.
+
+.. class:: cryptography.primitives.hashes.MD5()
+
+    MD5 is a deprecated cryptographic hash function. It has a 160-bit message
+    digest and has practical known collision attacks.
diff --git a/docs/primitives/index.rst b/docs/primitives/index.rst
index 1066e30..c18c62c 100644
--- a/docs/primitives/index.rst
+++ b/docs/primitives/index.rst
@@ -4,4 +4,5 @@
 .. toctree::
     :maxdepth: 1
 
+    cryptographic-hashes
     symmetric-encryption
diff --git a/tests/primitives/test_hash_vectors.py b/tests/primitives/test_hash_vectors.py
index 51c4b85..02ef4db 100644
--- a/tests/primitives/test_hash_vectors.py
+++ b/tests/primitives/test_hash_vectors.py
@@ -109,3 +109,38 @@
         only_if=lambda api: api.supports_hash(hashes.RIPEMD160),
         skip_message="Does not support RIPEMD160",
     )
+
+
+class TestWhirlpool(object):
+    test_whirlpool = generate_hash_test(
+        load_hash_vectors_from_file,
+        os.path.join("ISO", "whirlpool"),
+        [
+            "iso-test-vectors.txt",
+        ],
+        hashes.Whirlpool,
+        only_if=lambda api: api.supports_hash(hashes.Whirlpool),
+        skip_message="Does not support Whirlpool",
+    )
+
+    test_whirlpool_long_string = generate_long_string_hash_test(
+        hashes.Whirlpool,
+        ("0c99005beb57eff50a7cf005560ddf5d29057fd86b2"
+         "0bfd62deca0f1ccea4af51fc15490eddc47af32bb2b"
+         "66c34ff9ad8c6008ad677f77126953b226e4ed8b01"),
+        only_if=lambda api: api.supports_hash(hashes.Whirlpool),
+        skip_message="Does not support Whirlpool",
+    )
+
+
+class TestMD5(object):
+    test_md5 = generate_hash_test(
+        load_hash_vectors_from_file,
+        os.path.join("RFC", "MD5"),
+        [
+            "rfc-1321.txt",
+        ],
+        hashes.MD5,
+        only_if=lambda api: api.supports_hash(hashes.MD5),
+        skip_message="Does not support MD5",
+    )
diff --git a/tests/primitives/test_hashes.py b/tests/primitives/test_hashes.py
index bfb4503..901ddab 100644
--- a/tests/primitives/test_hashes.py
+++ b/tests/primitives/test_hashes.py
@@ -76,3 +76,23 @@
         only_if=lambda api: api.supports_hash(hashes.RIPEMD160),
         skip_message="Does not support RIPEMD160",
     )
+
+
+class TestWhirlpool(object):
+    test_Whirlpool = generate_base_hash_test(
+        hashes.Whirlpool,
+        digest_size=64,
+        block_size=64,
+        only_if=lambda api: api.supports_hash(hashes.Whirlpool),
+        skip_message="Does not support Whirlpool",
+    )
+
+
+class TestMD5(object):
+    test_MD5 = generate_base_hash_test(
+        hashes.MD5,
+        digest_size=16,
+        block_size=64,
+        only_if=lambda api: api.supports_hash(hashes.MD5),
+        skip_message="Does not support MD5",
+    )