-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsha256.py
156 lines (118 loc) · 4.65 KB
/
sha256.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#!/usr/bin/env python3
"""This is an implementation of the Secure Hash Algorithm SHA-256 based on the RFC 6234
Usage: {script_name} <filename>
"""
# NOTE:
# n % 2^i = n & (2^i - 1)
# 2^32 - 1 = 0xffffffff
import os
import sys
BLOCK_SIZE_BYTE = 64
class InputFileError(Exception):
pass
def _rotr(x: int, n: int) -> int :
"""Binary right rotate number x by n
SHA-256 operate on 32-bit words
"""
return (x >> n) | ((x << (32 - n)) & 0xffffffff)
# Functions ch, maj, bsig0, bsig1, ssig0, ssig1, see rfc6234#section-5.1
def _ch(x: int, y: int, z: int) -> int:
return (x & y) ^ ((~x) & z)
def _maj(x: int, y: int, z: int) -> int:
return (x & y) ^ (x & z) ^ (y & z)
def _bsig0(x: int) -> int:
return _rotr(x, 2) ^ _rotr(x, 13) ^ _rotr(x, 22)
def _bsig1(x: int) -> int:
return _rotr(x, 6) ^ _rotr(x, 11) ^ _rotr(x, 25)
def _ssig0(x: int) -> int:
return _rotr(x, 7) ^ _rotr(x, 18) ^ (x >> 3)
def _ssig1(x: int) -> int:
return _rotr(x, 17) ^ _rotr(x, 19) ^ (x >> 10)
# K constants, see rfc6234#section-5.1
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,
]
# Hash initialization, see rfc6234#section-6.1
H = [
0x6a09e667, 0xbb67ae85,
0x3c6ef372, 0xa54ff53a,
0x510e527f, 0x9b05688c,
0x1f83d9ab, 0x5be0cd19,
]
# Pad message on the right to get length L % 512 == 0, see rfc6234#section-4.1
def _perform_padding(chunck: bytes, input_size: int) -> bytes:
assert len(chunck) <= 64,\
f'_perform_padding(chunck, input_size): Bad block size, must be <= 512 bits (64 bytes) got {len(chunck)}.'
assert input_size < 2 ** 64,\
f'_perform_padding(chunck, input_size): Bad input_size, must be < 2^64 bits got {input_size}.'
block = bytearray(chunck)
l = input_size
# We have to substract 7 zeros from 447 because we works with bytes (8 bits)
k = (440 - l) % 512
block.extend(b'\x80') # 0b10000000
block.extend(b'\x00' * (k >> 3))
block.extend(l.to_bytes(8, byteorder = 'big', signed = False)) # 64 bits int
return bytes(block)
def _compute_hash(block: bytes) -> None:
assert len(block) == BLOCK_SIZE_BYTE,\
f'_compute_hash(block): Bad block size, must be 512 bits (64 bytes) got {len(block)}.'
words = []
words_count = len(block) >> 2 # 32 bits words
for wi in range(words_count):
words.append(int.from_bytes(block[wi * 4:wi * 4 + 4], byteorder = 'big', signed = False))
for wi in range(16, BLOCK_SIZE_BYTE):
word = (_ssig1(words[wi - 2]) + words[wi - 7] + _ssig0(words[wi - 15]) + words[wi - 16]) & 0xffffffff
words.append(word)
a, b, c, d, e, f, g, h = H[0:]
for t in range(0, BLOCK_SIZE_BYTE):
t1 = (h + _bsig1(e) + _ch(e, f, g) + K[t] + words[t]) & 0xffffffff
t2 = (_bsig0(a) + _maj(a, b, c)) & 0xffffffff
h = g
g = f
f = e
e = (d + t1) & 0xffffffff
d = c
c = b
b = a
a = (t1 + t2) & 0xffffffff
for i, x in enumerate([a, b, c, d, e, f, g, h]):
H[i] = (x + H[i]) & 0xffffffff
def sha256(filename: str) -> str:
if not os.path.isfile(filename):
raise InputFileError(f'The provided file "{filename}" could not be found.')
file_size = os.stat(filename).st_size * 8 # message length in bits
if file_size >= 2 ** 64:
raise InputFileError(f'The provided file "{filename}" is too big.')
# empty file, no need to read it
if file_size == 0:
block = _perform_padding(b'', file_size)
_compute_hash(block)
else:
nb_chuncks = file_size >> 9
if file_size % (BLOCK_SIZE_BYTE * 8):
nb_chuncks += 1
with open(filename, 'rb') as f:
while chunck := f.read(BLOCK_SIZE_BYTE):
# pad only the last chunck
if nb_chuncks == 1:
chunck = _perform_padding(chunck, file_size)
# there are edge cases where the padding adds enought 0 to make a new chunck
for i in range(0, len(chunck), BLOCK_SIZE_BYTE):
_compute_hash(chunck[i:i + BLOCK_SIZE_BYTE])
nb_chuncks -= 1
return ''.join(map(lambda h: f'{h:x}', H))
def main() -> int:
if len(sys.argv) != 2:
print(__doc__.format(script_name = os.path.basename(__file__)))
return 0
filename = sys.argv[1]
print(sha256(filename))
if __name__ == "__main__":
exit(main())