summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichał Górny <mgorny@gentoo.org>2017-10-22 16:35:43 +0200
committerMichał Górny <mgorny@gentoo.org>2017-10-22 16:35:43 +0200
commita57c8f61591d3d9041c1eab356c397b9891f5ba4 (patch)
tree7540f91240063b1096947ca7b45971b681a16e50
downloadgemato-a57c8f61591d3d9041c1eab356c397b9891f5ba4.tar.gz
Initial checkout, with a basic hash backend
-rw-r--r--.gitignore4
-rw-r--r--COPYING23
-rw-r--r--gemato/__init__.py0
-rw-r--r--gemato/hash.py59
-rw-r--r--setup.py27
-rw-r--r--tests/__init__.py0
-rw-r--r--tests/test_hash.py239
-rw-r--r--tox.ini5
8 files changed, 357 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..211b215
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+__pycache__
+*.pyc
+/.tox
+/MANIFEST
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..130fbf2
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,23 @@
+Copyright (c) 2017, Michał Górny
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/gemato/__init__.py b/gemato/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gemato/__init__.py
diff --git a/gemato/hash.py b/gemato/hash.py
new file mode 100644
index 0000000..7247ace
--- /dev/null
+++ b/gemato/hash.py
@@ -0,0 +1,59 @@
+# gemato: hash support
+# vim:fileencoding=utf-8
+# (c) 2017 Michał Górny
+# Licensed under the terms of 2-clause BSD license
+
+import hashlib
+import io
+
+
+HASH_BUFFER_SIZE = 65536
+
+
+class UnsupportedHash(Exception):
+ def __init__(self, hash_name):
+ super(UnsupportedHash, self).__init__(
+ 'Unsupported hash name: {}'.format(hash_name))
+
+
+def get_hash_by_name(name):
+ """
+ Get a hashlib-compatible hash object for hash named @name. Supports
+ multiple backends.
+ """
+ try:
+ return hashlib.new(name)
+ except ValueError:
+ raise UnsupportedHash(name)
+
+
+def hash_file(f, hash_names):
+ """
+ Hash the contents of file object @f using all hashes specified
+ as @hash_names. Returns a dict of (hash_name -> hex value) mappings.
+ """
+ hashes = {}
+ for h in hash_names:
+ hashes[h] = get_hash_by_name(h)
+ for block in iter(lambda: f.read(HASH_BUFFER_SIZE), b''):
+ for h in hashes.values():
+ h.update(block)
+ return dict((k, h.hexdigest()) for k, h in hashes.items())
+
+
+def hash_path(path, hash_names):
+ """
+ Hash the contents of file at specified path @path using all hashes
+ specified as @hash_names. Returns a dict of (hash_name -> hex value)
+ mappings.
+ """
+ with io.open(path, 'rb') as f:
+ return hash_file(f, hash_names)
+
+
+def hash_bytes(buf, hash_name):
+ """
+ Hash the data in provided buffer @buf using the hash @hash_name.
+ Returns the hex value.
+ """
+ return hash_file(io.BytesIO(buf), (hash_name,))[hash_name]
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..832d174
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,27 @@
+#!/usr/bin/python
+# vim:fileencoding=utf-8
+# (C) 2017 Michał Górny <mgorny@gentoo.org>
+# Licensed under the terms of 2-clause BSD license
+
+from distutils.core import setup
+
+
+setup(
+ name='gemato',
+ version=0,
+ author='Michał Górny',
+ author_email='mgorny@gentoo.org',
+ url='http://github.com/mgorny/gemato',
+
+ packages=['gemato'],
+
+ classifiers=[
+ 'Development Status :: 2 - Pre-Alpha',
+ 'Environment :: Console',
+ 'Intended Audience :: System Administrators',
+ 'License :: OSI Approved :: BSD License',
+ 'Operating System :: POSIX',
+ 'Programming Language :: Python',
+ 'Topic :: Security :: Cryptography',
+ ]
+)
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/__init__.py
diff --git a/tests/test_hash.py b/tests/test_hash.py
new file mode 100644
index 0000000..6a134f0
--- /dev/null
+++ b/tests/test_hash.py
@@ -0,0 +1,239 @@
+# gemato: hash support tests
+# vim:fileencoding=utf-8
+# (c) 2017 Michał Górny
+# Licensed under the terms of 2-clause BSD license
+
+import io
+import tempfile
+import unittest
+
+import gemato.hash
+
+
+TEST_STRING = b'The quick brown fox jumps over the lazy dog'
+
+
+class HashAPITest(unittest.TestCase):
+ """
+ Test basic aspects of the hash function API.
+ """
+
+ def test_get_valid(self):
+ gemato.hash.get_hash_by_name('md5')
+ gemato.hash.get_hash_by_name('sha1')
+
+ def test_get_invalid(self):
+ self.assertRaises(gemato.hash.UnsupportedHash,
+ gemato.hash.get_hash_by_name, '_invalid_name_')
+
+ def test_hash_file(self):
+ f = io.BytesIO(TEST_STRING)
+ self.assertDictEqual(gemato.hash.hash_file(f, ('md5', 'sha1', 'sha256')),
+ {
+ 'md5': '9e107d9d372bb6826bd81d3542a419d6',
+ 'sha1': '2fd4e1c67a2d28fced849ee1bb76e7391b93eb12',
+ 'sha256': 'd7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592',
+ })
+
+ def test_hash_path(self):
+ with tempfile.NamedTemporaryFile() as f:
+ f.write(TEST_STRING)
+ f.flush()
+ self.assertDictEqual(gemato.hash.hash_path(f.name, ('md5', 'sha1', 'sha256')),
+ {
+ 'md5': '9e107d9d372bb6826bd81d3542a419d6',
+ 'sha1': '2fd4e1c67a2d28fced849ee1bb76e7391b93eb12',
+ 'sha256': 'd7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592',
+ })
+
+
+class GuaranteedHashTest(unittest.TestCase):
+ """
+ Test basic operation of various hash functions. This test aims
+ mostly to make sure that we can load and run the various backend
+ routines, and that they run the correct version of the hash.
+ This set covers hash functions that are guaranteed to be provided.
+ """
+
+ def test_md5(self):
+ self.assertEqual(gemato.hash.hash_bytes(TEST_STRING, 'md5'),
+ '9e107d9d372bb6826bd81d3542a419d6')
+
+ def test_md5_empty(self):
+ self.assertEqual(gemato.hash.hash_bytes(b'', 'md5'),
+ 'd41d8cd98f00b204e9800998ecf8427e')
+
+ def test_sha1(self):
+ self.assertEqual(gemato.hash.hash_bytes(TEST_STRING, 'sha1'),
+ '2fd4e1c67a2d28fced849ee1bb76e7391b93eb12')
+
+ def test_sha1_empty(self):
+ self.assertEqual(gemato.hash.hash_bytes(b'', 'sha1'),
+ 'da39a3ee5e6b4b0d3255bfef95601890afd80709')
+
+ def test_sha224(self):
+ self.assertEqual(gemato.hash.hash_bytes(TEST_STRING, 'sha224'),
+ '730e109bd7a8a32b1cb9d9a09aa2325d2430587ddbc0c38bad911525')
+
+ def test_sha224_empty(self):
+ self.assertEqual(gemato.hash.hash_bytes(b'', 'sha224'),
+ 'd14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f')
+
+ def test_sha256(self):
+ self.assertEqual(gemato.hash.hash_bytes(TEST_STRING, 'sha256'),
+ 'd7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592')
+
+ def test_sha256_empty(self):
+ self.assertEqual(gemato.hash.hash_bytes(b'', 'sha256'),
+ 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855')
+
+ def test_sha384(self):
+ self.assertEqual(gemato.hash.hash_bytes(TEST_STRING, 'sha384'),
+ 'ca737f1014a48f4c0b6dd43cb177b0afd9e5169367544c494011e3317dbf9a509cb1e5dc1e85a941bbee3d7f2afbc9b1')
+
+ def test_sha384_empty(self):
+ self.assertEqual(gemato.hash.hash_bytes(b'', 'sha384'),
+ '38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b')
+
+ def test_sha512(self):
+ self.assertEqual(gemato.hash.hash_bytes(TEST_STRING, 'sha512'),
+ '07e547d9586f6a73f73fbac0435ed76951218fb7d0c8d788a309d785436bbb642e93a252a954f23912547d1e8a3b5ed6e1bfd7097821233fa0538f3db854fee6')
+
+ def test_sha512_empty(self):
+ self.assertEqual(gemato.hash.hash_bytes(b'', 'sha512'),
+ 'cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e')
+
+
+class OptionalHashTest(unittest.TestCase):
+ """
+ Test basic operation of various hash functions. This test aims
+ mostly to make sure that we can load and run the various backend
+ routines, and that they run the correct version of the hash.
+ This set covers hash functions that are optional.
+ """
+
+ def test_md4(self):
+ try:
+ self.assertEqual(gemato.hash.hash_bytes(TEST_STRING, 'md4'),
+ '1bee69a46ba811185c194762abaeae90')
+ except gemato.hash.UnsupportedHash:
+ raise unittest.SkipTest('hash not supported')
+
+ def test_md4_empty(self):
+ try:
+ self.assertEqual(gemato.hash.hash_bytes(b'', 'md4'),
+ '31d6cfe0d16ae931b73c59d7e0c089c0')
+ except gemato.hash.UnsupportedHash:
+ raise unittest.SkipTest('hash not supported')
+
+ def test_ripemd160(self):
+ try:
+ self.assertEqual(gemato.hash.hash_bytes(TEST_STRING, 'ripemd160'),
+ '37f332f68db77bd9d7edd4969571ad671cf9dd3b')
+ except gemato.hash.UnsupportedHash:
+ raise unittest.SkipTest('hash not supported')
+
+ def test_ripemd160_empty(self):
+ try:
+ self.assertEqual(gemato.hash.hash_bytes(b'', 'ripemd160'),
+ '9c1185a5c5e9fc54612808977ee8f548b2258d31')
+ except gemato.hash.UnsupportedHash:
+ raise unittest.SkipTest('hash not supported')
+
+ def test_blake2b(self):
+ try:
+ self.assertEqual(gemato.hash.hash_bytes(TEST_STRING, 'blake2b'),
+ 'a8add4bdddfd93e4877d2746e62817b116364a1fa7bc148d95090bc7333b3673f82401cf7aa2e4cb1ecd90296e3f14cb5413f8ed77be73045b13914cdcd6a918')
+ except gemato.hash.UnsupportedHash:
+ raise unittest.SkipTest('hash not supported')
+
+ def test_blake2b_empty(self):
+ try:
+ self.assertEqual(gemato.hash.hash_bytes(b'', 'blake2b'),
+ '786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce')
+ except gemato.hash.UnsupportedHash:
+ raise unittest.SkipTest('hash not supported')
+
+ def test_blake2s(self):
+ try:
+ self.assertEqual(gemato.hash.hash_bytes(TEST_STRING, 'blake2s'),
+ '606beeec743ccbeff6cbcdf5d5302aa855c256c29b88c8ed331ea1a6bf3c8812')
+ except gemato.hash.UnsupportedHash:
+ raise unittest.SkipTest('hash not supported')
+
+ def test_blake2s_empty(self):
+ try:
+ self.assertEqual(gemato.hash.hash_bytes(b'', 'blake2s'),
+ '69217a3079908094e11121d042354a7c1f55b6482ca1a51e1b250dfd1ed0eef9')
+ except gemato.hash.UnsupportedHash:
+ raise unittest.SkipTest('hash not supported')
+
+ def test_sha3_224(self):
+ try:
+ self.assertEqual(gemato.hash.hash_bytes(TEST_STRING, 'sha3_224'),
+ 'd15dadceaa4d5d7bb3b48f446421d542e08ad8887305e28d58335795')
+ except gemato.hash.UnsupportedHash:
+ raise unittest.SkipTest('hash not supported')
+
+ def test_sha3_224_empty(self):
+ try:
+ self.assertEqual(gemato.hash.hash_bytes(b'', 'sha3_224'),
+ '6b4e03423667dbb73b6e15454f0eb1abd4597f9a1b078e3f5b5a6bc7')
+ except gemato.hash.UnsupportedHash:
+ raise unittest.SkipTest('hash not supported')
+
+ def test_sha3_256(self):
+ try:
+ self.assertEqual(gemato.hash.hash_bytes(TEST_STRING, 'sha3_256'),
+ '69070dda01975c8c120c3aada1b282394e7f032fa9cf32f4cb2259a0897dfc04')
+ except gemato.hash.UnsupportedHash:
+ raise unittest.SkipTest('hash not supported')
+
+ def test_sha3_256_empty(self):
+ try:
+ self.assertEqual(gemato.hash.hash_bytes(b'', 'sha3_256'),
+ 'a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a')
+ except gemato.hash.UnsupportedHash:
+ raise unittest.SkipTest('hash not supported')
+
+ def test_sha3_384(self):
+ try:
+ self.assertEqual(gemato.hash.hash_bytes(TEST_STRING, 'sha3_384'),
+ '7063465e08a93bce31cd89d2e3ca8f602498696e253592ed26f07bf7e703cf328581e1471a7ba7ab119b1a9ebdf8be41')
+ except gemato.hash.UnsupportedHash:
+ raise unittest.SkipTest('hash not supported')
+
+ def test_sha3_384_empty(self):
+ try:
+ self.assertEqual(gemato.hash.hash_bytes(b'', 'sha3_384'),
+ '0c63a75b845e4f7d01107d852e4c2485c51a50aaaa94fc61995e71bbee983a2ac3713831264adb47fb6bd1e058d5f004')
+ except gemato.hash.UnsupportedHash:
+ raise unittest.SkipTest('hash not supported')
+
+ def test_sha3_512(self):
+ try:
+ self.assertEqual(gemato.hash.hash_bytes(TEST_STRING, 'sha3_512'),
+ '01dedd5de4ef14642445ba5f5b97c15e47b9ad931326e4b0727cd94cefc44fff23f07bf543139939b49128caf436dc1bdee54fcb24023a08d9403f9b4bf0d450')
+ except gemato.hash.UnsupportedHash:
+ raise unittest.SkipTest('hash not supported')
+
+ def test_sha3_512_empty(self):
+ try:
+ self.assertEqual(gemato.hash.hash_bytes(b'', 'sha3_512'),
+ 'a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26')
+ except gemato.hash.UnsupportedHash:
+ raise unittest.SkipTest('hash not supported')
+
+ def test_whirlpool(self):
+ try:
+ self.assertEqual(gemato.hash.hash_bytes(TEST_STRING, 'whirlpool'),
+ 'b97de512e91e3828b40d2b0fdce9ceb3c4a71f9bea8d88e75c4fa854df36725fd2b52eb6544edcacd6f8beddfea403cb55ae31f03ad62a5ef54e42ee82c3fb35')
+ except gemato.hash.UnsupportedHash:
+ raise unittest.SkipTest('hash not supported')
+
+ def test_whirlpool_empty(self):
+ try:
+ self.assertEqual(gemato.hash.hash_bytes(b'', 'whirlpool'),
+ '19fa61d75522a4669b44e39c1d2e1726c530232130d407f89afee0964997f7a73e83be698b288febcf88e3e03c4f0757ea8964e59b63d93708b138cc42a66eb3')
+ except gemato.hash.UnsupportedHash:
+ raise unittest.SkipTest('hash not supported')
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..c62895a
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,5 @@
+[tox]
+envlist = py27,py34,py35,py36,pypy,pypy3
+
+[testenv]
+commands = python -m unittest discover -v