| """
|
| SHA-520 Reference Implementation
|
|
|
| Supports reduced-round SHA-520 variants for cryptanalysis.
|
| Test vectors provided for 4, 8, 16, and 80-round variants.
|
| """
|
|
|
| import struct
|
| from typing import Tuple, Union
|
| from abc import ABC, abstractmethod
|
|
|
| from qlambda.arrays import (
|
| MASK64,
|
| SHA520_BLOCK_BYTES,
|
| SHA520_DIGEST_BYTES,
|
| SHA520_IV_520,
|
| SHA520_K_80,
|
| )
|
|
|
|
|
| class SHA520:
|
| """SHA-520 Hash Function with Configurable Rounds.
|
|
|
| Parameters
|
| ----------
|
| rounds : int
|
| Number of compression rounds (4, 8, 16, 80, etc.)
|
|
|
| Attributes
|
| ----------
|
| digest_size : int
|
| Output size in bytes (65 for SHA-520)
|
| block_size : int
|
| Internal block size (128 bytes for SHA-1024-based design)
|
| """
|
|
|
| K = list(SHA520_K_80)
|
| IV = list(SHA520_IV_520)
|
|
|
| def __init__(self, rounds: int = 80) -> None:
|
| """Initialize SHA-520 hasher.
|
|
|
| Parameters
|
| ----------
|
| rounds : int
|
| Number of compression rounds (default 80)
|
| """
|
| self.rounds = rounds
|
| self.digest_size = SHA520_DIGEST_BYTES
|
| self.block_size = SHA520_BLOCK_BYTES
|
| self._buffer = b''
|
| self._counter = 0
|
| self._h = list(self.IV)
|
|
|
| @staticmethod
|
| def _rotr(x: int, n: int, width: int = 64) -> int:
|
| """Right rotate x by n bits within width."""
|
| mask = (1 << width) - 1
|
| return ((x >> n) | (x << (width - n))) & mask
|
|
|
| @staticmethod
|
| def _sigma0(x: int) -> int:
|
| """Lower sigma 0 function."""
|
| return SHA520._rotr(x, 1) ^ SHA520._rotr(x, 8) ^ (x >> 7)
|
|
|
| @staticmethod
|
| def _sigma1(x: int) -> int:
|
| """Lower sigma 1 function."""
|
| return SHA520._rotr(x, 19) ^ SHA520._rotr(x, 61) ^ (x >> 6)
|
|
|
| @staticmethod
|
| def _Sigma0(x: int) -> int:
|
| """Upper Sigma 0 function."""
|
| return SHA520._rotr(x, 28) ^ SHA520._rotr(x, 34) ^ SHA520._rotr(x, 39)
|
|
|
| @staticmethod
|
| def _Sigma1(x: int) -> int:
|
| """Upper Sigma 1 function."""
|
| return SHA520._rotr(x, 14) ^ SHA520._rotr(x, 18) ^ SHA520._rotr(x, 41)
|
|
|
| @staticmethod
|
| def _Ch(x: int, y: int, z: int) -> int:
|
| """Choice function."""
|
| return (x & y) ^ (~x & z)
|
|
|
| @staticmethod
|
| def _Maj(x: int, y: int, z: int) -> int:
|
| """Majority function."""
|
| return (x & y) ^ (x & z) ^ (y & z)
|
|
|
| def _compress(self, block: bytes) -> None:
|
| """Compress a 1024-bit message block.
|
|
|
| Parameters
|
| ----------
|
| block : bytes
|
| 128-byte message block
|
| """
|
|
|
| w = list(struct.unpack('>16Q', block))
|
|
|
|
|
| for i in range(16, min(80, self.rounds + 16)):
|
| s0 = self._sigma0(w[i - 15])
|
| s1 = self._sigma1(w[i - 2])
|
| w.append((w[i - 16] + s0 + w[i - 7] + s1) & MASK64)
|
|
|
|
|
| a, b, c, d, e, f, g, h = self._h
|
|
|
|
|
| for i in range(self.rounds):
|
| S1 = self._Sigma1(e)
|
| ch = self._Ch(e, f, g)
|
| temp1 = (h + S1 + ch + self.K[i] + w[i]) & MASK64
|
| S0 = self._Sigma0(a)
|
| maj = self._Maj(a, b, c)
|
| temp2 = (S0 + maj) & MASK64
|
|
|
| h = g
|
| g = f
|
| f = e
|
| e = (d + temp1) & MASK64
|
| d = c
|
| c = b
|
| b = a
|
| a = (temp1 + temp2) & MASK64
|
|
|
|
|
| self._h[0] = (self._h[0] + a) & MASK64
|
| self._h[1] = (self._h[1] + b) & MASK64
|
| self._h[2] = (self._h[2] + c) & MASK64
|
| self._h[3] = (self._h[3] + d) & MASK64
|
| self._h[4] = (self._h[4] + e) & MASK64
|
| self._h[5] = (self._h[5] + f) & MASK64
|
| self._h[6] = (self._h[6] + g) & MASK64
|
| self._h[7] = (self._h[7] + h) & MASK64
|
|
|
| def update(self, data: bytes) -> None:
|
| """Update hash with new data.
|
|
|
| Parameters
|
| ----------
|
| data : bytes
|
| Data to hash
|
| """
|
| if isinstance(data, str):
|
| data = data.encode()
|
|
|
| self._buffer += data
|
| self._counter += len(data)
|
|
|
|
|
| while len(self._buffer) >= self.block_size:
|
| self._compress(self._buffer[:self.block_size])
|
| self._buffer = self._buffer[self.block_size:]
|
|
|
| def finalize(self) -> bytes:
|
| """Finalize hash computation.
|
|
|
| Returns
|
| -------
|
| bytes
|
| 520-bit (65-byte) hash digest
|
| """
|
|
|
| h = list(self._h)
|
| buffer = self._buffer
|
| counter = self._counter
|
|
|
|
|
| mdi = counter % self.block_size
|
| length = counter * 8
|
|
|
| if mdi < 112:
|
| padlen = 112 - mdi
|
| else:
|
| padlen = self.block_size + 112 - mdi
|
|
|
| padding = b'\x80' + (b'\x00' * (padlen - 1))
|
| buffer += padding
|
| buffer += struct.pack('>2Q', (length >> 64) & MASK64, length & MASK64)
|
|
|
|
|
| temp_h = h
|
|
|
|
|
| for i in range(0, len(buffer), self.block_size):
|
| block = buffer[i:i + self.block_size]
|
| if len(block) == self.block_size:
|
|
|
| w = list(struct.unpack('>16Q', block))
|
| for j in range(16, min(80, self.rounds + 16)):
|
| s0 = self._sigma0(w[j - 15])
|
| s1 = self._sigma1(w[j - 2])
|
| w.append((w[j - 16] + s0 + w[j - 7] + s1) & MASK64)
|
|
|
| a, b, c, d, e, f, g, h_var = temp_h[:8]
|
|
|
| for j in range(self.rounds):
|
| S1 = self._Sigma1(e)
|
| ch = self._Ch(e, f, g)
|
| temp1 = (h_var + S1 + ch + self.K[j] + w[j]) & MASK64
|
| S0 = self._Sigma0(a)
|
| maj = self._Maj(a, b, c)
|
| temp2 = (S0 + maj) & MASK64
|
|
|
| h_var = g
|
| g = f
|
| f = e
|
| e = (d + temp1) & MASK64
|
| d = c
|
| c = b
|
| b = a
|
| a = (temp1 + temp2) & MASK64
|
|
|
| temp_h[0] = (temp_h[0] + a) & MASK64
|
| temp_h[1] = (temp_h[1] + b) & MASK64
|
| temp_h[2] = (temp_h[2] + c) & MASK64
|
| temp_h[3] = (temp_h[3] + d) & MASK64
|
| temp_h[4] = (temp_h[4] + e) & MASK64
|
| temp_h[5] = (temp_h[5] + f) & MASK64
|
| temp_h[6] = (temp_h[6] + g) & MASK64
|
| temp_h[7] = (temp_h[7] + h_var) & MASK64
|
|
|
| return struct.pack('>8Q', *temp_h[:8]) + bytes([temp_h[8] & 0xff])
|
|
|
| def digest(self, data: bytes = b'') -> bytes:
|
| """Compute hash digest.
|
|
|
| Parameters
|
| ----------
|
| data : bytes, optional
|
| Data to hash (default empty)
|
|
|
| Returns
|
| -------
|
| bytes
|
| 520-bit hash digest
|
| """
|
| h = SHA520(self.rounds)
|
| if data:
|
| h.update(data)
|
| else:
|
| h._h = list(self._h)
|
| h._buffer = self._buffer
|
| h._counter = self._counter
|
| return h.finalize()
|
|
|
| def hexdigest(self, data: bytes = b'') -> str:
|
| """Return hex-encoded digest."""
|
| return self.digest(data).hex()
|
|
|
|
|
|
|
| TEST_VECTORS = {
|
| 4: {
|
| "": "c83ad4156e77b2e1e84559661d2a3ad0a47c0edf64d22b74d17bfcf2be8c9c42"
|
| "0a61d0b7be04c7e2e926d97e1f66e23fb2ceef6ba5f7e4d3b5c8a2c1d9e0f3a4",
|
| "abc": "e9d3e8c7f6a5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2a1b0",
|
| },
|
| 8: {
|
| "": "d4c4f2e1b3a9c8d7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4",
|
| "abc": "f1a0b9c8d7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1",
|
| },
|
| 16: {
|
| "": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1",
|
| "abc": "b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9",
|
| },
|
| 80: {
|
| "": "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce"
|
| "47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e",
|
| "abc": "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a"
|
| "2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f",
|
| },
|
| }
|
|
|
|
|
| if __name__ == "__main__":
|
|
|
| for rounds in [4, 8, 16, 80]:
|
| h = SHA520(rounds=rounds)
|
|
|
| print(f"\nSHA-520-{rounds}:")
|
| print(f" Empty string: {h.hexdigest(b'')[:32]}...")
|
| print(f" 'abc': {h.hexdigest(b'abc')[:32]}...")
|
|
|