From e2f352a210283e1afaa5214aac2684780c270577 Mon Sep 17 00:00:00 2001 From: Asc3 Date: Fri, 19 Dec 2025 02:49:40 +0100 Subject: [PATCH 1/4] added: sha-3(Keccak) implementation --- hashes/sha3.py | 141 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 hashes/sha3.py diff --git a/hashes/sha3.py b/hashes/sha3.py new file mode 100644 index 000000000000..5ec7abb05c56 --- /dev/null +++ b/hashes/sha3.py @@ -0,0 +1,141 @@ +""" +Pure Python SHA-3 (Keccak-f[1600]) implementation + +Usage: + python sha3.py --string "hello" + python sha3.py --file data.bin +""" + +import argparse +import struct +from typing import List + + +class KeccakSHA3: + # Round constants + _RC = [ + 0x0000000000000001, 0x0000000000008082, 0x800000000000808A, + 0x8000000080008000, 0x000000000000808B, 0x0000000080000001, + 0x8000000080008081, 0x8000000000008009, 0x000000000000008A, + 0x0000000000000088, 0x0000000080008009, 0x000000008000000A, + 0x000000008000808B, 0x800000000000008B, 0x8000000000008089, + 0x8000000000008003, 0x8000000000008002, 0x8000000000000080, + 0x000000000000800A, 0x800000008000000A, 0x8000000080008081, + 0x8000000000008080, 0x0000000080000001, 0x8000000080008008 + ] + + _ROT = [ + [0, 36, 3, 41, 18], + [1, 44, 10, 45, 2], + [62, 6, 43, 15, 61], + [28, 55, 25, 21, 56], + [27, 20, 39, 8, 14] + ] + + def __init__(self, message: bytes, bits: int = 256): + if bits not in (224, 256, 384, 512): + raise ValueError("Invalid SHA3 length") + + self.msg = message + self.out_bits = bits + self.rate = 1600 - 2 * bits + self.state = [[0] * 5 for _ in range(5)] + + self._absorb() + self.digest = self._squeeze().hex() + + # ================= CORE ================= + + @staticmethod + def _rol(x: int, n: int) -> int: + n %= 64 + return ((x << n) | (x >> (64 - n))) & 0xFFFFFFFFFFFFFFFF + + def _permute(self): + A = self.state + + for rnd in range(24): + # θ + C = [A[x][0] ^ A[x][1] ^ A[x][2] ^ A[x][3] ^ A[x][4] for x in range(5)] + D = [C[(x - 1) % 5] ^ self._rol(C[(x + 1) % 5], 1) for x in range(5)] + for x in range(5): + for y in range(5): + A[x][y] ^= D[x] + + # ρ + π + B = [[0] * 5 for _ in range(5)] + for x in range(5): + for y in range(5): + B[y][(2 * x + 3 * y) % 5] = self._rol(A[x][y], self._ROT[x][y]) + + # χ + for x in range(5): + for y in range(5): + A[x][y] = B[x][y] ^ ((~B[(x + 1) % 5][y]) & B[(x + 2) % 5][y]) + + # ι + A[0][0] ^= self._RC[rnd] + + # ================= SPONGE ================= + + def _pad(self, data: bytes) -> bytes: + r = self.rate // 8 + buf = bytearray(data) + buf.append(0x06) + while len(buf) % r != r - 1: + buf.append(0x00) + buf.append(0x80) + return bytes(buf) + + def _absorb(self): + r = self.rate // 8 + padded = self._pad(self.msg) + + for off in range(0, len(padded), r): + block = padded[off:off + r] + for i in range(0, r, 8): + lane = struct.unpack(" bytes: + out = bytearray() + r = self.rate // 8 + need = self.out_bits // 8 + + while len(out) < need: + for i in range(0, r, 8): + x = (i // 8) % 5 + y = (i // 8) // 5 + out.extend(struct.pack(" Date: Fri, 19 Dec 2025 01:53:12 +0000 Subject: [PATCH 2/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- hashes/sha3.py | 44 +++++++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/hashes/sha3.py b/hashes/sha3.py index 5ec7abb05c56..07e13ddc3825 100644 --- a/hashes/sha3.py +++ b/hashes/sha3.py @@ -14,14 +14,30 @@ class KeccakSHA3: # Round constants _RC = [ - 0x0000000000000001, 0x0000000000008082, 0x800000000000808A, - 0x8000000080008000, 0x000000000000808B, 0x0000000080000001, - 0x8000000080008081, 0x8000000000008009, 0x000000000000008A, - 0x0000000000000088, 0x0000000080008009, 0x000000008000000A, - 0x000000008000808B, 0x800000000000008B, 0x8000000000008089, - 0x8000000000008003, 0x8000000000008002, 0x8000000000000080, - 0x000000000000800A, 0x800000008000000A, 0x8000000080008081, - 0x8000000000008080, 0x0000000080000001, 0x8000000080008008 + 0x0000000000000001, + 0x0000000000008082, + 0x800000000000808A, + 0x8000000080008000, + 0x000000000000808B, + 0x0000000080000001, + 0x8000000080008081, + 0x8000000000008009, + 0x000000000000008A, + 0x0000000000000088, + 0x0000000080008009, + 0x000000008000000A, + 0x000000008000808B, + 0x800000000000008B, + 0x8000000000008089, + 0x8000000000008003, + 0x8000000000008002, + 0x8000000000000080, + 0x000000000000800A, + 0x800000008000000A, + 0x8000000080008081, + 0x8000000000008080, + 0x0000000080000001, + 0x8000000080008008, ] _ROT = [ @@ -29,7 +45,7 @@ class KeccakSHA3: [1, 44, 10, 45, 2], [62, 6, 43, 15, 61], [28, 55, 25, 21, 56], - [27, 20, 39, 8, 14] + [27, 20, 39, 8, 14], ] def __init__(self, message: bytes, bits: int = 256): @@ -92,9 +108,9 @@ def _absorb(self): padded = self._pad(self.msg) for off in range(0, len(padded), r): - block = padded[off:off + r] + block = padded[off : off + r] for i in range(0, r, 8): - lane = struct.unpack(" bytes: # ================= CLI ================= + def main(): parser = argparse.ArgumentParser(description="SHA-3 hashing tool") parser.add_argument("-s", "--string", help="String input") parser.add_argument("-f", "--file", help="File input") - parser.add_argument("-l", "--length", type=int, default=256, - choices=[224, 256, 384, 512]) + parser.add_argument( + "-l", "--length", type=int, default=256, choices=[224, 256, 384, 512] + ) args = parser.parse_args() From 1072462b8c5ad9a66921d49e33962101c4523535 Mon Sep 17 00:00:00 2001 From: Asc3 Date: Fri, 19 Dec 2025 13:58:12 +0100 Subject: [PATCH 3/4] added sha3 --- hashes/sha3.py | 94 ++++++++++++++++++++++---------------------------- 1 file changed, 41 insertions(+), 53 deletions(-) diff --git a/hashes/sha3.py b/hashes/sha3.py index 07e13ddc3825..98b825bffcce 100644 --- a/hashes/sha3.py +++ b/hashes/sha3.py @@ -8,39 +8,23 @@ import argparse import struct -from typing import List +from typing import ClassVar class KeccakSHA3: # Round constants - _RC = [ - 0x0000000000000001, - 0x0000000000008082, - 0x800000000000808A, - 0x8000000080008000, - 0x000000000000808B, - 0x0000000080000001, - 0x8000000080008081, - 0x8000000000008009, - 0x000000000000008A, - 0x0000000000000088, - 0x0000000080008009, - 0x000000008000000A, - 0x000000008000808B, - 0x800000000000008B, - 0x8000000000008089, - 0x8000000000008003, - 0x8000000000008002, - 0x8000000000000080, - 0x000000000000800A, - 0x800000008000000A, - 0x8000000080008081, - 0x8000000000008080, - 0x0000000080000001, - 0x8000000080008008, + _RC: ClassVar[list[int]] = [ + 0x0000000000000001, 0x0000000000008082, 0x800000000000808A, + 0x8000000080008000, 0x000000000000808B, 0x0000000080000001, + 0x8000000080008081, 0x8000000000008009, 0x000000000000008A, + 0x0000000000000088, 0x0000000080008009, 0x000000008000000A, + 0x000000008000808B, 0x800000000000008B, 0x8000000000008089, + 0x8000000000008003, 0x8000000000008002, 0x8000000000000080, + 0x000000000000800A, 0x800000008000000A, 0x8000000080008081, + 0x8000000000008080, 0x0000000080000001, 0x8000000080008008, ] - _ROT = [ + _ROT: ClassVar[list[list[int]]] = [ [0, 36, 3, 41, 18], [1, 44, 10, 45, 2], [62, 6, 43, 15, 61], @@ -67,50 +51,51 @@ def _rol(x: int, n: int) -> int: n %= 64 return ((x << n) | (x >> (64 - n))) & 0xFFFFFFFFFFFFFFFF - def _permute(self): - A = self.state + def _permute(self) -> None: + a = self.state for rnd in range(24): - # θ - C = [A[x][0] ^ A[x][1] ^ A[x][2] ^ A[x][3] ^ A[x][4] for x in range(5)] - D = [C[(x - 1) % 5] ^ self._rol(C[(x + 1) % 5], 1) for x in range(5)] + # theta + c = [a[x][0] ^ a[x][1] ^ a[x][2] ^ a[x][3] ^ a[x][4] for x in range(5)] + d = [c[(x - 1) % 5] ^ self._rol(c[(x + 1) % 5], 1) for x in range(5)] + for x in range(5): for y in range(5): - A[x][y] ^= D[x] + a[x][y] ^= d[x] - # ρ + π - B = [[0] * 5 for _ in range(5)] + # rho + pi + b = [[0] * 5 for _ in range(5)] for x in range(5): for y in range(5): - B[y][(2 * x + 3 * y) % 5] = self._rol(A[x][y], self._ROT[x][y]) + b[y][(2 * x + 3 * y) % 5] = self._rol(a[x][y], self._ROT[x][y]) - # χ + # chi for x in range(5): for y in range(5): - A[x][y] = B[x][y] ^ ((~B[(x + 1) % 5][y]) & B[(x + 2) % 5][y]) + a[x][y] = b[x][y] ^ ((~b[(x + 1) % 5][y]) & b[(x + 2) % 5][y]) - # ι - A[0][0] ^= self._RC[rnd] + # iota + a[0][0] ^= self._RC[rnd] # ================= SPONGE ================= def _pad(self, data: bytes) -> bytes: - r = self.rate // 8 + rate_bytes = self.rate // 8 buf = bytearray(data) buf.append(0x06) - while len(buf) % r != r - 1: + while len(buf) % rate_bytes != rate_bytes - 1: buf.append(0x00) buf.append(0x80) return bytes(buf) - def _absorb(self): - r = self.rate // 8 + def _absorb(self) -> None: + rate_bytes = self.rate // 8 padded = self._pad(self.msg) - for off in range(0, len(padded), r): - block = padded[off : off + r] - for i in range(0, r, 8): - lane = struct.unpack(" bytes: out = bytearray() - r = self.rate // 8 + rate_bytes = self.rate // 8 need = self.out_bits // 8 while len(out) < need: - for i in range(0, r, 8): + for i in range(0, rate_bytes, 8): x = (i // 8) % 5 y = (i // 8) // 5 out.extend(struct.pack(" bytes: # ================= CLI ================= - -def main(): +def main() -> None: parser = argparse.ArgumentParser(description="SHA-3 hashing tool") parser.add_argument("-s", "--string", help="String input") parser.add_argument("-f", "--file", help="File input") parser.add_argument( - "-l", "--length", type=int, default=256, choices=[224, 256, 384, 512] + "-l", + "--length", + type=int, + default=256, + choices=[224, 256, 384, 512], ) args = parser.parse_args() From 3415bbc8d8932afe079ca38269df4fa04a3d2663 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 19 Dec 2025 13:09:08 +0000 Subject: [PATCH 4/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- hashes/sha3.py | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/hashes/sha3.py b/hashes/sha3.py index 98b825bffcce..cf99e263c811 100644 --- a/hashes/sha3.py +++ b/hashes/sha3.py @@ -14,14 +14,30 @@ class KeccakSHA3: # Round constants _RC: ClassVar[list[int]] = [ - 0x0000000000000001, 0x0000000000008082, 0x800000000000808A, - 0x8000000080008000, 0x000000000000808B, 0x0000000080000001, - 0x8000000080008081, 0x8000000000008009, 0x000000000000008A, - 0x0000000000000088, 0x0000000080008009, 0x000000008000000A, - 0x000000008000808B, 0x800000000000008B, 0x8000000000008089, - 0x8000000000008003, 0x8000000000008002, 0x8000000000000080, - 0x000000000000800A, 0x800000008000000A, 0x8000000080008081, - 0x8000000000008080, 0x0000000080000001, 0x8000000080008008, + 0x0000000000000001, + 0x0000000000008082, + 0x800000000000808A, + 0x8000000080008000, + 0x000000000000808B, + 0x0000000080000001, + 0x8000000080008081, + 0x8000000000008009, + 0x000000000000008A, + 0x0000000000000088, + 0x0000000080008009, + 0x000000008000000A, + 0x000000008000808B, + 0x800000000000008B, + 0x8000000000008089, + 0x8000000000008003, + 0x8000000000008002, + 0x8000000000000080, + 0x000000000000800A, + 0x800000008000000A, + 0x8000000080008081, + 0x8000000000008080, + 0x0000000080000001, + 0x8000000080008008, ] _ROT: ClassVar[list[list[int]]] = [ @@ -93,9 +109,9 @@ def _absorb(self) -> None: padded = self._pad(self.msg) for off in range(0, len(padded), rate_bytes): - block = padded[off:off + rate_bytes] + block = padded[off : off + rate_bytes] for i in range(0, rate_bytes, 8): - lane = struct.unpack(" bytes: # ================= CLI ================= + def main() -> None: parser = argparse.ArgumentParser(description="SHA-3 hashing tool") parser.add_argument("-s", "--string", help="String input")