-
-
Save prokls/41e82472bd4968720d1482f81235e0ac to your computer and use it in GitHub Desktop.
| #!/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) |
Hey. Previous comment is correct. I am not yet sure whats the flaw here - but it is the same as with @thomdixon's pysha2 implementation (your gist is based on). If you add one more (64th) character or more to your inp byte string and run the same testcase again - it will unfortunately fail...
This issue was brought up on my repository here: thomdixon/pysha2#6
The error is in this gist's update method. If len(m) == 64 then the update loop becomes:
for i in range(0, 64 // 64):
self._compress(m[64 * i:64 * (i + 1)])
self._cache = m[-64 % 64):]which simplifies to:
self._compress(m[0:64])
# this will cause the finalization (digest() method) to include the previous message,
# because we set m = self._cache + m in the update() method
self._cache = m[0:] To fix this, you can e.g., add an if before storing the _cache:
if len(m) % 64:
self._cache = m[-(len(m) % 64):]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:
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:]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.
I assume
00baf662…is considered as bytestring input to the hash algorithm. In this case, I store the source code of my gist insha.pyand run the following testcase:As can be seen, the hexdigests match. As such, I cannot reproduce your problem. Did you purposefully assign the same value to
atwice in your message?