#!/usr/bin/env python # coding: UTF-8 """ sha512plus.py version 1, 26-Feb-11 version 2, 01-Mar-11 - added support for Psyco This program is a demonstration of the SHA2 hashing algorithms based on the SHA-512 hashing engine. Supported are: SHA-384 SHA-512 SHA-512/224 SHA-512/256 In February 2011 NIST announced two new algorithms, SHA-512/224 and SHA-512/256. These new algorithms use the SHA-512 hashing engine (different initialization parameters and the result is truncated to the desired length) making them stronger than SHA-224 and SHA-256. http://csrc.nist.gov/groups/ST/toolkit/secure_hashing.html http://csrc.nist.gov/publications/PubsDrafts.html The purpose of this program is interoperability testing. It is NOT intended to be fast. Specifically, I wanted a version that was endian agnostic, hence this Python implementation. sha512plus.py is a "pythonized" port of the SHA-512 algorithm written in C by Tom St Denis, author of LibTomCrypt. While I hold the copyright for this implementation, I must acknowledge Tom's coding style, the architecture of his LibTomCrypt, and Tom's placing it in the public domain made my job easy. Copyright (c) 2011 by Larry Bugbee, Kent, WA, USA ALL RIGHTS RESERVED. sha512plus.py IS EXPERIMENTAL SOFTWARE FOR EDUCATIONAL PURPOSES ONLY. IT IS MADE AVAILABLE "AS-IS" WITHOUT WARRANTY OR GUARANTEE OF ANY KIND. USE SIGNIFIES ACCEPTANCE OF ALL RISK. To make your learning and experimentation less cumbersome, sha512plus.py is free for any use. Very simple timing runs were made with no special attention paid to other apps or services running on the test machine, an Apple mini Mac [upgraded from an Intel Core Solo now] running a 2.0GHz Core 2 Duo. For 1000 iterations of a short message (a single block), the times were: Python 2.7 compiled for 64 bits 1.477 sec Python 2.5 compiled for 32 bits 2.231 sec Python 2.5 with Psyco 0.983 sec This program has NOT been tested under Python 3. Enjoy, Larry Bugbee February 2011 NB: SHA-512 is defined in terms of 64-bit words; the SHA-256 definition uses 32-bit words. One would therefore expect the SHA-512-based algorithms to run a little faster on 64-bit machines. This is great for the bigger boxes, but don't rush out and embrace these new algorithms too quickly. There is a significant time and space penalty to be paid when running the SHA-512 engine on 32-bit and smaller machines. Be wise. """ import struct try: import psyco # a specializing [runtime] compiler have_psyco = True # for 32-bit compilations of Python print 'psyco enabled' except: have_psyco = False #--------------------------------------------------------------- # utility functions def eightByte2int(bytestr): """ convert a 8-byte string to an int """ return struct.unpack('!Q', bytestr)[0] def int2eightByte(num): """ convert a number to a 8-byte string, high order truncation possible """ return struct.pack('!Q', num) def rotr64(x,n): """ rotate a 64-bit word right n bits """ return (x >> n) | ((x << (64 - n)) & 0xFFFFFFFFFFFFFFFF) #--------------------------------------------------------------- # the SHA-512 hash engine class SHA512_engine(object): # this is but a base class BLKSIZE = 128 # in bytes (1024 bits) WORDSIZE = 8 # in bytes ( 64 bits) MASK64BITS = 0xFFFFFFFFFFFFFFFF # ( 64 bits) k512 = ( # K array 0x428A2F98D728AE22, 0x7137449123EF65CD, 0xB5C0FBCFEC4D3B2F, 0xE9B5DBA58189DBBC, 0x3956C25BF348B538, 0x59F111F1B605D019, 0x923F82A4AF194F9B, 0xAB1C5ED5DA6D8118, 0xD807AA98A3030242, 0x12835B0145706FBE, 0x243185BE4EE4B28C, 0x550C7DC3D5FFB4E2, 0x72BE5D74F27B896F, 0x80DEB1FE3B1696B1, 0x9BDC06A725C71235, 0xC19BF174CF692694, 0xE49B69C19EF14AD2, 0xEFBE4786384F25E3, 0x0FC19DC68B8CD5B5, 0x240CA1CC77AC9C65, 0x2DE92C6F592B0275, 0x4A7484AA6EA6E483, 0x5CB0A9DCBD41FBD4, 0x76F988DA831153B5, 0x983E5152EE66DFAB, 0xA831C66D2DB43210, 0xB00327C898FB213F, 0xBF597FC7BEEF0EE4, 0xC6E00BF33DA88FC2, 0xD5A79147930AA725, 0x06CA6351E003826F, 0x142929670A0E6E70, 0x27B70A8546D22FFC, 0x2E1B21385C26C926, 0x4D2C6DFC5AC42AED, 0x53380D139D95B3DF, 0x650A73548BAF63DE, 0x766A0ABB3C77B2A8, 0x81C2C92E47EDAEE6, 0x92722C851482353B, 0xA2BFE8A14CF10364, 0xA81A664BBC423001, 0xC24B8B70D0F89791, 0xC76C51A30654BE30, 0xD192E819D6EF5218, 0xD69906245565A910, 0xF40E35855771202A, 0x106AA07032BBD1B8, 0x19A4C116B8D2D0C8, 0x1E376C085141AB53, 0x2748774CDF8EEB99, 0x34B0BCB5E19B48A8, 0x391C0CB3C5C95A63, 0x4ED8AA4AE3418ACB, 0x5B9CCA4F7763E373, 0x682E6FF3D6B2B8A3, 0x748F82EE5DEFB2FC, 0x78A5636F43172F60, 0x84C87814A1F0AB72, 0x8CC702081A6439EC, 0x90BEFFFA23631E28, 0xA4506CEBDE82BDE9, 0xBEF9A3F7B2C67915, 0xC67178F2E372532B, 0xCA273ECEEA26619C, 0xD186B8C721C0C207, 0xEADA7DD6CDE0EB1E, 0xF57D4F7FEE6ED178, 0x06F067AA72176FBA, 0x0A637DC5A2C898A6, 0x113F9804BEF90DAE, 0x1B710B35131C471B, 0x28DB77F523047D84, 0x32CAAB7B40C72493, 0x3C9EBE0A15C9BEBC, 0x431D67C49C100D4C, 0x4CC5D4BECB3E42B6, 0x597F299CFC657E2A, 0x5FCB6FAB3AD6FAEC, 0x6C44198C4A475817, ) # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - def _init(self, digest_size): self.digest_size = digest_size self.buf = '' # a bytestring of leftover # data yet to be processed self.length = 0 # total num bits compressed # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - def process(self, data): """ Feed data to compression engine. Multiple calls permitted, and they need not be in increments of BLKSIZE. """ datalen = len(data) while datalen > 0: if (len(self.buf) == 0 and datalen >= 128): self._sha512_compress(data[:self.BLKSIZE]) self.length += self.BLKSIZE << 3 # * 8 data = data[self.BLKSIZE:] datalen -= self.BLKSIZE else: n = min(datalen, (self.BLKSIZE - len(self.buf))) self.buf = self.buf + data[:n] data = data[n:] datalen -= n if (len(self.buf) == self.BLKSIZE): self._sha512_compress(self.buf) self.length += self.BLKSIZE << 3 # * 8 return self # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - def _sha512_compress(self, buf): """ compress buf's 128 bytes (1024 bits) """ # local functions def Sigma0(x): return (rotr64(x, 28) ^ rotr64(x, 34) ^ rotr64(x, 39)) def Sigma1(x): return (rotr64(x, 14) ^ rotr64(x, 18) ^ rotr64(x, 41)) def Gamma0(x): return (rotr64(x, 1) ^ rotr64(x, 8) ^ (x >> 7)) def Gamma1(x): return (rotr64(x, 19) ^ rotr64(x, 61) ^ (x >> 6)) def Ch(x,y,z): return (z ^ (x & (y ^ z))) def Maj(x,y,z): return ((x & y) | (z & (x ^ y))) S = self.state[:] # copy state into S W = [0]*80 for i in xrange(16): # copy buf into 1024-bits into W[0..15] j = i << 3 W[i] = eightByte2int(buf[j:j+self.WORDSIZE]) for i in xrange(16, 80, 1): # fill W[16..79] W[i] = (Gamma1(W[i- 2]) + W[i- 7] + Gamma0(W[i-15]) + W[i-16]) & self.MASK64BITS # here is where we do the heavy lifting and where # performance optimization will have the largest # effect for i in xrange(80): t0 = (S[7] + Sigma1(S[4]) + Ch(S[4], S[5], S[6]) + self.k512[i] + W[i]) & self.MASK64BITS t1 = (Sigma0(S[0]) + Maj(S[0], S[1], S[2])) & self.MASK64BITS S[7] = S[6] S[6] = S[5] S[5] = S[4] S[4] = (S[3] + t0) & self.MASK64BITS S[3] = S[2] S[2] = S[1] S[1] = S[0] S[0] = (t0 + t1) & self.MASK64BITS # feedback for i in xrange(8): self.state[i] = (self.state[i] + S[i]) & self.MASK64BITS # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - def done(self): """ Terminate the hash to get the digest """ # increase the length of the message self.length += len(self.buf) << 3 # * 8 # append the '1' bit self.buf += chr(0x80) # if the length is currently above 112 bytes we append zeros # then compress. Then we can fall back to padding zeros and # length encoding like normal. # if len(self.buf) > 112: self.buf += chr(0)*(128-len(self.buf)) self._sha512_compress(self.buf) self.buf = '' # pad up to 120 bytes of zeros # note: that from 112 to 120 is the 64 MSB of the length. # We assume that you won't hash > 2^64 bits of data... :-) # self.buf += chr(0)*(120-len(self.buf)) # store length self.buf += int2eightByte(self.length) self._sha512_compress(self.buf) # return the hash value hashwords = [] for i in xrange(8): hashwords.append(int2eightByte(self.state[i])) return ''.join(hashwords)[:self.digest_size] if have_psyco: _sha512_compress = psyco.proxy(_sha512_compress) pass #--------------------------------------------------------------- class SHA512_224(SHA512_engine): SHA224_DIGEST_SIZE = 28 # in bytes (224 bits) i512_224 = [ # SHA512_224 init params 0x8C3D37C819544DA2, 0x73E1996689DCD4D6, 0x1DFAB7AE32FF9C82, 0x679DD514582F9FCF, 0x0F6D2B697BD44DA8, 0x77E36F7304C48942, 0x3F9D85A86A1D36C8, 0x1112E6AD91D692A1, ] def __init__(self): self._init(self.SHA224_DIGEST_SIZE) self.state = self.i512_224[:] # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - class SHA512_256(SHA512_engine): SHA256_DIGEST_SIZE = 32 # in bytes (256 bits) i512_256 = [ # SHA512_256 init params 0x22312194FC2BF72C, 0x9F555FA3C84C64C2, 0x2393B86B6F53B151, 0x963877195940EABD, 0x96283EE2A88EFFE3, 0xBE5E1E2553863992, 0x2B0199FC2C85B8AA, 0x0EB72DDC81C52CA2, ] def __init__(self): self._init(self.SHA256_DIGEST_SIZE) self.state = self.i512_256[:] # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - class SHA384(SHA512_engine): SHA384_DIGEST_SIZE = 48 # in bytes (384 bits) i384 = [ # SHA384 init params 0xCBBB9D5DC1059ED8, 0x629A292A367CD507, 0x9159015A3070DD17, 0x152FECD8F70E5939, 0x67332667FFC00B31, 0x8EB44A8768581511, 0xDB0C2E0D64F98FA7, 0x47B5481DBEFA4FA4, ] def __init__(self): self._init(self.SHA384_DIGEST_SIZE) self.state = self.i384[:] # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - class SHA512(SHA512_engine): SHA512_DIGEST_SIZE = 64 # in bytes (512 bits) i512 = [ # SHA512 init params 0x6A09E667F3BCC908, 0xBB67AE8584CAA73B, 0x3C6EF372FE94F82B, 0xA54FF53A5F1D36F1, 0x510E527FADE682D1, 0x9B05688C2B3E6C1F, 0x1F83D9ABFB41BD6B, 0x5BE0CD19137E2179, ] def __init__(self): self._init(self.SHA512_DIGEST_SIZE) self.state = self.i512[:] #--------------------------------------------------------------- #--------------------------------------------------------------- if __name__ == '__main__': print print ' The following test vectors are published by NIST in the' print ' document "SHA_All.pdf" and may be found at:' print ' http://csrc.nist.gov/groups/ST/toolkit/examples.html' print if 1: data = 'abc' hash = SHA512_224().process(data).done().encode('hex') print ' SHA-512/224("%s"): \n %s' % (data, hash) print hash = SHA512_256().process(data).done().encode('hex') print ' SHA-512/256("%s"): \n %s' % (data, hash) print hash = SHA384().process(data).done().encode('hex') print ' SHA-384("%s"): \n %s' % (data, hash) print hash = SHA512().process(data).done().encode('hex') print ' SHA-512("%s"): \n %s' % (data, hash) print print if 0: data = 'abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu' print ' data = "%s" \n' % data hash = SHA512_224().process(data).done().encode('hex') print ' SHA-512/224(data): \n %s' % (hash) print hash = SHA512_256().process(data).done().encode('hex') print ' SHA-512/256(data): \n %s' % (hash) print hash = SHA384().process(data).done().encode('hex') print ' SHA-384(data): \n %s' % (hash) print hash = SHA512().process(data).done().encode('hex') print ' SHA-512(data): \n %s' % (hash) print if 0: import time data = 'abc' iterations = 1000 t0 = time.time() for i in xrange(iterations): hash = SHA512_256().process(data).done().encode('hex') t1 = time.time() print print ' Time to hash up to %s iterations' % iterations, print 'of a single block of data using SHA-512/256()', print 'is: %6.3f seconds' % (t1-t0) print #--------------------------------------------------------------- #--------------------------------------------------------------- #--------------------------------------------------------------- ''' # here is an alternate encoding: (runtime is the same) def Maj(x,y,z): return (((x | y) & z) | (x & y)) # here is another alternate encoding: (runtime is the same) # here is where we do the heavy lifting and where # performance optimization will have the largest # effect def RND(a,b,c,d,e,f,g,h,i): t0 = (h + Sigma1(e) + Ch(e, f, g) + self.k512[i] + W[i] ) & self.MASK64BITS t1 = (Sigma0(a) + Maj(a, b, c)) & self.MASK64BITS d = (d + t0) & self.MASK64BITS h = (t0 + t1) & self.MASK64BITS return (a,b,c,d,e,f,g,h) for i in xrange(0, 80, 8): S[0],S[1],S[2],S[3],S[4],S[5],S[6],S[7] = RND(S[0],S[1],S[2],S[3],S[4],S[5],S[6],S[7],i+0); S[7],S[0],S[1],S[2],S[3],S[4],S[5],S[6] = RND(S[7],S[0],S[1],S[2],S[3],S[4],S[5],S[6],i+1); S[6],S[7],S[0],S[1],S[2],S[3],S[4],S[5] = RND(S[6],S[7],S[0],S[1],S[2],S[3],S[4],S[5],i+2); S[5],S[6],S[7],S[0],S[1],S[2],S[3],S[4] = RND(S[5],S[6],S[7],S[0],S[1],S[2],S[3],S[4],i+3); S[4],S[5],S[6],S[7],S[0],S[1],S[2],S[3] = RND(S[4],S[5],S[6],S[7],S[0],S[1],S[2],S[3],i+4); S[3],S[4],S[5],S[6],S[7],S[0],S[1],S[2] = RND(S[3],S[4],S[5],S[6],S[7],S[0],S[1],S[2],i+5); S[2],S[3],S[4],S[5],S[6],S[7],S[0],S[1] = RND(S[2],S[3],S[4],S[5],S[6],S[7],S[0],S[1],i+6); S[1],S[2],S[3],S[4],S[5],S[6],S[7],S[0] = RND(S[1],S[2],S[3],S[4],S[5],S[6],S[7],S[0],i+7); '''