Skip to content

Commit

Permalink
Initial checkout, with a basic hash backend
Browse files Browse the repository at this point in the history
  • Loading branch information
mgorny committed Oct 22, 2017
0 parents commit a57c8f6
Show file tree
Hide file tree
Showing 8 changed files with 357 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
__pycache__
*.pyc
/.tox
/MANIFEST
23 changes: 23 additions & 0 deletions COPYING
Original file line number Diff line number Diff line change
@@ -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.
Empty file added gemato/__init__.py
Empty file.
59 changes: 59 additions & 0 deletions gemato/hash.py
Original file line number Diff line number Diff line change
@@ -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]
27 changes: 27 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/python
# vim:fileencoding=utf-8
# (C) 2017 Michał Górny <[email protected]>
# 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='[email protected]',
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',
]
)
Empty file added tests/__init__.py
Empty file.
239 changes: 239 additions & 0 deletions tests/test_hash.py
Original file line number Diff line number Diff line change
@@ -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')
5 changes: 5 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[tox]
envlist = py27,py34,py35,py36,pypy,pypy3

[testenv]
commands = python -m unittest discover -v

0 comments on commit a57c8f6

Please sign in to comment.