96 lines
3.0 KiB
Python
96 lines
3.0 KiB
Python
|
|
# modes/hash_mode.py
|
||
|
|
import math
|
||
|
|
|
||
|
|
|
||
|
|
class HashMode:
|
||
|
|
"""
|
||
|
|
Учебная хэш-функция.
|
||
|
|
Варианты:
|
||
|
|
1 = sin-based
|
||
|
|
2 = cos-based
|
||
|
|
3 = xor-based
|
||
|
|
"""
|
||
|
|
|
||
|
|
def run(self, variant: str, message: str, salt: str = "", bytes: int = 16) -> str:
|
||
|
|
if not (2 <= bytes <= 16):
|
||
|
|
raise ValueError("Параметр bytes должен быть в диапазоне [2, 16]")
|
||
|
|
|
||
|
|
data = (message + salt).encode("utf-8")
|
||
|
|
|
||
|
|
if variant == "1":
|
||
|
|
out = self._sin_hash(data)
|
||
|
|
elif variant == "2":
|
||
|
|
out = self._cos_hash(data)
|
||
|
|
elif variant == "3":
|
||
|
|
out = self._xor_hash(data)
|
||
|
|
else:
|
||
|
|
raise ValueError(f"Неизвестный вариант: {variant}")
|
||
|
|
|
||
|
|
# Обрезаем до нужного числа байт
|
||
|
|
truncated = self._truncate_bytes(out, bytes)
|
||
|
|
return ''.join(f"{x:02x}" for x in truncated)
|
||
|
|
|
||
|
|
# --- служебные ------------------------------------------------------
|
||
|
|
|
||
|
|
def _truncate_bytes(self, out: bytearray, bytes: int) -> bytearray:
|
||
|
|
if bytes >= len(out):
|
||
|
|
return out
|
||
|
|
truncated = out[:bytes]
|
||
|
|
# примешиваем все отброшенные байты в оставшиеся
|
||
|
|
for i, b in enumerate(out[bytes:], start=1):
|
||
|
|
truncated[i % len(truncated)] ^= b
|
||
|
|
return truncated
|
||
|
|
|
||
|
|
# --- реализации ------------------------------------------------------
|
||
|
|
|
||
|
|
def _sin_hash(self, data: bytes) -> bytearray:
|
||
|
|
out = [0x55 ^ i for i in range(16)]
|
||
|
|
if not data:
|
||
|
|
return out
|
||
|
|
|
||
|
|
angle = (2 * math.pi) / (len(out) * len(data))
|
||
|
|
|
||
|
|
for i, b in enumerate(data, 1):
|
||
|
|
for j in range(len(out)):
|
||
|
|
val = int((math.sin(b + (i + j) * angle) + 1) * 127)
|
||
|
|
out[j] ^= val & 0xFF
|
||
|
|
|
||
|
|
return out
|
||
|
|
|
||
|
|
def _cos_hash(self, data: bytes) -> bytearray:
|
||
|
|
out = [0x77 ^ i for i in range(16)]
|
||
|
|
if not data:
|
||
|
|
return out
|
||
|
|
|
||
|
|
angle = (2 * math.pi) / (len(out) * len(data))
|
||
|
|
|
||
|
|
for i, b in enumerate(data, 1):
|
||
|
|
for j in range(len(out)):
|
||
|
|
val = int((math.cos(b + (i + j) * angle) + 1) * 127)
|
||
|
|
out[j] ^= val & 0xFF
|
||
|
|
|
||
|
|
return out
|
||
|
|
|
||
|
|
def _mix_bits(self, mix: int) -> bytearray:
|
||
|
|
# количество байт у числа = (битовая длина + 7) // 8
|
||
|
|
count = (mix.bit_length() + 7) // 8
|
||
|
|
for i in range(1, count):
|
||
|
|
mix ^= (mix >> (8 * i))
|
||
|
|
|
||
|
|
return mix
|
||
|
|
|
||
|
|
def _xor_hash(self, data: bytes) -> bytearray:
|
||
|
|
out = [0xAA ^ i for i in range(16)]
|
||
|
|
if not data:
|
||
|
|
return out
|
||
|
|
|
||
|
|
phi = (1 + 5 ** 0.5) / 2 # золотое сечение
|
||
|
|
|
||
|
|
for i, b in enumerate(data, 1):
|
||
|
|
for j in range(16):
|
||
|
|
mix = (b * (i + j + 1)) ^ int(phi * 1e6)
|
||
|
|
mix = self._mix_bits(mix)
|
||
|
|
out[j] ^= mix & 0xFF # только младший байт
|
||
|
|
|
||
|
|
return out
|