Initial commit

This commit is contained in:
user
2026-07-12 14:06:45 +04:00
commit a75028d68f
148 changed files with 3275 additions and 0 deletions
+109
View File
@@ -0,0 +1,109 @@
from argparse import ArgumentParser
from secrets import randbelow
from utils.check_case import check_case
from utils.format_line import format_line
from orchestration.param_generator import ParamGenerator
def _random_secret() -> int:
# 2..9999 включительно (меньше 10000, исключаем 0/1)
return 2 + randbelow(9998)
def run_auto(count: int) -> None:
"""
Автоматический режим:
- случайно генерируем (p, q, g);
- случайно выбираем x, y (<10000);
- вычисляем K и проверяем через check_case;
- печатаем результат через format_line
"""
for i in range(1, count + 1):
p, q, g = ParamGenerator.generate() # q выводим для информации
x = _random_secret()
y = _random_secret()
# Вычислим K заранее, чтобы передать как эталон в check_case
# (тем самым используем общий хелпер единообразно)
ok, (A, B, K_a, K_b) = check_case(p, g, x, y, K_expected=None)
# Когда K_expected=None, наша старая версия check_case
# сравнивала строго с эталоном. Если у тебя именно так,
# просто заменим на повторный вызов с «эталоном = K_a»
if K_a != K_b:
ok = False
else:
ok, (A, B, K_a, K_b) = check_case(p, g, x, y, K_expected=K_a)
print(f"[auto #{i}] p={p} q={q} g={g}")
print(format_line(i, A, B, K_a, K_b, ok))
def run_manual(p: int, g: int, x: int, y: int, k: int | None) -> None:
"""
Ручной режим:
- принимаем p, g, x, y;
- если указан k — проверяем с эталоном;
- если k не указан — просто считаем и показываем
"""
if k is None:
ok, (A, B, K_a, K_b) = check_case(p, g, x, y, K_expected=None)
ok = (K_a == K_b)
print(f"[manual] p={p} g={g}")
print(format_line(1, A, B, K_a, K_b, ok))
else:
ok, (A, B, K_a, K_b) = check_case(p, g, x, y, K_expected=k)
print(f"[manual] p={p} g={g} K_expected={k}")
print(format_line(1, A, B, K_a, K_b, ok))
def parse_args():
ap = ArgumentParser(
description=(
"Demo DiffieHellman: режимы manual и auto\n"
"Auto: случайно генерирует (p,q,g) и секреты (<10000)\n"
"Manual: используется заданный набор параметров"
)
)
ap.add_argument(
"--mode",
choices=["auto", "manual"],
default="auto",
help="Режим работы (auto|manual). По умолчанию auto",
)
ap.add_argument(
"--count",
type=int,
default=5,
help="Сколько генераций в auto-режиме (по умолчанию 5)",
)
ap.add_argument("--p", type=int, help="Модуль (prime_modulus) в manual")
ap.add_argument("--g", type=int, help="Генератор (generator) в manual")
ap.add_argument("--x", type=int, help="Секрет Алисы (<10000) в manual")
ap.add_argument("--y", type=int, help="Секрет Боба (<10000) в manual")
ap.add_argument(
"--k",
type=int,
help="Эталонный общий ключ для проверки в manual (необязательно)",
)
return ap.parse_args()
def main():
args = parse_args()
if args.mode == "auto":
run_auto(count=args.count)
return
# manual
missing = [n for n, v in [("p", args.p), ("g", args.g),
("x", args.x), ("y", args.y)] if v is None]
if missing:
need = ", ".join(missing)
raise SystemExit(f"manual-режим: необходимо указать параметры: {need}")
run_manual(p=args.p, g=args.g, x=args.x, y=args.y, k=args.k)
if __name__ == "__main__":
main()
+1
View File
@@ -0,0 +1 @@
#
@@ -0,0 +1,19 @@
from secrets import randbelow
from .public_params import PublicParams
class Participant:
"""
Участник протокола (Алиса или Боб).
Держит свой секрет, публикует g^secret mod p,
умеет вычислять общий ключ.
"""
def __init__(self, params: PublicParams, secret: int | None = None):
self.params = params
self.secret = secret if secret is not None else (2 + randbelow(9998))
self.public_value = pow(self.params.generator,
self.secret, self.params.prime_modulus)
def compute_shared_key(self, other_public: int) -> int:
return pow(other_public, self.secret, self.params.prime_modulus)
@@ -0,0 +1,12 @@
def is_prime(n: int) -> bool:
"""Проверка на простоту"""
if n < 2:
return False
if n % 2 == 0:
return n == 2
d = 3
while d * d <= n:
if n % d == 0:
return False
d += 2
return True
@@ -0,0 +1,30 @@
from dataclasses import dataclass
from .prime_utils import is_prime
@dataclass(frozen=True)
class PublicParams:
"""
Публичные параметры протокола:
- prime_modulus: простое число p
- generator: порождающий элемент g
"""
prime_modulus: int
generator: int
def __post_init__(self):
p = self.prime_modulus
g = self.generator
# Проверка простоты модуля
if not is_prime(p):
raise ValueError(f"prime_modulus={p} не является простым числом")
# Проверка допустимости генератора
if g <= 1 or g >= p - 1:
raise ValueError(
f"generator={g} вне допустимого диапазона (2..p-2)")
# Проверка, что генератор не сворачивается в малую подгруппу
if pow(g, (p - 1) // 2, p) == 1:
raise ValueError(f"generator={g} не является корректным для p={p}")
@@ -0,0 +1,44 @@
from secrets import randbelow, randbits
from core.prime_utils import is_prime
class ParamGenerator:
"""
Генератор параметров протокола Диффи–Хеллмана.
Возвращает тройку (p, q, g):
- p = 2*q + 1, где p и q простые
- g — корректный генератор для группы по модулю p
"""
@staticmethod
def generate() -> tuple[int, int, int]:
"""
Случайным образом выбирает размерность (кол-во бит) для q,
подбирает безопасное простое p и генератор g.
Возвращает (p, q, g).
"""
# Размер числа выбираем случайно от 8 до 16 бит (от 256 до 65 535)
bits = 8 + randbelow(9)
# Генерация безопасного простого
while True:
q = randbits(bits - 1) | 1 # нечётное число
if not is_prime(q):
continue
p = 2 * q + 1
if not is_prime(p):
continue
# Найдём генератор
g = ParamGenerator._find_generator(p, q)
return p, q, g
@staticmethod
def _find_generator(p: int, q: int) -> int:
"""
Ищет генератор g: минимальное число 2..p-2,
для которого g^q mod p != 1.
"""
for g in range(2, p - 1):
if pow(g, q, p) != 1:
return g
raise ValueError("Не удалось найти генератор для p")
@@ -0,0 +1,16 @@
from core.participant import Participant
from core.public_params import PublicParams
class Alice:
def __init__(self, params: PublicParams, secret: int | None = None):
self.party = Participant(params, secret=secret)
self.shared_key = None
@property
def A(self) -> int:
return self.party.public_value
def receive_B(self, B: int):
self.shared_key = self.party.compute_shared_key(B)
return self.shared_key
@@ -0,0 +1,16 @@
from core.participant import Participant
from core.public_params import PublicParams
class Bob:
def __init__(self, params: PublicParams, secret: int | None = None):
self.party = Participant(params, secret=secret)
self.shared_key = None
@property
def B(self) -> int:
return self.party.public_value
def receive_A(self, A: int):
self.shared_key = self.party.compute_shared_key(A)
return self.shared_key
+30
View File
@@ -0,0 +1,30 @@
from utils.check_case import check_case
from utils.format_line import format_line
CASES = [
(29, 2, 60, 82, 23),
(53, 2, 20, 77, 42),
(73, 5, 37, 47, 31),
(31, 3, 31, 51, 15),
(41, 6, 31, 98, 8),
(19, 2, 20, 73, 4),
(71, 7, 30, 89, 45),
(109, 6, 7, 14, 84),
(131, 2, 88, 15, 52),
(97, 5, 58, 68, 6),
]
def main():
all_ok = True
for i, case in enumerate(CASES, start=1):
ok, (A, B, K_a, K_b) = check_case(*case)
print(format_line(i, A, B, K_a, K_b, ok))
if not ok:
all_ok = False
if not all_ok:
raise SystemExit(1)
if __name__ == "__main__":
main()
@@ -0,0 +1 @@
#
@@ -0,0 +1,21 @@
from core.public_params import PublicParams
from core.participant import Participant
def check_case(prime_modulus, generator, x, y, K_expected):
"""
Строит участников, имитирует обмен и проверяет совпадение ключа с эталоном.
Возвращает: ok (bool), (A, B, K_a, K_b)
"""
params = PublicParams(prime_modulus, generator)
alice = Participant(params, secret=x)
bob = Participant(params, secret=y)
A = alice.public_value
B = bob.public_value
K_a = alice.compute_shared_key(B)
K_b = bob.compute_shared_key(A)
ok = (K_a == K_b == K_expected)
return ok, (A, B, K_a, K_b)
@@ -0,0 +1,13 @@
def format_line(i, A, B, K_a, K_b, ok):
"""
Форматирует вывод строки для теста.
"""
status = "OK" if ok else "FAIL"
return (
f"#{i:>2}: "
f"A={A:>6} "
f"B={B:>6} "
f"K_A={K_a:>6} "
f"K_B={K_b:>6} "
f"-> {status}"
)
Binary file not shown.