Last active
July 24, 2019 22:06
-
-
Save Sam-Belliveau/5c5a7998c52f85394f5db36df5089758 to your computer and use it in GitHub Desktop.
Simple SHA1 Implementation in python
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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