Skip to content

Instantly share code, notes, and snippets.

@Sam-Belliveau
Last active July 24, 2019 22:06
Show Gist options
  • Save Sam-Belliveau/5c5a7998c52f85394f5db36df5089758 to your computer and use it in GitHub Desktop.
Save Sam-Belliveau/5c5a7998c52f85394f5db36df5089758 to your computer and use it in GitHub Desktop.
Simple SHA1 Implementation in python
from numpy import uint32
from numpy import uint64
from numpy import geterr
from numpy import seterr
class sha1:
STARTING_VALUES = [
uint32(0x67452301),
uint32(0xEFCDAB89),
uint32(0x98BADCFE),
uint32(0x10325476),
uint32(0xC3D2E1F0)
]
def __init__(self, message=""):
self.h = self.STARTING_VALUES
self.message = bytearray(message)
def reset(self):
self.h = self.STARTING_VALUES.copy()
self.message = bytearray()
def update(self, message_bytes):
self.message += bytes(message_bytes)
def _digest_chunk(self, chunk):
def lrotate(input, r):
return (uint32(input) << uint32(r)) | \
(uint32(input) >> uint32(32 - r))
if len(chunk) == 64:
w = []
for i in range(0, 64, 4):
w.append(uint32(int.from_bytes(chunk[i:i+4], 'big')))
for i in range(16, 80):
w.append(lrotate(w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16], 1))
d = self.h.copy()
for i in range(0, 80):
if i < 20:
f = (d[1] & d[2]) | (~d[1] & d[3])
elif i < 40:
f = d[1] ^ d[2] ^ d[3]
elif i < 60:
f = (d[1] & d[2]) | (d[1] & d[3]) | (d[2] & d[3])
else:
f = d[1] ^ d[2] ^ d[3]
k = uint32(
[0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6][i // 20]
)
temp = lrotate(d[0], 5) + f + d[4] + k + w[i]
d[4] = d[3]
d[3] = d[2]
d[2] = lrotate(d[1], 30)
d[1] = d[0]
d[0] = temp
self.h[0] += d[0]
self.h[1] += d[1]
self.h[2] += d[2]
self.h[3] += d[3]
self.h[4] += d[4]
def digest(self):
def to_bytes_64(uint64_input):
output = bytearray()
for i in range(0, 8):
output.extend(bytearray([uint64_input & uint64(0xff)]))
uint64_input >>= uint64(8)
return bytes(reversed(output))
def to_bytes_32(uint32_arr):
output = bytearray()
for i in range(0, 4):
output.extend(bytearray([uint32_arr & uint32(0xff)]))
uint32_arr >>= uint32(8)
return bytes(reversed(output))
# Turn off overflow errors for numpy
temp_set = geterr()['over']
seterr(over='ignore')
# Get a copy of the message to digest
message = self.message.copy()
# Get message length in bits
message_len = uint64(len(self.message) * 8)
# Add set bit to end of message
self.message += bytearray([0x80])
# Add padding 0's to end of message
self.message += bytearray([0] * (64 - (len(self.message) + 8) % 64))
# Add message length to message
self.message += to_bytes_64(message_len)
# Reset H values for digest
self.h = self.STARTING_VALUES.copy()
# Digest the message in chunks
for i in range(0, len(self.message), 64):
self._digest_chunk(self.message[i:i+64])
# Revert numpy error settings
seterr(over=temp_set)
# Return digest
digest = bytearray()
for word in self.h:
digest += to_bytes_32(word)
return bytes(digest)
def hex_digest(self):
digest = self.digest()
return digest.hex()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment