Skip to content

Instantly share code, notes, and snippets.

@PM2Ring
Created October 4, 2017 16:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save PM2Ring/63be1b6f43c77c3b36b19d4190400215 to your computer and use it in GitHub Desktop.
Save PM2Ring/63be1b6f43c77c3b36b19d4190400215 to your computer and use it in GitHub Desktop.
An implementation of SHA-1 in pure Python 3
#!/usr/bin/env python3
''' sha1_demo
An implementation of SHA-1 in pure Python 3
Built from the pseudocode at
https://en.wikipedia.org/wiki/SHA-1#SHA-1_pseudocode
Written by PM 2Ring 2017.10.01
'''
# SHA-1 initialization constants
h_init = 0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0
# A bitmask of 32 '1' bits, so we can do
# 32 bit unsigned arithmetic in Python
mask = 0xffffffff
def leftrotate(x, n):
''' Left rotate 32 bit integer x by n bits '''
return ((x << n) | (x >> (32 - n))) & mask
def sha1(s):
# Add padding bits and length to message
ml = len(s) << 3
d = (447 - ml) % 512
n = 1 << d
nlen = (1 + d) >> 3
s += n.to_bytes(nlen, 'big') + ml.to_bytes(8, 'big')
# Initialise the hash
h = list(h_init)
# Process the message in successive 512-bit chunks
for chunk in zip(*[iter(s)] * 64):
# Break chunk into sixteen 32-bit big-endian words
w = [int.from_bytes(u, 'big') for u in zip(*[iter(chunk)] * 4)]
# Extend the sixteen 32-bit words into eighty 32-bit words
for i in range(16, 80):
x = (w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16])
w.append(leftrotate(x, 1))
# Initialize hash value for this chunk
a, b, c, d, e = h
# Main loop
for i in range(80):
if 0 <= i <= 19:
f = (b & c) | ((b ^ mask) & d)
k = 0x5A827999
elif 20 <= i <= 39:
f = b ^ c ^ d
k = 0x6ED9EBA1
elif 40 <= i <= 59:
f = (b & c) | (b & d) | (c & d)
k = 0x8F1BBCDC
elif 60 <= i <= 79:
f = b ^ c ^ d
k = 0xCA62C1D6
a, b, c, d, e = (
(leftrotate(a, 5) + f + e + k + w[i]) & mask,
a, leftrotate(b, 30), c, d
)
# Add this chunk's hash to result so far
h = [(u + v) & mask for u, v in zip(h, (a, b, c, d, e))]
# Produce the final hash value
return b''.join([u.to_bytes(4, 'big') for u in h])
def test(numtests=1000, random_seed=None):
''' Validate sha1 against the standard version in hashlib '''
seed(random_seed)
s = b''
for i in range(numtests):
h0 = std_sha1(s).digest()
h1 = sha1(s)
assert h0 == h1, i
# Prepend a random byte to s
s = bytes([randrange(256)]) + s
if i % 100 == 0:
print(' {}'.format(i), end='\r', flush=True)
print('\nok')
def main():
''' Calculate SHA-1 hashes of all files named on the command
line, or read from stdin if no file names are given
'''
if len(sys.argv) == 1:
fname = '-'
data = sys.stdin.buffer.read()
h = sha1(data)
print('{} -'.format(h.hex()))
else:
for fname in sys.argv[1:]:
try:
data = Path(fname).read_bytes()
except IOError as e:
print(e)
else:
h = sha1(data)
print('{} {}'.format(h.hex(), fname))
if __name__ == '__main__':
import sys
from pathlib import Path
from hashlib import sha1 as std_sha1
from random import seed, randrange
main()
#test()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment