aboutsummaryrefslogtreecommitdiffstats
path: root/python3
diff options
context:
space:
mode:
Diffstat (limited to '')
-rwxr-xr-xpython3/sha3sum.py398
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)