Last active
October 31, 2024 16:17
-
-
Save prokls/41e82472bd4968720d1482f81235e0ac to your computer and use it in GitHub Desktop.
Pure python3 implementation of SHA256 based on @thomdixon's pysha2
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
#!/usr/bin/python3 | |
__base__ = 'https://github.com/thomdixon/pysha2/blob/master/sha2/sha256.py' | |
__author__ = 'Lukas Prokop' | |
__license__ = 'MIT' | |
import copy | |
import struct | |
import binascii | |
F32 = 0xFFFFFFFF | |
_k = [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, | |
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, | |
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, | |
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, | |
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, | |
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, | |
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, | |
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, | |
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, | |
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, | |
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, | |
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, | |
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, | |
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, | |
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, | |
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2] | |
_h = [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, | |
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19] | |
def _pad(msglen): | |
mdi = msglen & 0x3F | |
length = struct.pack('!Q', msglen << 3) | |
if mdi < 56: | |
padlen = 55 - mdi | |
else: | |
padlen = 119 - mdi | |
return b'\x80' + (b'\x00' * padlen) + length | |
def _rotr(x, y): | |
return ((x >> y) | (x << (32 - y))) & F32 | |
def _maj(x, y, z): | |
return (x & y) ^ (x & z) ^ (y & z) | |
def _ch(x, y, z): | |
return (x & y) ^ ((~x) & z) | |
class SHA256: | |
_output_size = 8 | |
blocksize = 1 | |
block_size = 64 | |
digest_size = 32 | |
def __init__(self, m=None): | |
self._counter = 0 | |
self._cache = b'' | |
self._k = copy.deepcopy(_k) | |
self._h = copy.deepcopy(_h) | |
self.update(m) | |
def _compress(self, c): | |
w = [0] * 64 | |
w[0:16] = struct.unpack('!16L', c) | |
for i in range(16, 64): | |
s0 = _rotr(w[i-15], 7) ^ _rotr(w[i-15], 18) ^ (w[i-15] >> 3) | |
s1 = _rotr(w[i-2], 17) ^ _rotr(w[i-2], 19) ^ (w[i-2] >> 10) | |
w[i] = (w[i-16] + s0 + w[i-7] + s1) & F32 | |
a, b, c, d, e, f, g, h = self._h | |
for i in range(64): | |
s0 = _rotr(a, 2) ^ _rotr(a, 13) ^ _rotr(a, 22) | |
t2 = s0 + _maj(a, b, c) | |
s1 = _rotr(e, 6) ^ _rotr(e, 11) ^ _rotr(e, 25) | |
t1 = h + s1 + _ch(e, f, g) + self._k[i] + w[i] | |
h = g | |
g = f | |
f = e | |
e = (d + t1) & F32 | |
d = c | |
c = b | |
b = a | |
a = (t1 + t2) & F32 | |
for i, (x, y) in enumerate(zip(self._h, [a, b, c, d, e, f, g, h])): | |
self._h[i] = (x + y) & F32 | |
def update(self, m): | |
if not m: | |
return | |
self._cache += m | |
self._counter += len(m) | |
while len(self._cache) >= 64: | |
self._compress(self._cache[:64]) | |
self._cache = self._cache[64:] | |
def digest(self): | |
r = copy.deepcopy(self) | |
r.update(_pad(self._counter)) | |
data = [struct.pack('!L', i) for i in r._h[:self._output_size]] | |
return b''.join(data) | |
def hexdigest(self): | |
return binascii.hexlify(self.digest()).decode('ascii') | |
if __name__ == '__main__': | |
def check(msg, sig): | |
m = SHA256() | |
m.update(msg.encode('ascii')) | |
print(m.hexdigest() == sig) | |
tests = { | |
"": | |
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', | |
"a": | |
'ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb', | |
"abc": | |
'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad', | |
"message digest": | |
'f7846f55cf23e14eebeab5b4e1550cad5b509e3348fbc4efa3a1413d393cb650', | |
"abcdefghijklmnopqrstuvwxyz": | |
'71c480df93d6ae2f1efad1447c66c9525e316218cf51fc8d9ed832f2daf18b73', | |
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789": | |
'db4bfcbd4da0cd85a60c3c37d3fbd8805c77f15fc6b1fdfe614ee0a7c8fdb4c0', | |
("12345678901234567890123456789012345678901234567890123456789" | |
"012345678901234567890"): | |
'f371bc4a311f2b009eef952dd83ca80e2b60026c8e935592d0f9c308453c813e', | |
"00baf6626abc2df808da36a518c69f09b0d2ed0a79421ccfde4f559d2e42128b": | |
'b835e56173be2b5b7177d71bf02850dc578ac855ac60f91a108eec253bd5a543' | |
} | |
for inp, out in tests.items(): | |
check(inp, out) |
The annoying part of this code is the lack of comments.
@thomdixon Thanks for the details and the fix! I applied to last one to a new revision
@CesarCastellonE Thanks, now I was able to reproduce it!
@manuelinfosec Not sure what you mean. The algorithm and its subroutines themselves are well-documented → https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf I am not sure what you are looking for.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@daugela @prokls
This issue was brought up on my repository here: thomdixon/pysha2#6
The error is in this gist's
update
method. Iflen(m) == 64
then the update loop becomes:which simplifies to:
To fix this, you can e.g., add an
if
before storing the_cache
:or you can keep the spirit of the original and process the data as a buffer with a white loop, so that it handles the boundary correctly: