#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
ГОСТ Шифрование - Отечественное блочное шифрование ГОСТ 28147-89

Реализация российского стандарта шифрования:
- Блочный шифр с размером блока 64 бита
- Ключ длиной 256 бит (32 байта)
- 32 раунда шифрования
- S-блоки для нелинейного преобразования

© 2025 NativeMind
"""

import os
import struct
from typing import Union, Tuple
import numpy as np


class GOSTEncryption:
    """
    Реализация ГОСТ 28147-89 - российский стандарт блочного шифрования
    
    Особенности:
    - Размер блока: 64 бита
    - Размер ключа: 256 бит
    - Количество раундов: 32
    - Использует S-блоки для нелинейности
    """
    
    def __init__(self):
        """Инициализация ГОСТ шифрования"""
        self.block_size = 8  # 64 бита = 8 байт
        self.key_size = 32   # 256 бит = 32 байта
        self.rounds = 32
        
        # S-блоки ГОСТ (упрощенные для демонстрации)
        self.s_boxes = self._init_s_boxes()
        
        print("🔐 ГОСТ 28147-89 Шифрование инициализировано")
    
    def _init_s_boxes(self) -> list:
        """Инициализация S-блоков ГОСТ"""
        # Упрощенные S-блоки (в реальности используются стандартные)
        s_boxes = []
        for i in range(8):
            # Генерируем псевдослучайные S-блоки
            np.random.seed(42 + i)  # Детерминированная генерация
            s_box = np.random.randint(0, 16, (16, 16))
            s_boxes.append(s_box)
        return s_boxes
    
    def _split_block(self, block: bytes) -> Tuple[int, int]:
        """
        Разделение 64-битного блока на две 32-битные части
        
        Args:
            block: 8-байтный блок данных
            
        Returns:
            Tuple[int, int]: (левая часть, правая часть)
        """
        if len(block) != 8:
            raise ValueError("Размер блока должен быть 8 байт")
        
        # Разделяем на левую и правую части
        left = struct.unpack('>I', block[:4])[0]
        right = struct.unpack('>I', block[4:])[0]
        
        return left, right
    
    def _join_block(self, left: int, right: int) -> bytes:
        """
        Объединение двух 32-битных частей в 64-битный блок
        
        Args:
            left: Левая 32-битная часть
            right: Правая 32-битная часть
            
        Returns:
            bytes: 8-байтный блок
        """
        return struct.pack('>I', left) + struct.pack('>I', right)
    
    def _f_function(self, data: int, key: int) -> int:
        """
        Функция F ГОСТ (основная функция раунда)
        
        Args:
            data: 32-битные данные
            key: 32-битный ключ раунда
            
        Returns:
            int: Результат функции F
        """
        # Сложение по модулю 2^32
        result = (data + key) & 0xFFFFFFFF
        
        # Применение S-блоков
        for i in range(8):
            # Извлекаем 4-битный сегмент
            segment = (result >> (i * 4)) & 0xF
            # Применяем S-блок
            s_box = self.s_boxes[i]
            # Упрощенное применение S-блока
            new_segment = s_box[segment & 0xF][segment >> 4] if segment < 16 else segment
            # Заменяем сегмент
            result = (result & ~(0xF << (i * 4))) | (new_segment << (i * 4))
        
        # Циклический сдвиг влево на 11 позиций
        result = ((result << 11) | (result >> 21)) & 0xFFFFFFFF
        
        return result
    
    def _generate_round_keys(self, key: bytes) -> list:
        """
        Генерация ключей раундов
        
        Args:
            key: 256-битный ключ
            
        Returns:
            list: Список ключей раундов
        """
        if len(key) != 32:
            raise ValueError("Размер ключа должен быть 32 байта")
        
        # Разделяем ключ на 8 частей по 32 бита
        key_parts = []
        for i in range(8):
            part = struct.unpack('>I', key[i*4:(i+1)*4])[0]
            key_parts.append(part)
        
        # Генерируем ключи для 32 раундов
        round_keys = []
        for round_num in range(32):
            # Используем ключи циклически
            key_index = round_num % 8
            round_keys.append(key_parts[key_index])
        
        return round_keys
    
    def encrypt_block(self, block: bytes, key: bytes) -> bytes:
        """
        Шифрование одного блока ГОСТ
        
        Args:
            block: 8-байтный блок данных
            key: 32-байтный ключ
            
        Returns:
            bytes: Зашифрованный блок
        """
        if len(block) != 8:
            raise ValueError("Размер блока должен быть 8 байт")
        if len(key) != 32:
            raise ValueError("Размер ключа должен быть 32 байта")
        
        # Разделяем блок на части
        left, right = self._split_block(block)
        
        # Генерируем ключи раундов
        round_keys = self._generate_round_keys(key)
        
        # 32 раунда шифрования
        for round_num in range(32):
            # Функция F
            f_result = self._f_function(right, round_keys[round_num])
            
            # XOR с левой частью
            new_right = left ^ f_result
            new_left = right
            
            # Обновляем части
            left, right = new_left, new_right
        
        # Финальная перестановка
        return self._join_block(right, left)
    
    def decrypt_block(self, block: bytes, key: bytes) -> bytes:
        """
        Расшифрование одного блока ГОСТ
        
        Args:
            block: 8-байтный зашифрованный блок
            key: 32-байтный ключ
            
        Returns:
            bytes: Расшифрованный блок
        """
        if len(block) != 8:
            raise ValueError("Размер блока должен быть 8 байт")
        if len(key) != 32:
            raise ValueError("Размер ключа должен быть 32 байта")
        
        # Разделяем блок на части
        left, right = self._split_block(block)
        
        # Генерируем ключи раундов (в обратном порядке)
        round_keys = self._generate_round_keys(key)
        
        # 32 раунда расшифрования (ключи в обратном порядке)
        for round_num in range(31, -1, -1):
            # Функция F
            f_result = self._f_function(right, round_keys[round_num])
            
            # XOR с левой частью
            new_right = left ^ f_result
            new_left = right
            
            # Обновляем части
            left, right = new_left, new_right
        
        # Финальная перестановка
        return self._join_block(right, left)
    
    def encrypt_data(self, data: bytes, key: bytes, mode: str = "ECB") -> bytes:
        """
        Шифрование данных ГОСТ
        
        Args:
            data: Данные для шифрования
            key: Ключ шифрования
            mode: Режим шифрования (ECB, CBC, CFB, OFB)
            
        Returns:
            bytes: Зашифрованные данные
        """
        if len(key) != 32:
            raise ValueError("Размер ключа должен быть 32 байта")
        
        # Дополняем данные до размера блока
        padded_data = self._pad_data(data)
        
        if mode == "ECB":
            return self._encrypt_ecb(padded_data, key)
        elif mode == "CBC":
            return self._encrypt_cbc(padded_data, key)
        elif mode == "CFB":
            return self._encrypt_cfb(padded_data, key)
        elif mode == "OFB":
            return self._encrypt_ofb(padded_data, key)
        else:
            raise ValueError(f"Неподдерживаемый режим: {mode}")
    
    def decrypt_data(self, encrypted_data: bytes, key: bytes, mode: str = "ECB") -> bytes:
        """
        Расшифрование данных ГОСТ
        
        Args:
            encrypted_data: Зашифрованные данные
            key: Ключ расшифрования
            mode: Режим расшифрования
            
        Returns:
            bytes: Расшифрованные данные
        """
        if len(key) != 32:
            raise ValueError("Размер ключа должен быть 32 байта")
        
        if mode == "ECB":
            return self._decrypt_ecb(encrypted_data, key)
        elif mode == "CBC":
            return self._decrypt_cbc(encrypted_data, key)
        elif mode == "CFB":
            return self._decrypt_cfb(encrypted_data, key)
        elif mode == "OFB":
            return self._decrypt_ofb(encrypted_data, key)
        else:
            raise ValueError(f"Неподдерживаемый режим: {mode}")
    
    def _pad_data(self, data: bytes) -> bytes:
        """Дополнение данных до размера блока"""
        padding_length = (8 - (len(data) % 8)) % 8
        if padding_length > 0:
            padding = bytes([padding_length] * padding_length)
            return data + padding
        return data
    
    def _unpad_data(self, data: bytes) -> bytes:
        """Удаление дополнения"""
        if len(data) == 0:
            return data
        
        padding_length = data[-1]
        if padding_length > 8 or padding_length == 0:
            return data
        
        # Проверяем корректность дополнения
        for i in range(padding_length):
            if data[-(i+1)] != padding_length:
                return data
        
        return data[:-padding_length]
    
    def _encrypt_ecb(self, data: bytes, key: bytes) -> bytes:
        """Шифрование в режиме ECB"""
        encrypted = bytearray()
        
        for i in range(0, len(data), 8):
            block = data[i:i+8]
            encrypted_block = self.encrypt_block(block, key)
            encrypted.extend(encrypted_block)
        
        return bytes(encrypted)
    
    def _decrypt_ecb(self, data: bytes, key: bytes) -> bytes:
        """Расшифрование в режиме ECB"""
        decrypted = bytearray()
        
        for i in range(0, len(data), 8):
            block = data[i:i+8]
            decrypted_block = self.decrypt_block(block, key)
            decrypted.extend(decrypted_block)
        
        return self._unpad_data(bytes(decrypted))
    
    def _encrypt_cbc(self, data: bytes, key: bytes, iv: bytes = None) -> bytes:
        """Шифрование в режиме CBC"""
        if iv is None:
            iv = os.urandom(8)
        
        encrypted = bytearray(iv)
        prev_block = iv
        
        for i in range(0, len(data), 8):
            block = data[i:i+8]
            # XOR с предыдущим блоком
            xor_block = bytes(a ^ b for a, b in zip(block, prev_block))
            encrypted_block = self.encrypt_block(xor_block, key)
            encrypted.extend(encrypted_block)
            prev_block = encrypted_block
        
        return bytes(encrypted)
    
    def _decrypt_cbc(self, data: bytes, key: bytes) -> bytes:
        """Расшифрование в режиме CBC"""
        if len(data) < 8:
            raise ValueError("Недостаточно данных для расшифрования")
        
        iv = data[:8]
        encrypted_data = data[8:]
        
        decrypted = bytearray()
        prev_block = iv
        
        for i in range(0, len(encrypted_data), 8):
            block = encrypted_data[i:i+8]
            decrypted_block = self.decrypt_block(block, key)
            # XOR с предыдущим зашифрованным блоком
            xor_block = bytes(a ^ b for a, b in zip(decrypted_block, prev_block))
            decrypted.extend(xor_block)
            prev_block = block
        
        return self._unpad_data(bytes(decrypted))
    
    def _encrypt_cfb(self, data: bytes, key: bytes, iv: bytes = None) -> bytes:
        """Шифрование в режиме CFB"""
        if iv is None:
            iv = os.urandom(8)
        
        encrypted = bytearray(iv)
        prev_block = iv
        
        for i in range(0, len(data), 8):
            block = data[i:i+8]
            # Шифруем предыдущий блок
            encrypted_prev = self.encrypt_block(prev_block, key)
            # XOR с данными
            encrypted_block = bytes(a ^ b for a, b in zip(block, encrypted_prev))
            encrypted.extend(encrypted_block)
            prev_block = encrypted_block
        
        return bytes(encrypted)
    
    def _decrypt_cfb(self, data: bytes, key: bytes) -> bytes:
        """Расшифрование в режиме CFB"""
        if len(data) < 8:
            raise ValueError("Недостаточно данных для расшифрования")
        
        iv = data[:8]
        encrypted_data = data[8:]
        
        decrypted = bytearray()
        prev_block = iv
        
        for i in range(0, len(encrypted_data), 8):
            block = encrypted_data[i:i+8]
            # Шифруем предыдущий блок
            encrypted_prev = self.encrypt_block(prev_block, key)
            # XOR с зашифрованными данными
            decrypted_block = bytes(a ^ b for a, b in zip(block, encrypted_prev))
            decrypted.extend(decrypted_block)
            prev_block = block
        
        return self._unpad_data(bytes(decrypted))
    
    def _encrypt_ofb(self, data: bytes, key: bytes, iv: bytes = None) -> bytes:
        """Шифрование в режиме OFB"""
        if iv is None:
            iv = os.urandom(8)
        
        encrypted = bytearray()
        current_block = iv
        
        for i in range(0, len(data), 8):
            block = data[i:i+8]
            # Шифруем текущий блок
            encrypted_block = self.encrypt_block(current_block, key)
            # XOR с данными
            result_block = bytes(a ^ b for a, b in zip(block, encrypted_block))
            encrypted.extend(result_block)
            current_block = encrypted_block
        
        return bytes(encrypted)
    
    def _decrypt_ofb(self, data: bytes, key: bytes) -> bytes:
        """Расшифрование в режиме OFB (аналогично шифрованию)"""
        return self._encrypt_ofb(data, key)
    
    def generate_key(self) -> bytes:
        """
        Генерация случайного ключа ГОСТ
        
        Returns:
            bytes: 32-байтный ключ
        """
        return os.urandom(32)
    
    def get_key_info(self, key: bytes) -> dict:
        """
        Получение информации о ключе
        
        Args:
            key: Ключ для анализа
            
        Returns:
            dict: Информация о ключе
        """
        if len(key) != 32:
            return {'error': 'Неверный размер ключа'}
        
        # Анализ энтропии ключа
        entropy = self._calculate_entropy(key)
        
        # Проверка на повторяющиеся байты
        unique_bytes = len(set(key))
        
        return {
            'size': len(key),
            'entropy': entropy,
            'unique_bytes': unique_bytes,
            'entropy_ratio': unique_bytes / 256,
            'is_strong': entropy > 7.0 and unique_bytes > 200
        }
    
    def _calculate_entropy(self, data: bytes) -> float:
        """Вычисление энтропии Шеннона"""
        if not data:
            return 0.0
        
        # Подсчет частоты каждого байта
        byte_counts = {}
        for byte in data:
            byte_counts[byte] = byte_counts.get(byte, 0) + 1
        
        # Вычисление энтропии
        entropy = 0.0
        data_len = len(data)
        
        for count in byte_counts.values():
            probability = count / data_len
            if probability > 0:
                entropy -= probability * np.log2(probability)
        
        return entropy


# Создание глобального экземпляра
gost_encryption = GOSTEncryption()

# Алиасы для совместимости
encrypt_data = gost_encryption.encrypt_data
decrypt_data = gost_encryption.decrypt_data
generate_key = gost_encryption.generate_key