diff options
Diffstat (limited to 'python3')
-rwxr-xr-x | python3/sha3sum.py | 398 |
1 files changed, 327 insertions, 71 deletions
diff --git a/python3/sha3sum.py b/python3/sha3sum.py index ae863c2..32b09d1 100755 --- a/python3/sha3sum.py +++ b/python3/sha3sum.py @@ -23,6 +23,11 @@ import sys import os +def printerr(text, end = '\n'): + sys.stderr.buffer.write((text + end).encode('utf-8')) + sys.stderr.buffer.flush() + + class SHA3: ''' SHA-3/Keccak hash algorithm implementation @@ -372,12 +377,16 @@ class SHA3: @staticmethod - def update(msg): + def update(msg, msglen = None): ''' Absorb the more of the message message to the Keccak sponge - @param msg:bytes The partial message + @param msg:bytes The partial message + @param msglen:int The length of the partial message ''' + if msglen is not None: + msg = msg[:msglen] + rr = SHA3.r >> 3 ww = SHA3.w >> 3 @@ -449,19 +458,24 @@ class SHA3: @staticmethod - def digest(msg = None): + def digest(msg = None, msglen = None, withReturn = None): ''' Absorb the last part of the message and squeeze the Keccak sponge - @param msg:bytes The rest of the message + @param msg:bytes? The rest of the message + @param msglen:int The length of the partial message + @param withReturn:bool Whether to return the hash instead of just do a quick squeeze phrase and return `None` + @return :bytes? The hash sum, or `None` if `withReturn` is `False` ''' + if (msglen is not None) and isinstance(msglen, bool): + (msglen, withReturn) = (withReturn, msglen) if msg is None: msg = bytes([]) + elif msglen is not None: + msg = msg[:msglen] message = SHA3.pad10star1(SHA3.M + msg, SHA3.r) SHA3.M = None nnn = len(message) - rc = [0] * ((SHA3.n + 7) >> 3) - ptr = 0 rr = SHA3.r >> 3 nn = (SHA3.n + 7) >> 3 @@ -528,16 +542,86 @@ class SHA3: SHA3.keccakF(SHA3.S) # Squeezing phase + if withReturn: + rc = [0] * ((SHA3.n + 7) >> 3) + ptr = 0 + + olen = SHA3.n + j = 0 + ni = min(25, rr) + while olen > 0: + i = 0 + while (i < ni) and (j < nn): + v = SHA3.S[(i % 5) * 5 + i // 5] + for _ in range(ww): + if j < nn: + rc[ptr] = v & 255 + ptr += 1 + v >>= 8 + j += 1 + i += 1 + olen -= SHA3.r + if olen > 0: + SHA3.keccakF(SHA3.S) + if (SHA3.n & 7) != 0: + rc[len(rc) - 1] &= (1 << (SHA3.n & 7)) - 1 + + return bytes(rc) + + olen = SHA3.n + while olen > SHA3.r: + olen -= SHA3.r + SHA3.keccakF(SHA3.S) + return None + + + def simpleSqueeze(times = 1): + ''' + Force some rounds of Keccak-f + + @param times:int The number of rounds + ''' + for i in range(times): + SHA3.keccakF(SHA3.S) + + + def fastSqueeze(times = 1): + ''' + Squeeze as much as is needed to get a digest a number of times + + @param times:int The number of digests + ''' + for i in range(times): + SHA3.keccakF(SHA3.S) # Last squeeze did not do a ending squeeze + int olen = SHA3.n + while olen > SHA3.r: + olen -= SHA3.r + SHA3.keccakF(SHA3.S) + + + def squeeze(): + ''' + Squeeze out another digest + + @return :bytes The hash sum + ''' + SHA3.keccakF(SHA3.S) # Last squeeze did not do a ending squeeze + + nn = (SHA3.n + 7) >> 3 + ww = SHA3.w >> 3 + rc = [0] * nn olen = SHA3.n j = 0 - ni = min(25, rr) - while (olen > 0): + ptr = 0 + ni = min(25, SHA3.r >> 3) + + while olen > 0: i = 0 while (i < ni) and (j < nn): v = SHA3.S[(i % 5) * 5 + i // 5] for _ in range(ww): - if (j < nn): - rc[ptr] = v & 255 + if j < nn: + rc[ptr] = v ptr += 1 v >>= 8 j += 1 @@ -545,11 +629,12 @@ class SHA3: olen -= SHA3.r if olen > 0: SHA3.keccakF(SHA3.S) + if (SHA3.n & 7) != 0: rc[len(rc) - 1] &= (1 << (SHA3.n & 7)) - 1 return bytes(rc) - + if __name__ == '__main__': @@ -560,19 +645,20 @@ if __name__ == '__main__': if cmd.endswith('.py'): cmd = cmd[:-3] - o = 512 # --outputsize - if cmd == 'sha3-224sum': o = 224 - elif cmd == 'sha3-256sum': o = 256 - elif cmd == 'sha3-384sum': o = 384 - elif cmd == 'sha3-512sum': o = 512 - s = 1600 # --statesize - r = s - (o << 1) # --bitrate - c = s - r # --capacity - w = s // 25 # --wordsize - i = 1 # --iterations - binary = False - - (_r, _c, _w, _o, _s, _i) = (r, c, w, o, s, i) + (O, S, R, C, W, I, J) = (None, None, None, None, None, None, None) + (o, s, r, c, w, i, j) = (0, 0, 0, 0, 0, 0, 0) + _o = 512 # --outputsize + if cmd == 'sha3-224sum': _o = 224 + elif cmd == 'sha3-256sum': _o = 256 + elif cmd == 'sha3-384sum': _o = 384 + elif cmd == 'sha3-512sum': _o = 512 + _s = 1600 # --statesize + _r = _s - (_o << 1) # --bitrate + _c = _s - _r # --capacity + _w = _s / 25 # --wordsize + _i = 1 # --iterations + _j = 1 # --squeezes + (binary, hex, multi) = (False, False, 0) files = [] dashed = False @@ -606,9 +692,18 @@ OPTIONS: -i ITERATIONS --iterations The number of hash iterations to run. (default: %d) - + + -j SQUEEZES + --squeezes The number of hash squeezes to run. (default: %d) + + -h + --hex Read the input in hexadecimal, rather than binary. + -b --binary Print the checksum in binary, rather than hexadecimal. + + -m + --multi Print the chechsum at all iterations. COPYRIGHT: @@ -628,7 +723,7 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. -''' % (_r, _c, _w, _o, _s, _i)).encode('utf-8')) +''' % (_r, _c, _w, _o, _s, _i, _j)).encode('utf-8')) sys.stderr.buffer.flush() exit(2) else: @@ -636,26 +731,22 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. linger[1] = arg arg = None if linger[0] in ('-r', '--bitrate'): - r = int(linger[1]) - o = (s - r) >> 1 + R = int(linger[1]) elif linger[0] in ('-c', '--capacity'): - c = int(linger[1]) - r = s - c + C = int(linger[1]) elif linger[0] in ('-w', '--wordsize'): - w = int(linger[1]) - s = w * 25 + W = int(linger[1]) elif linger[0] in ('-o', '--outputsize'): - o = int(linger[1]) - r = s - (o << 1) + O = int(linger[1]) elif linger[0] in ('-s', '--statesize'): - s = int(linger[1]) - r = s - (o << 1) + S = int(linger[1]) elif linger[0] in ('-i', '--iterations'): - i = int(linger[1]) + I = int(linger[1]) + elif linger[0] in ('-j', '--squeezes'): + J = int(linger[1]) else: - sys.stderr.buffer.write((sys.argv[0] + ': unrecognised option: ' + linger[0] + '\n').encode('utf-8')) - sys.stdout.buffer.flush() - exit(1) + printerr(sys.argv[0] + ': unrecognised option: ' + linger[0]) + sys.exit(1) linger = None if arg is None: continue @@ -673,6 +764,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. else: if arg == '--binary': binary = True + elif arg == '--multi': + multi += 1 + elif arg == '--hex': + hex = True else: linger = [arg, None] elif arg.startswith('-'): @@ -680,6 +775,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. if arg[0] == 'b': binary = True arg = arg[1:] + elif arg[0] == 'b': + multi += 1 + arg = arg[1:] + elif arg[0] == 'h': + hex = True + arg = arg[1:] elif len(arg) == 1: linger = ['-' + arg, None] else: @@ -687,12 +788,95 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. else: files.append(arg) + + i = _i if I is None else I + j = _j if J is None else J + + + if S is not None: + s = S + if ((s <= 0) or (s > 1600) or (s % 25 != 0)): + printerr(cmd + ': the state size must be a positive multiple of 25 and is limited to 1600.') + sys.exit(6) + + if W is not None: + w = W + if (w <= 0) or (w > 64): + printerr(cmd + ': the word size must be positive and is limited to 64.') + sys.exit(6) + if (S is not None) and (s != w * 25): + printerr(cmd + ': the state size must be 25 times of the word size.') + sys.exit(6) + elif S is None: + S = w * 25 + + if C is not None: + c = C + if (c <= 0) or ((c & 7) != 0): + printerr(cmd + ': the capacity must be a positive multiple of 8.') + sys.exit(6); + + if R is not None: + r = R + if (r <= 0) or ((r & 7) != 0): + printerr(cmd + ': the bitrate must be a positive multiple of 8.') + sys.exit(6); + + if O is not None: + o = O + if o <= 0: + printerr(cmd + ': the output size must be positive.') + sys.exit(6); + + + if (R is None) and (C is None) and (O is None): ## s? + r = -((c = (o = ((((s = S is None ? _s : s) << 5) / 100 + 7) >> 3) << 3) << 1) - s); + o = 8 if o < 8 else o + elif (R is None) and (C is None): ## !o s? + r = _r + c = _c + s = (r + c) if S is None else s + elif R is None: ## !c o? s? + s = _s if S is None else s + r = s - c; + o = (8 if c == 8 else (c << 1)) if O is None else o; + elif C is None: ## !r o? s? + s = _s if S is None else s + c = s - r; + o = (8 if c == 8 else (c << 1)) if O is None else o; + else: ## !r !c o? s? + s = (r + c) if S is None else s + o = (8 if c == 8 else (c << 1)) if O is None else o + + + if r > s: + printerr(cmd + ': the bitrate must not be higher than the state size.'); + sys.exit(6); + if c > s: + printerr(cmd + ': the capacity must not be higher than the state size.'); + sys.exit(6); + if r + c != s: + printerr(cmd + ': the sum of the bitrate and the capacity must equal the state size.'); + sys.exit(6); + + + printerr('Bitrate: ' % r) + printerr('Capacity: ' % c) + printerr('Word size: ' % w) + printerr('State size: ' % s) + printerr('Output size: ' % o) + printerr('Iterations: ' % i) + printerr('Squeezes: ' % j) + + if len(files) == 0: files.append(None) if i < 1: - sys.stdout.buffer.write((sys.argv[0] + ': sorry, I will only do at least one iteration!\n').encode('utf-8')) - sys.stdout.buffer.flush() - exit(3) + printerr(cmd + ': sorry, I will only do at least one hash iteration!\n') + sys.exit(3) + if j < 1: + printerr(cmd + ': sorry, I will only do at least one squeeze iteration!\n') + sys.exit(3) stdin = None for filename in files: if (filename is None) and (stdin is not None): @@ -700,35 +884,107 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. continue rc = '' fn = '/dev/stdin' if filename is None else filename + fail = False with open(fn, 'rb') as file: - SHA3.initialise(r, c, o) - blksize = (o + 7) >> 3 try: - blksize = os.stat(os.path.realpath(fn)).st_blksize - except: - pass - while True: - chunk = file.read(blksize) - if len(chunk) == 0: - break - SHA3.update(chunk) - bs = SHA3.digest(file.read()) - for _ in range(1, i): - SHA3.initialise(r, c, o) - bs = SHA3.digest(bs) - if binary: - if filename is None: - stdin = bs - sys.stdout.buffer.write(bs) - sys.stdout.buffer.flush() - else: - for b in bs: - rc += "0123456789ABCDEF"[b >> 4] - rc += "0123456789ABCDEF"[b & 15] - rc += ' ' + ('-' if filename is None else filename) + '\n' - if filename is None: - stdin = rc - sys.stdout.buffer.write(rc.encode('UTF-8')) + if (filename is not None) or (stdin is None): + SHA3.initialise(r, c, o) + blksize = 4096 + try: + blksize = os.stat(os.path.realpath(fn)).st_blksize + except: + pass + while True: + chunk = file.read(blksize) + if len(chunk) == 0: + break + if not hex: + SHA3.update(chunk) + else: + chunk = list(chunk) + n = len(chunk) >> 1 + for _ in range(n): + (a, b) = (chunk[_ << 1], chunk[(_ << 1 | 1)]) + a = ((a & 15) + (0 if a <= '9' else 9)) << 4 + b = (b & 15) + (0 if b <= '9' else 0) + chunk[_] = a | b + SHA3.update(chunk, n) + bs = SHA3.digest(j == 1) + if j > 2: + SHA3.fastSqueeze(j - 2) + if j > 1: + bs = SHA3.squeeze(); + if filename is None: + stdin = bs + else: + bs = stdin + if multi == 0: + for _ in range(n - 1): + SHA3.initialise(r, c, o) + bs = SHA3.digest(bs, j == 1) + if j > 2: + SHA3.fastSqueeze(j - 2) + if j > 1: + bs = SHA3.squeeze(); + if binary: + sys.stdout.buffer.write(bs) + else: + for b in bs: + rc += "0123456789ABCDEF"[b >> 4] + rc += "0123456789ABCDEF"[b & 15] + rc += ' ' + ('-' if filename is None else filename) + '\n' + sys.stdout.buffer.write(rc.encode('utf-8')) + elif multi == 1: + if binary: + sys.stdout.buffer.write(bs) + else: + for b in bs: + rc += "0123456789ABCDEF"[b >> 4] + rc += "0123456789ABCDEF"[b & 15] + rc += '\n' + sys.stdout.buffer.write(rc.encode('UTF-8')) + for _ in range(i - 1): + SHA3.initialise(r, c, o) + bs = SHA3.digest(bs, j == 1) + if j > 2: + SHA3.fastSqueeze(j - 2) + if j > 1: + bs = SHA3.squeeze(); + if binary: + sys.stdout.buffer.write(bs); + else: + rc = '' + for b in bs: + rc += "0123456789ABCDEF"[b >> 4] + rc += "0123456789ABCDEF"[b & 15] + rc += '\n' + sys.stdout.buffer.write(rc.encode('UTF-8')) + else: + got = set() + loop = None + for _ in range(i): + if _ > 0: + pass + rc = '' + for b in bs: + rc += "0123456789ABCDEF"[b >> 4] + rc += "0123456789ABCDEF"[b & 15] + if loop is None: + if rc in got: + loop = rc + else: + got.add(rc) + if loop == rc: + rc = '\033[31m%s\033[00m' % rc; + sys.stdout.buffer.write(rc.encode('utf-8')) + sys.stdout.buffer.flush() + if loop is not None: + printerr('\033[01;31mLoop found\033[00m') sys.stdout.buffer.flush() - + except Exception as err: + printerr(cmd + ': connot read file: ' + filename + ': ' + str(err)) + fail = True + sys.stdout.buffer.flush() + if fail: + sys.exit(5) |