Skip to content

Commit e2f352a

Browse files
committed
added: sha-3(Keccak) implementation
1 parent 2c15b8c commit e2f352a

File tree

1 file changed

+141
-0
lines changed

1 file changed

+141
-0
lines changed

hashes/sha3.py

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
"""
2+
Pure Python SHA-3 (Keccak-f[1600]) implementation
3+
4+
Usage:
5+
python sha3.py --string "hello"
6+
python sha3.py --file data.bin
7+
"""
8+
9+
import argparse
10+
import struct
11+
from typing import List
12+
13+
14+
class KeccakSHA3:
15+
# Round constants
16+
_RC = [
17+
0x0000000000000001, 0x0000000000008082, 0x800000000000808A,
18+
0x8000000080008000, 0x000000000000808B, 0x0000000080000001,
19+
0x8000000080008081, 0x8000000000008009, 0x000000000000008A,
20+
0x0000000000000088, 0x0000000080008009, 0x000000008000000A,
21+
0x000000008000808B, 0x800000000000008B, 0x8000000000008089,
22+
0x8000000000008003, 0x8000000000008002, 0x8000000000000080,
23+
0x000000000000800A, 0x800000008000000A, 0x8000000080008081,
24+
0x8000000000008080, 0x0000000080000001, 0x8000000080008008
25+
]
26+
27+
_ROT = [
28+
[0, 36, 3, 41, 18],
29+
[1, 44, 10, 45, 2],
30+
[62, 6, 43, 15, 61],
31+
[28, 55, 25, 21, 56],
32+
[27, 20, 39, 8, 14]
33+
]
34+
35+
def __init__(self, message: bytes, bits: int = 256):
36+
if bits not in (224, 256, 384, 512):
37+
raise ValueError("Invalid SHA3 length")
38+
39+
self.msg = message
40+
self.out_bits = bits
41+
self.rate = 1600 - 2 * bits
42+
self.state = [[0] * 5 for _ in range(5)]
43+
44+
self._absorb()
45+
self.digest = self._squeeze().hex()
46+
47+
# ================= CORE =================
48+
49+
@staticmethod
50+
def _rol(x: int, n: int) -> int:
51+
n %= 64
52+
return ((x << n) | (x >> (64 - n))) & 0xFFFFFFFFFFFFFFFF
53+
54+
def _permute(self):
55+
A = self.state
56+
57+
for rnd in range(24):
58+
# θ
59+
C = [A[x][0] ^ A[x][1] ^ A[x][2] ^ A[x][3] ^ A[x][4] for x in range(5)]
60+
D = [C[(x - 1) % 5] ^ self._rol(C[(x + 1) % 5], 1) for x in range(5)]
61+
for x in range(5):
62+
for y in range(5):
63+
A[x][y] ^= D[x]
64+
65+
# ρ + π
66+
B = [[0] * 5 for _ in range(5)]
67+
for x in range(5):
68+
for y in range(5):
69+
B[y][(2 * x + 3 * y) % 5] = self._rol(A[x][y], self._ROT[x][y])
70+
71+
# χ
72+
for x in range(5):
73+
for y in range(5):
74+
A[x][y] = B[x][y] ^ ((~B[(x + 1) % 5][y]) & B[(x + 2) % 5][y])
75+
76+
# ι
77+
A[0][0] ^= self._RC[rnd]
78+
79+
# ================= SPONGE =================
80+
81+
def _pad(self, data: bytes) -> bytes:
82+
r = self.rate // 8
83+
buf = bytearray(data)
84+
buf.append(0x06)
85+
while len(buf) % r != r - 1:
86+
buf.append(0x00)
87+
buf.append(0x80)
88+
return bytes(buf)
89+
90+
def _absorb(self):
91+
r = self.rate // 8
92+
padded = self._pad(self.msg)
93+
94+
for off in range(0, len(padded), r):
95+
block = padded[off:off + r]
96+
for i in range(0, r, 8):
97+
lane = struct.unpack("<Q", block[i:i + 8])[0]
98+
x = (i // 8) % 5
99+
y = (i // 8) // 5
100+
self.state[x][y] ^= lane
101+
self._permute()
102+
103+
def _squeeze(self) -> bytes:
104+
out = bytearray()
105+
r = self.rate // 8
106+
need = self.out_bits // 8
107+
108+
while len(out) < need:
109+
for i in range(0, r, 8):
110+
x = (i // 8) % 5
111+
y = (i // 8) // 5
112+
out.extend(struct.pack("<Q", self.state[x][y]))
113+
if len(out) < need:
114+
self._permute()
115+
116+
return bytes(out[:need])
117+
118+
119+
# ================= CLI =================
120+
121+
def main():
122+
parser = argparse.ArgumentParser(description="SHA-3 hashing tool")
123+
parser.add_argument("-s", "--string", help="String input")
124+
parser.add_argument("-f", "--file", help="File input")
125+
parser.add_argument("-l", "--length", type=int, default=256,
126+
choices=[224, 256, 384, 512])
127+
128+
args = parser.parse_args()
129+
130+
if args.file:
131+
with open(args.file, "rb") as f:
132+
data = f.read()
133+
else:
134+
data = (args.string or "Hello World").encode()
135+
136+
h = KeccakSHA3(data, args.length)
137+
print(f"SHA3-{args.length}: {h.digest}")
138+
139+
140+
if __name__ == "__main__":
141+
main()

0 commit comments

Comments
 (0)