Загрузка данных


import random
import string
import math
from pathlib import Path
import tkinter as tk
from tkinter import ttk, messagebox


# ================== ЛОГИКА РАБОТЫ С ПАРОЛЯМИ (без GUI) ==================


def generate_password(length: int,
                      use_letters: bool = True,
                      use_digits: bool = True,
                      use_symbols: bool = True) -> str:
    """
    Генерация случайного пароля заданной длины.

    :param length: длина пароля в символах
    :param use_letters: использовать ли буквы (латинские, строчные и заглавные)
    :param use_digits: использовать ли цифры
    :param use_symbols: использовать ли спецсимволы
    :return: сгенерированный пароль в виде строки
    """
    # Формируем "алфавит" символов, из которых можно собирать пароль
    alphabet = ""
    if use_letters:
        # string.ascii_letters содержит 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
        alphabet += string.ascii_letters
    if use_digits:
        # string.digits содержит '0123456789'
        alphabet += string.digits
    if use_symbols:
        # Набор спецсимволов задаём вручную
        alphabet += "!@#$%^&*()-_=+[]{};:,.?/"

    # Если пользователь отключил все типы символов — это ошибка
    if not alphabet:
        raise ValueError("Нужно выбрать хотя бы один тип символов")

    # Собираем пароль из случайных символов выбранного алфавита
    return "".join(random.choice(alphabet) for _ in range(length))


def load_common_passwords(path: str = "common_passwords.txt") -> set[str]:
    """
    Загружает список распространённых паролей из файла.

    Предполагается, что в файле по одному паролю в строке.
    Используем множество (set), чтобы быстро проверять вхождение.

    :param path: путь к файлу со списком паролей
    :return: множество строк-паролей
    """
    p = Path(path)
    # Если файла нет, просто возвращаем пустое множество,
    # чтобы программа не падала, а работала без этой проверки
    if not p.exists():
        return set()

    # Читаем файл целиком, разбиваем по строкам, обрезаем пробелы
    return {
        line.strip()
        for line in p.read_text(encoding="utf-8").splitlines()
        if line.strip()  # пропускаем пустые строки
    }


def check_strength(password: str, common_passwords: set[str]) -> dict:
    """
    Проверяет пароль по нескольким критериям:
    - длина
    - наличие строчных/заглавных букв, цифр, спецсимволов
    - присутствие в списке популярных паролей

    Возвращает словарь с подробной информацией и итоговым уровнем.
    """
    length = len(password)
    # Проверяем, есть ли хотя бы один символ каждого типа
    has_lower = any(c.islower() for c in password)
    has_upper = any(c.isupper() for c in password)
    has_digit = any(c.isdigit() for c in password)
    # not c.isalnum() — символ не является буквой или цифрой, значит спецсимвол
    has_symbol = any(not c.isalnum() for c in password)

    # Проверяем, входит ли пароль в список распространённых
    in_common = password in common_passwords

    # Подсчитываем "очки" надёжности
    score = 0
    if length >= 8:
        score += 1
    if length >= 12:
        score += 1
    if has_lower and has_upper:
        score += 1
    if has_digit:
        score += 1
    if has_symbol:
        score += 1
    # Если пароль часто встречается, независимо от остальных критериев — считаем его слабым
    if in_common:
        score = 0

    # Переводим числовой балл в текстовый уровень
    if score <= 1:
        level = "слабый"
    elif score <= 3:
        level = "средний"
    else:
        level = "сильный"

    # Возвращаем подробный отчёт в виде словаря
    return {
        "length": length,
        "has_lower": has_lower,
        "has_upper": has_upper,
        "has_digit": has_digit,
        "has_symbol": has_symbol,
        "in_common": in_common,
        "score": score,
        "level": level,
    }


# Небольшой встроенный словарь слов для генерации парольных фраз
WORDS = [
    "cat", "dog", "tree", "cloud", "river", "stone", "mouse", "green",
    "red", "blue", "star", "music", "black", "white", "coffee", "book",
    "sun", "moon", "road", "car"
]


def generate_passphrase(num_words: int = 4, add_number: bool = True) -> str:
    """
    Генерирует запоминающуюся парольную фразу из нескольких случайных слов.

    :param num_words: сколько слов использовать в фразе
    :param add_number: добавлять ли в конце случайное число
    :return: фраза вида 'cat-dog-tree42'
    """
    # Выбираем нужное количество случайных слов из списка WORDS
    words = [random.choice(WORDS) for _ in range(num_words)]
    # Соединяем слова через дефис
    phrase = "-".join(words)
    # По желанию добавляем в конец случайное число от 0 до 99
    if add_number:
        phrase += str(random.randint(0, 99))
    return phrase


def estimate_bruteforce_time(password: str,
                             attempts_per_second: float = 1e9) -> dict:
    """
    Оценивает время перебора пароля методом грубой силы (brute force).

    Предполагаем, что атакующий знает, какие типы символов используются,
    и перебирает все возможные комбинации заданного алфавита.

    :param password: пароль, который оцениваем
    :param attempts_per_second: предполагаемая скорость перебора (попыток в секунду)
    :return: словарь с размером алфавита, числом комбинаций и временем в секундах/человекочитаемом виде
    """
    length = len(password)
    alphabet_size = 0

    # Определяем, какие типы символов присутствуют в пароле,
    # и по этому оцениваем размер алфавита
    if any(c.islower() for c in password):
        alphabet_size += 26  # строчные латинские буквы
    if any(c.isupper() for c in password):
        alphabet_size += 26  # заглавные латинские буквы
    if any(c.isdigit() for c in password):
        alphabet_size += 10  # цифры 0-9
    if any(not c.isalnum() for c in password):
        # Грубо считаем около 32 возможных спецсимволов
        alphabet_size += 32

    # Если пароль пустой или алфавит не определён — время взлома считать бессмысленно
    if alphabet_size == 0 or length == 0:
        return {
            "seconds": 0,
            "formatted": "0 секунд",
            "alphabet_size": 0,
            "combinations": 0,
        }

    # Общее число возможных комбинаций: A^L (A — размер алфавита, L — длина пароля)
    combinations = alphabet_size ** length
    # Время (в секундах) = количество комбинаций / скорость перебора
    seconds = combinations / attempts_per_second

    # Внутренняя функция для красивого форматирования времени
    def format_time(sec: float) -> str:
        # Меньше минуты — показываем в секундах
        if sec < 60:
            return f"{sec:.2f} секунд"
        minutes = sec / 60
        # Меньше часа — в минутах
        if minutes < 60:
            return f"{minutes:.2f} минут"
        hours = minutes / 60
        # Меньше суток — в часах
        if hours < 24:
            return f"{hours:.2f} часов"
        days = hours / 24
        # Меньше года — в днях
        if days < 365:
            return f"{days:.2f} дней"
        years = days / 365
        # Больше года — в годах в экспоненциальном виде
        return f"{years:.2e} лет"

    # Возвращаем подробную информацию
    return {
        "alphabet_size": alphabet_size,
        "combinations": combinations,
        "seconds": seconds,
        "formatted": format_time(seconds),
    }


# ================== TKINTER GUI ==================


class PasswordApp(tk.Tk):
    """
    Класс графического приложения на Tkinter.

    Наследуемся от tk.Tk, чтобы описать окно и все его виджеты.
    """

    def __init__(self):
        # Инициализируем базовый класс окна
        super().__init__()

        # Задаём заголовок окна и размер
        self.title("Генератор и проверка паролей")
        self.geometry("700x450")

        # Один раз загружаем список популярных паролей
        self.common_passwords = load_common_passwords()

        # Создаём виджет Notebook — это набор вкладок (как в браузере)
        notebook = ttk.Notebook(self)
        # Размещаем notebook так, чтобы он занимал всё окно
        notebook.pack(fill="both", expand=True, padx=10, pady=10)

        # Создаём отдельный фрейм (страницу) под каждую задачу
        self.tab_gen = ttk.Frame(notebook)
        self.tab_check = ttk.Frame(notebook)
        self.tab_phrase = ttk.Frame(notebook)
        self.tab_bruteforce = ttk.Frame(notebook)

        # Добавляем страницы во вкладки с подписями
        notebook.add(self.tab_gen, text="Генерация пароля")
        notebook.add(self.tab_check, text="Проверка пароля")
        notebook.add(self.tab_phrase, text="Парольная фраза")
        notebook.add(self.tab_bruteforce, text="Время взлома")

        # Инициализируем содержимое каждой вкладки
        self.create_gen_tab()
        self.create_check_tab()
        self.create_phrase_tab()
        self.create_bruteforce_tab()

    # ---------- Вкладка 1: генерация пароля ----------

    def create_gen_tab(self):
        """Создание элементов интерфейса для вкладки генерации пароля."""
        frame = self.tab_gen

        # Переменные Tkinter, к которым будут "привязаны" виджеты
        self.gen_length_var = tk.IntVar(value=12)          # длина пароля
        self.gen_letters_var = tk.BooleanVar(value=True)   # использовать буквы
        self.gen_digits_var = tk.BooleanVar(value=True)    # использовать цифры
        self.gen_symbols_var = tk.BooleanVar(value=True)   # использовать спецсимволы
        self.gen_result_var = tk.StringVar()               # результат (сам пароль)

        # Подпись и поле ввода длины пароля
        ttk.Label(frame, text="Длина пароля:").grid(row=0, column=0, sticky="w", padx=5, pady=5)
        ttk.Spinbox(frame, from_=4, to=64, textvariable=self.gen_length_var, width=5).grid(
            row=0, column=1, sticky="w", padx=5, pady=5
        )

        # Чекбоксы для выбора типов символов
        ttk.Checkbutton(frame, text="Буквы (a-z, A-Z)", variable=self.gen_letters_var).grid(
            row=1, column=0, columnspan=2, sticky="w", padx=5, pady=2
        )
        ttk.Checkbutton(frame, text="Цифры (0-9)", variable=self.gen_digits_var).grid(
            row=2, column=0, columnspan=2, sticky="w", padx=5, pady=2
        )
        ttk.Checkbutton(frame, text="Спецсимволы (!@#$...)", variable=self.gen_symbols_var).grid(
            row=3, column=0, columnspan=2, sticky="w", padx=5, pady=2
        )

        # Кнопка генерации
        ttk.Button(frame, text="Сгенерировать пароль", command=self.on_generate_password).grid(
            row=4, column=0, columnspan=2, pady=10
        )

        # Поле для вывода результата
        ttk.Label(frame, text="Результат:").grid(row=5, column=0, sticky="w", padx=5, pady=5)
        ttk.Entry(frame, textvariable=self.gen_result_var, width=40).grid(
            row=5, column=1, sticky="w", padx=5, pady=5
        )

    def on_generate_password(self):
        """Обработчик нажатия кнопки 'Сгенерировать пароль'."""
        try:
            # Берём длину и флаги из GUI-переменных
            length = int(self.gen_length_var.get())
            pwd = generate_password(
                length,
                self.gen_letters_var.get(),
                self.gen_digits_var.get(),
                self.gen_symbols_var.get(),
            )
            # Кладём результат в StringVar, к которому привязан Entry
            self.gen_result_var.set(pwd)
        except ValueError as e:
            # Показываем всплывающее окно с сообщением об ошибке
            messagebox.showerror("Ошибка", str(e))

    # ---------- Вкладка 2: проверка пароля ----------

    def create_check_tab(self):
        """Создание элементов интерфейса для вкладки проверки пароля."""
        frame = self.tab_check

        self.check_input_var = tk.StringVar()  # сюда пользователь вводит пароль для проверки

        ttk.Label(frame, text="Пароль для проверки:").grid(
            row=0, column=0, sticky="w", padx=5, pady=5
        )
        ttk.Entry(frame, textvariable=self.check_input_var, width=40, show="*").grid(
            row=0, column=1, sticky="w", padx=5, pady=5
        )

        ttk.Button(frame, text="Проверить надёжность", command=self.on_check_password).grid(
            row=1, column=0, columnspan=2, pady=10
        )

        # Многострочный текстовый виджет для вывода отчёта
        self.check_result_text = tk.Text(frame, height=10, width=60, state="disabled")
        self.check_result_text.grid(row=2, column=0, columnspan=2, padx=5, pady=5)

    def on_check_password(self):
        """Обработчик нажатия кнопки 'Проверить надёжность'."""
        pwd = self.check_input_var.get()
        if not pwd:
            messagebox.showwarning("Внимание", "Введите пароль для проверки.")
            return

        info = check_strength(pwd, self.common_passwords)

        # Разрешаем запись в Text
        self.check_result_text.config(state="normal")
        # Очищаем предыдущее содержимое
        self.check_result_text.delete("1.0", "end")

        # Печатаем подробную информацию по паролю
        self.check_result_text.insert("end", f"Длина: {info['length']}\n")
        self.check_result_text.insert(
            "end",
            f"Строчные буквы: {info['has_lower']}\n"
            f"Заглавные буквы: {info['has_upper']}\n"
            f"Цифры: {info['has_digit']}\n"
            f"Спецсимволы: {info['has_symbol']}\n",
        )
        self.check_result_text.insert("end", f"В списке популярных: {info['in_common']}\n")
        self.check_result_text.insert("end", f"Уровень надёжности: {info['level']}\n")

        # Запрещаем редактирование пользователем
        self.check_result_text.config(state="disabled")

    # ---------- Вкладка 3: генерация парольной фразы ----------

    def create_phrase_tab(self):
        """Создание элементов интерфейса для вкладки парольной фразы."""
        frame = self.tab_phrase

        self.phrase_num_words_var = tk.IntVar(value=4)      # сколько слов во фразе
        self.phrase_add_number_var = tk.BooleanVar(value=True)  # добавлять ли число
        self.phrase_result_var = tk.StringVar()             # результат

        ttk.Label(frame, text="Количество слов:").grid(
            row=0, column=0, sticky="w", padx=5, pady=5
        )
        ttk.Spinbox(frame, from_=2, to=10, textvariable=self.phrase_num_words_var, width=5).grid(
            row=0, column=1, sticky="w", padx=5, pady=5
        )

        ttk.Checkbutton(frame, text="Добавить число в конец", variable=self.phrase_add_number_var).grid(
            row=1, column=0, columnspan=2, sticky="w", padx=5, pady=5
        )

        ttk.Button(frame, text="Сгенерировать фразу", command=self.on_generate_phrase).grid(
            row=2, column=0, columnspan=2, pady=10
        )

        ttk.Label(frame, text="Результат:").grid(row=3, column=0, sticky="w", padx=5, pady=5)
        ttk.Entry(frame, textvariable=self.phrase_result_var, width=50).grid(
            row=3, column=1, sticky="w", padx=5, pady=5
        )

    def on_generate_phrase(self):
        """Обработчик нажатия кнопки 'Сгенерировать фразу'."""
        try:
            n = int(self.phrase_num_words_var.get())
        except ValueError:
            messagebox.showerror("Ошибка", "Некорректное число слов.")
            return

        phrase = generate_passphrase(n, self.phrase_add_number_var.get())
        self.phrase_result_var.set(phrase)

    # ---------- Вкладка 4: оценка времени взлома ----------

    def create_bruteforce_tab(self):
        """Создание элементов интерфейса для вкладки оценки времени взлома."""
        frame = self.tab_bruteforce

        self.brute_password_var = tk.StringVar()   # пароль для оценки
        self.brute_speed_var = tk.StringVar(value="1000000000")  # скорость перебора (1e9 по умолчанию)

        ttk.Label(frame, text="Пароль:").grid(row=0, column=0, sticky="w", padx=5, pady=5)
        ttk.Entry(frame, textvariable=self.brute_password_var, width=40, show="*").grid(
            row=0, column=1, sticky="w", padx=5, pady=5
        )

        ttk.Label(frame, text="Скорость перебора (попыток/сек):").grid(
            row=1, column=0, sticky="w", padx=5, pady=5
        )
        ttk.Entry(frame, textvariable=self.brute_speed_var, width=20).grid(
            row=1, column=1, sticky="w", padx=5, pady=5
        )

        ttk.Button(frame, text="Оценить время взлома", command=self.on_bruteforce_estimate).grid(
            row=2, column=0, columnspan=2, pady=10
        )

        self.brute_result_text = tk.Text(frame, height=10, width=60, state="disabled")
        self.brute_result_text.grid(row=3, column=0, columnspan=2, padx=5, pady=5)

    def on_bruteforce_estimate(self):
        """Обработчик нажатия кнопки 'Оценить время взлома'."""
        pwd = self.brute_password_var.get()
        if not pwd:
            messagebox.showwarning("Внимание", "Введите пароль.")
            return

        speed_str = self.brute_speed_var.get().strip()
        # Если пользователь что-то ввёл — пытаемся преобразовать в число
        if speed_str:
            try:
                attempts_per_second = float(speed_str)
            except ValueError:
                messagebox.showerror("Ошибка", "Некорректная скорость перебора.")
                return
        else:
            # Если поле пустое, используем значение по умолчанию
            attempts_per_second = 1e9

        est = estimate_bruteforce_time(pwd, attempts_per_second)

        self.brute_result_text.config(state="normal")
        self.brute_result_text.delete("1.0", "end")

        self.brute_result_text.insert("end", f"Размер алфавита: {est['alphabet_size']}\n")
        self.brute_result_text.insert("end", f"Количество вариантов: {est['combinations']}\n")
        self.brute_result_text.insert("end", f"Оценка времени перебора: {est['formatted']}\n")

        self.brute_result_text.config(state="disabled")


# ================== ТОЧКА ВХОДА ==================


def main():
    """
    Главная точка входа в программу.

    Здесь мы создаём экземпляр нашего графического приложения
    и запускаем главный цикл обработки событий Tkinter.
    """
    app = PasswordApp()
    app.mainloop()  # запускаем цикл обработки событий (ожидание кликов, ввода и т.д.)


# Запускаем main() только если файл исполнен как скрипт,
# а не импортирован как модуль
if __name__ == "__main__":
    main()