diff options
Diffstat (limited to 'pycryptopp/test/test_sha256.py')
-rw-r--r-- | pycryptopp/test/test_sha256.py | 181 |
1 files changed, 181 insertions, 0 deletions
diff --git a/pycryptopp/test/test_sha256.py b/pycryptopp/test/test_sha256.py new file mode 100644 index 0000000..5e982dc --- /dev/null +++ b/pycryptopp/test/test_sha256.py @@ -0,0 +1,181 @@ +#!/usr/bin/env python + +import random, re + +import unittest + +from binascii import b2a_hex, a2b_hex + +global VERBOSE +VERBOSE=False + +from pycryptopp.hash import sha256 + +from pkg_resources import resource_string + +def resource_string_lines(pkgname, resname): + return split_on_newlines(resource_string(pkgname, resname)) + +from base64 import b32encode +def ab(x): # debuggery + if len(x) >= 3: + return "%s:%s" % (len(x), b32encode(x[-3:]),) + elif len(x) == 2: + return "%s:%s" % (len(x), b32encode(x[-2:]),) + elif len(x) == 1: + return "%s:%s" % (len(x), b32encode(x[-1:]),) + elif len(x) == 0: + return "%s:%s" % (len(x), "--empty--",) + +def randstr(n): + return ''.join(map(chr, map(random.randrange, [0]*n, [256]*n))) + +h0 = a2b_hex("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") +h_bd = a2b_hex("68325720aabd7c82f30f554b313d0570c95accbb7dc4b5aae11204c08ffe732b") +h_5fd4 = a2b_hex("7c4fbf484498d21b487b9d61de8914b2eadaf2698712936d47c3ada2558f6788") + +class SHA256(unittest.TestCase): + def test_digest(self): + empty_digest = sha256.SHA256().digest() + self.failUnless(isinstance(empty_digest, str)) + self.failUnlessEqual(len(empty_digest), 32) + self.failUnlessEqual(empty_digest, h0) + + def test_hexdigest(self): + empty_hexdigest = sha256.SHA256().hexdigest() + self.failUnlessEqual(a2b_hex(empty_hexdigest), h0) + test_hexdigest.todo = "Not yet implemented: SHA256.hexdigest()." + + def test_onebyte_1(self): + d = sha256.SHA256("\xbd").digest() + self.failUnlessEqual(d, h_bd) + + def test_onebyte_2(self): + s = sha256.SHA256() + s.update("\xbd") + d = s.digest() + self.failUnlessEqual(d, h_bd) + + def test_update(self): + s = sha256.SHA256("\x5f") + s.update("\xd4") + d = s.digest() + self.failUnlessEqual(d, h_5fd4) + + def test_constructor_type_check(self): + self.failUnlessRaises(TypeError, sha256.SHA256, None) + + def test_update_type_check(self): + h = sha256.SHA256() + self.failUnlessRaises(TypeError, h.update, None) + + def test_digest_twice(self): + h = sha256.SHA256() + d1 = h.digest() + self.failUnless(isinstance(d1, str)) + d2 = h.digest() + self.failUnlessEqual(d1, d2) + + def test_digest_then_update_fail(self): + h = sha256.SHA256() + h.digest() + try: + h.update("oops") + except sha256.Error, le: + self.failUnless("digest() has been called" in str(le), le) + + def test_chunksize(self): + # hashes can be computed on arbitrarily-sized chunks + problems = False + for length in range(2, 140): + s = "a"*length + expected = sha256.SHA256(s).hexdigest() + for a in range(0, length): + h = sha256.SHA256() + h.update(s[:a]) + h.update(s[a:]) + got = h.hexdigest() + if got != expected: + problems = True + print len(s[:a]), len(s[a:]), len(s), got, expected + self.failIf(problems) + + def test_recursive_different_chunksizes(self): + """ + Test that updating a hasher with various sized inputs yields + the expected answer. This is somewhat redundant with + test_chunksize(), but that's okay. This one exercises some + slightly different situations (such as finalizing a hash after + different length inputs.) This one is recursive so that there + is a single fixed result that we expect. + """ + hx = sha256.SHA256() + s = ''.join([ chr(c) for c in range(65) ]) + for i in range(0, 65): + hy = sha256.SHA256(s[:i]).digest() + hx.update(hy) + for i in range(0, 65): + hx.update(chr(0xFE)) + hx.update(s[:64]) + self.failUnlessEqual(hx.hexdigest().lower(), '5191c7841dd4e16aa454d40af924585dffc67157ffdbfd0236acddd07901629d') + + +VECTS_RE=re.compile("\nLen = ([0-9]+)\nMsg = ([0-9a-f]+)\nMD = ([0-9a-f]+)") + +# split_on_newlines() copied from pyutil.strutil +def split_on_newlines(s): + """ + Splits s on all of the three newline sequences: "\r\n", "\r", or "\n". + """ + res = [] + for x in s.split('\r\n'): + for y in x.split('\r'): + res.extend(y.split('\n')) + return res + +class SHSVectors(unittest.TestCase): + """ + All of the SHA-256 test vectors from the NIST SHS, in the files distributed + by NIST. (NIST distributes them in a .zip, but we expect them to be + unpacked and in a subdirectory named 'testvectors'). + """ + def test_short(self): + return self._test_vect(resource_string('pycryptopp', 'testvectors/SHA256ShortMsg.txt')) + + def test_long(self): + return self._test_vect(resource_string('pycryptopp', 'testvectors/SHA256LongMsg.txt')) + + def _test_vect(self, vects_str): + for mo in VECTS_RE.finditer(vects_str): + msglenbits = int(mo.group(1)) + assert msglenbits % 8 == 0 + msglen = msglenbits / 8 + msg = a2b_hex(mo.group(2))[:msglen] # The slice is necessary because NIST seems to think that "00" is a reasonable representation for the zero-length string. + assert len(msg) == msglen, (len(msg), msglen) + md = a2b_hex(mo.group(3)) + + computed_md = sha256.SHA256(msg).digest() + self.failUnlessEqual(computed_md, md) + + def test_monte(self): + inlines = resource_string_lines('pycryptopp', 'testvectors/SHA256Monte.txt') + for line in inlines: + line = line.strip() + if line[:7] == 'Seed = ': + seed = a2b_hex(line[7:]) + break + + j = 0 + for line in inlines: + line = line.strip() + if line[:8] == 'COUNT = ': + assert int(line[8:]) == j + elif line[:5] == 'MD = ': + mds = [] + mds.append(seed);mds.append(seed);mds.append(seed); + for i in range(1000): + m = mds[-3]+mds[-2]+mds[-1] + mds.append(sha256.SHA256(m).digest()) + seed = mds[-1] + self.failUnlessEqual(line[5:], b2a_hex(seed)) + j += 1 |