1
0
Просмотр исходного кода

Загрузить файлы 'ОАиП/2025-26/36гр/1 сем/Маркеев'

u23-27markeev 1 неделя назад
Родитель
Сommit
f582a0686d

+ 63 - 0
ОАиП/2025-26/36гр/1 сем/Маркеев/README (4).md

@@ -0,0 +1,63 @@
+Техническое задание на разработку генератора надёжных паролей PRO
+1. Назначение программы
+Программа предназначена для генерации надёжных паролей, оценки их качества, хранения истории созданных паролей и анализа статистики использования паролей пользователями. Она позволяет пользователям создавать уникальные и безопасные пароли, управлять своими учетными записями и улучшать безопасность онлайн-аккаунтов.
+
+2. Основные функциональные требования
+2.1 Генерация паролей
+Пользователь должен иметь возможность задать настройки генерации пароля, включая:
+
+длину пароля (от 8 до 32 символов);
+включение букв верхнего и нижнего регистра (a-z, A-Z);
+использование цифр (0-9);
+применение специальных символов (!@#$%^&*()).Дополнительно предоставляются опции:
+исключение похожих символов (например, i, l, I, L, o, O, 0, 1);
+удаление неоднозначных символов (таких как {}, [], (), <>, \, |, ~, `).
+2.2 Оценка надёжности пароля
+При создании нового пароля программа должна автоматически оценивать его качество, учитывая:
+
+длину пароля;
+разнообразие используемых символов (буквы, цифры, спецсимволы);
+наличие повторяющихся символов.По результатам оценки пользователю предлагается рейтинг («очень надёжный», «надёжный», «средний», «слабый») и рекомендации по улучшению пароля.
+2.3 Хранение истории паролей
+Все созданные пароли сохраняются в локальной базе данных SQLite с указанием даты и времени создания, а также уровня надёжности. Пользователи могут просматривать историю ранее созданных паролей, фильтруя их по уровню защиты.
+
+2.4 Интерфейс пользователя
+Приложение должно обладать удобным графическим интерфейсом с возможностью управления параметрами генерации, копирования пароля в буфер обмена, сохранения текущего пароля в базу данных и очистки истории паролей.
+
+2.5 Дополнительные возможности
+Программа должна поддерживать:
+
+экспорт всей истории паролей в текстовый файл;
+сброс настроек к стандартным параметрам;
+вывод рекомендаций по созданию безопасных паролей;
+создание статистического отчёта по использованию паролей (количество слабых/средних/надежных паролей, временные рамки использования паролей).
+3. Архитектура проекта
+Проект построен вокруг трех основных компонентов:
+
+3.1 Графический интерфейс (UI)
+Реализован с использованием библиотеки Tkinter, обеспечивающей удобный доступ к основным элементам GUI (окнам, кнопкам, меню и другим виджетам). Интерфейс состоит из фреймов, панелей, кнопок и полей ввода, позволяющих пользователю легко взаимодействовать с программой.
+
+3.2 Логика приложения
+Основные операции, такие как генерация пароля, оценка его силы, сохранение и обработка данных реализованы в классе PasswordGenerator. Этот класс включает методы для обработки запросов пользователей, вычисления надежности пароля и взаимодействия с базой данных.
+
+3.3 Базовая система хранения данных
+Для хранения истории паролей используется встроенная база данных SQLite. Таблица хранит записи о каждом созданном пароле вместе с уровнем его надежности и меткой времени. Данные хранятся локально и позволяют быстро извлекать необходимую информацию.
+
+4. Требования к качеству разработки
+Приложение должно обеспечивать высокую производительность при взаимодействии с базой данных.
+Программа должна защищать конфиденциальность пользователя путем маскировки сохраненных паролей и предоставления возможностей удаления старых записей.
+Интуитивно понятный и простой в использовании интерфейс.
+Минимальные системные требования: Python версии 3.x и стандартные модули (Tkinter, sqlite3).
+5. Пример реализации интерфейса
+import tkinter as tk
+from tkinter import messagebox, scrolledtext, ttk
+...
+Интерфейс представлен в следующем виде:
+
+Основное окно с заголовком «Генератор надёжных паролей».
+Поле для вывода сгенерированного пароля.
+Кнопки для операций над паролем (копирование, проверка надёжности, сохранение).
+Список предыдущих паролей с фильтрацией по силе и датам.
+Пространство для настройки параметров генерации пароля (длина, используемые символы, исключения некоторых символов).
+Строка состояния внизу окна для отображения текущих статусов.
+Таким образом, разработанная программа позволит пользователям эффективно создавать и хранить надежные пароли, обеспечивая защиту персональных данных и облегчая управление учетными записями.

+ 731 - 0
ОАиП/2025-26/36гр/1 сем/Маркеев/Генерация безопасных паролей.py

@@ -0,0 +1,731 @@
+import tkinter as tk
+from tkinter import messagebox, scrolledtext, ttk
+import string
+import random
+import sqlite3
+from datetime import datetime
+import json
+import os
+
+
+class PasswordGenerator:
+    def __init__(self):
+        self.root = tk.Tk()
+        self.root.title("Генератор надежных паролей PRO")
+        self.root.geometry("750x700")
+        
+        # Инициализация базы данных ДО создания интерфейса
+        self.init_database()
+        
+        self.setup_ui()
+        
+        # Переменные для настроек
+        self.current_password = ""
+        self.history_limit = 15
+        self.config_file = "config.json"
+        self.load_config()
+        
+    def setup_ui(self):
+        # Создание стилей
+        style = ttk.Style()
+        style.configure('Custom.TButton', font=('Arial', 10, 'bold'))
+        style.configure('Title.TLabel', font=('Arial', 16, 'bold'))
+        
+        # Главный контейнер
+        main_frame = ttk.Frame(self.root, padding="20")
+        main_frame.pack(fill=tk.BOTH, expand=True)
+        
+        # Заголовок
+        title_label = ttk.Label(main_frame, text="🔐 ГЕНЕРАТОР НАДЕЖНЫХ ПАРОЛЕЙ", 
+                               style='Title.TLabel')
+        title_label.pack(pady=(0, 20))
+        
+        # Фрейм настроек
+        settings_frame = ttk.LabelFrame(main_frame, text="Настройки генерации", padding="15")
+        settings_frame.pack(fill=tk.X, pady=(0, 15))
+        
+        # Длина пароля
+        length_frame = ttk.Frame(settings_frame)
+        length_frame.pack(fill=tk.X, pady=5)
+        
+        self.length_label = ttk.Label(length_frame, text="Длина пароля:", width=15)
+        self.length_label.pack(side=tk.LEFT)
+        
+        self.length_var = tk.IntVar(value=16)
+        self.length_slider = ttk.Scale(length_frame, from_=8, to=32, 
+                                      variable=self.length_var, orient=tk.HORIZONTAL)
+        self.length_slider.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=10)
+        
+        self.length_display = ttk.Label(length_frame, textvariable=self.length_var, width=3)
+        self.length_display.pack(side=tk.LEFT)
+        
+        # Чекбоксы для символов
+        self.letters_var = tk.BooleanVar(value=True)
+        self.digits_var = tk.BooleanVar(value=True)
+        self.punctuation_var = tk.BooleanVar(value=True)
+        self.exclude_similar = tk.BooleanVar(value=True)
+        self.exclude_ambiguous = tk.BooleanVar(value=False)
+        
+        checks_frame = ttk.Frame(settings_frame)
+        checks_frame.pack(fill=tk.X, pady=10)
+        
+        ttk.Checkbutton(checks_frame, text="Буквы (A-Z, a-z)", 
+                       variable=self.letters_var).pack(side=tk.LEFT, padx=10)
+        ttk.Checkbutton(checks_frame, text="Цифры (0-9)", 
+                       variable=self.digits_var).pack(side=tk.LEFT, padx=10)
+        ttk.Checkbutton(checks_frame, text="Спецсимволы (!@#$%)", 
+                       variable=self.punctuation_var).pack(side=tk.LEFT, padx=10)
+        
+        advanced_frame = ttk.Frame(settings_frame)
+        advanced_frame.pack(fill=tk.X, pady=5)
+        
+        ttk.Checkbutton(advanced_frame, text="Исключить похожие символы (i, l, 1, L, o, 0, O)", 
+                       variable=self.exclude_similar).pack(side=tk.LEFT, padx=10)
+        ttk.Checkbutton(advanced_frame, text="Исключить неоднозначные символы ({[]}<>\\|)", 
+                       variable=self.exclude_ambiguous).pack(side=tk.LEFT, padx=10)
+        
+        # Кнопка генерации
+        self.generate_btn = ttk.Button(main_frame, text="⚡ СГЕНЕРИРОВАТЬ ПАРОЛЬ", 
+                                      command=self.generate_password, 
+                                      style='Custom.TButton')
+        self.generate_btn.pack(pady=10)
+        
+        # Отображение пароля
+        result_frame = ttk.LabelFrame(main_frame, text="Сгенерированный пароль", padding="15")
+        result_frame.pack(fill=tk.X, pady=(0, 15))
+        
+        self.password_var = tk.StringVar()
+        self.password_entry = ttk.Entry(result_frame, textvariable=self.password_var, 
+                                       font=('Courier', 14), justify='center')
+        self.password_entry.pack(fill=tk.X)
+        
+        # Кнопки действий с паролем
+        action_frame = ttk.Frame(main_frame)
+        action_frame.pack(pady=(0, 15))
+        
+        ttk.Button(action_frame, text="📋 Копировать", 
+                  command=self.copy_to_clipboard).pack(side=tk.LEFT, padx=5)
+        ttk.Button(action_frame, text="💾 Сохранить", 
+                  command=self.save_password).pack(side=tk.LEFT, padx=5)
+        ttk.Button(action_frame, text="🔄 Новый", 
+                  command=self.generate_password).pack(side=tk.LEFT, padx=5)
+        ttk.Button(action_frame, text="🔍 Проверить надежность", 
+                  command=self.check_strength).pack(side=tk.LEFT, padx=5)
+        
+        # История паролей
+        history_frame = ttk.LabelFrame(main_frame, text="История паролей", padding="15")
+        history_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 15))
+        
+        # Панель инструментов истории
+        history_toolbar = ttk.Frame(history_frame)
+        history_toolbar.pack(fill=tk.X, pady=(0, 10))
+        
+        ttk.Button(history_toolbar, text="🔄 Обновить", 
+                  command=self.load_history).pack(side=tk.LEFT, padx=2)
+        ttk.Button(history_toolbar, text="🗑️ Очистить", 
+                  command=self.clear_history_confirm).pack(side=tk.LEFT, padx=2)
+        ttk.Button(history_toolbar, text="📤 Экспорт", 
+                  command=self.export_history).pack(side=tk.LEFT, padx=2)
+        ttk.Button(history_toolbar, text="📊 Статистика", 
+                  command=self.show_statistics).pack(side=tk.LEFT, padx=2)
+        
+        # Таблица истории
+        columns = ('id', 'password', 'strength', 'timestamp')
+        self.history_tree = ttk.Treeview(history_frame, columns=columns, show='headings', height=8)
+        
+        self.history_tree.heading('id', text='ID')
+        self.history_tree.heading('password', text='Пароль')
+        self.history_tree.heading('strength', text='Надежность')
+        self.history_tree.heading('timestamp', text='Время создания')
+        
+        self.history_tree.column('id', width=50, anchor='center')
+        self.history_tree.column('password', width=200)
+        self.history_tree.column('strength', width=100, anchor='center')
+        self.history_tree.column('timestamp', width=150)
+        
+        scrollbar = ttk.Scrollbar(history_frame, orient=tk.VERTICAL, command=self.history_tree.yview)
+        self.history_tree.configure(yscrollcommand=scrollbar.set)
+        
+        self.history_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
+        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
+        
+        # Привязка событий
+        self.history_tree.bind('<Double-Button-1>', self.on_history_select)
+        
+        # Статус бар
+        self.status_bar = ttk.Label(main_frame, text="Готов к работе", relief=tk.SUNKEN)
+        self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)
+        
+        # Меню
+        self.create_menu()
+        
+    def create_menu(self):
+        menubar = tk.Menu(self.root)
+        self.root.config(menu=menubar)
+        
+        # Меню Файл
+        file_menu = tk.Menu(menubar, tearoff=0)
+        menubar.add_cascade(label="Файл", menu=file_menu)
+        file_menu.add_command(label="Сохранить настройки", command=self.save_config)
+        file_menu.add_command(label="Загрузить настройки", command=self.load_config)
+        file_menu.add_separator()
+        file_menu.add_command(label="Экспорт всех паролей", command=self.export_all_passwords)
+        file_menu.add_separator()
+        file_menu.add_command(label="Выход", command=self.root.quit)
+        
+        # Меню Настройки
+        settings_menu = tk.Menu(menubar, tearoff=0)
+        menubar.add_cascade(label="Настройки", menu=settings_menu)
+        settings_menu.add_command(label="Ограничение истории", 
+                                 command=self.set_history_limit)
+        settings_menu.add_command(label="Сбросить настройки", 
+                                 command=self.reset_settings)
+        
+        # Меню Справка
+        help_menu = tk.Menu(menubar, tearoff=0)
+        menubar.add_cascade(label="Справка", menu=help_menu)
+        help_menu.add_command(label="О программе", command=self.show_about)
+        help_menu.add_command(label="Рекомендации", command=self.show_tips)
+        
+    def init_database(self):
+        try:
+            conn = sqlite3.connect('passwords.db')
+            cursor = conn.cursor()
+            
+            # Проверяем существование таблицы
+            cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='passwords'")
+            table_exists = cursor.fetchone()
+            
+            if table_exists:
+                # Проверяем структуру существующей таблицы
+                cursor.execute("PRAGMA table_info(passwords)")
+                columns = cursor.fetchall()
+                column_names = [col[1] for col in columns]
+                
+                # Если таблица создавалась по старому коду, пересоздаем её
+                if 'generated_password' in column_names:
+                    # Сохраняем старые данные
+                    cursor.execute("SELECT generated_password, timestamp FROM passwords")
+                    old_data = cursor.fetchall()
+                    
+                    # Удаляем старую таблицу
+                    cursor.execute("DROP TABLE passwords")
+                    
+                    # Создаем новую таблицу
+                    cursor.execute('''
+                        CREATE TABLE passwords (
+                            id INTEGER PRIMARY KEY AUTOINCREMENT,
+                            password TEXT NOT NULL,
+                            strength TEXT,
+                            timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
+                        )
+                    ''')
+                    
+                    # Восстанавливаем данные
+                    for data in old_data:
+                        password = data[0]
+                        timestamp = data[1]
+                        strength = self.calculate_strength(password)
+                        cursor.execute('''
+                            INSERT INTO passwords (password, strength, timestamp)
+                            VALUES (?, ?, ?)
+                        ''', (password, strength, timestamp))
+                elif 'password' not in column_names:
+                    # Таблица существует, но нет колонки password - добавляем
+                    cursor.execute('''
+                        ALTER TABLE passwords ADD COLUMN password TEXT
+                    ''')
+            else:
+                # Создаем новую таблицу
+                cursor.execute('''
+                    CREATE TABLE passwords (
+                        id INTEGER PRIMARY KEY AUTOINCREMENT,
+                        password TEXT NOT NULL,
+                        strength TEXT,
+                        timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
+                    )
+                ''')
+            
+            conn.commit()
+            conn.close()
+            
+        except Exception as e:
+            messagebox.showerror("Ошибка", f"Ошибка инициализации БД: {str(e)}")
+    
+    def generate_password(self):
+        try:
+            length = self.length_var.get()
+            selected_chars = ''
+            
+            if self.letters_var.get():
+                letters = string.ascii_letters
+                if self.exclude_similar.get():
+                    letters = letters.replace('i', '').replace('l', '').replace('I', '').replace('L', '')
+                    letters = letters.replace('o', '').replace('O', '')
+                selected_chars += letters
+                
+            if self.digits_var.get():
+                digits = string.digits
+                if self.exclude_similar.get():
+                    digits = digits.replace('0', '').replace('1', '')
+                selected_chars += digits
+                
+            if self.punctuation_var.get():
+                punctuation = string.punctuation
+                if self.exclude_ambiguous.get():
+                    ambiguous = '{}[]()<>\\|`~'
+                    for char in ambiguous:
+                        punctuation = punctuation.replace(char, '')
+                selected_chars += punctuation
+            
+            if not selected_chars:
+                messagebox.showwarning("Предупреждение", 
+                                     "Выберите хотя бы один тип символов!")
+                return
+            
+            # Гарантируем хотя бы по одному символу каждого выбранного типа
+            password_chars = []
+            if self.letters_var.get():
+                letters = string.ascii_letters
+                if self.exclude_similar.get():
+                    letters = letters.replace('i', '').replace('l', '').replace('I', '').replace('L', '')
+                    letters = letters.replace('o', '').replace('O', '')
+                password_chars.append(random.choice(letters))
+                length -= 1
+                    
+            if self.digits_var.get():
+                digits = string.digits
+                if self.exclude_similar.get():
+                    digits = digits.replace('0', '').replace('1', '')
+                password_chars.append(random.choice(digits))
+                length -= 1
+                
+            if self.punctuation_var.get():
+                punctuation = string.punctuation
+                if self.exclude_ambiguous.get():
+                    ambiguous = '{}[]()<>\\|`~'
+                    for char in ambiguous:
+                        punctuation = punctuation.replace(char, '')
+                password_chars.append(random.choice(punctuation))
+                length -= 1
+            
+            # Добавляем остальные случайные символы
+            for _ in range(max(0, length)):
+                password_chars.append(random.choice(selected_chars))
+            
+            # Перемешиваем символы
+            random.shuffle(password_chars)
+            password = ''.join(password_chars)
+            
+            self.current_password = password
+            self.password_var.set(password)
+            
+            # Определяем цвет в зависимости от надежности
+            strength = self.calculate_strength(password)
+            color = self.get_strength_color(strength)
+            self.password_entry.config(foreground=color)
+            
+            self.status_bar.config(text=f"Пароль сгенерирован | Надежность: {strength}")
+            
+        except Exception as e:
+            messagebox.showerror("Ошибка", f"Произошла ошибка: {str(e)}")
+    
+    def calculate_strength(self, password):
+        score = 0
+        
+        # Длина
+        if len(password) >= 20:
+            score += 3
+        elif len(password) >= 16:
+            score += 2
+        elif len(password) >= 12:
+            score += 1
+            
+        # Разнообразие символов
+        has_upper = any(c.isupper() for c in password)
+        has_lower = any(c.islower() for c in password)
+        has_digit = any(c.isdigit() for c in password)
+        has_special = any(c in string.punctuation for c in password)
+        
+        types_count = sum([has_upper, has_lower, has_digit, has_special])
+        score += types_count
+        
+        # Сложность
+        if len(set(password)) / len(password) > 0.7:
+            score += 1
+            
+        if score >= 6:
+            return "Очень надежный"
+        elif score >= 4:
+            return "Надежный"
+        elif score >= 2:
+            return "Средний"
+        else:
+            return "Слабый"
+    
+    def get_strength_color(self, strength):
+        colors = {
+            "Очень надежный": "green",
+            "Надежный": "blue",
+            "Средний": "orange",
+            "Слабый": "red"
+        }
+        return colors.get(strength, "black")
+    
+    def copy_to_clipboard(self):
+        if self.current_password:
+            self.root.clipboard_clear()
+            self.root.clipboard_append(self.current_password)
+            self.status_bar.config(text="Пароль скопирован в буфер обмена")
+            messagebox.showinfo("Успех", "Пароль скопирован в буфер обмена!")
+        else:
+            messagebox.showwarning("Предупреждение", "Сначала сгенерируйте пароль!")
+    
+    def save_password(self):
+        if not self.current_password:
+            messagebox.showwarning("Предупреждение", "Сначала сгенерируйте пароль!")
+            return
+        
+        try:
+            conn = sqlite3.connect('passwords.db')
+            cursor = conn.cursor()
+            
+            strength = self.calculate_strength(self.current_password)
+            
+            cursor.execute('''
+                INSERT INTO passwords (password, strength, timestamp)
+                VALUES (?, ?, ?)
+            ''', (self.current_password, strength, datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
+            
+            conn.commit()
+            conn.close()
+            
+            self.load_history()
+            self.status_bar.config(text="Пароль сохранен в базу данных")
+            messagebox.showinfo("Успех", "Пароль успешно сохранен!")
+            
+        except Exception as e:
+            messagebox.showerror("Ошибка", f"Не удалось сохранить пароль: {str(e)}")
+    
+    def load_history(self):
+        try:
+            # Очищаем дерево
+            for item in self.history_tree.get_children():
+                self.history_tree.delete(item)
+            
+            conn = sqlite3.connect('passwords.db')
+            cursor = conn.cursor()
+            
+            cursor.execute('''
+                SELECT id, password, strength, timestamp 
+                FROM passwords 
+                ORDER BY id DESC 
+                LIMIT ?
+            ''', (self.history_limit,))
+            
+            records = cursor.fetchall()
+            
+            for record in records:
+                # Маскируем пароль для отображения
+                masked_password = self.mask_password(record[1])
+                self.history_tree.insert('', 'end', values=(
+                    record[0], masked_password, record[2], record[3]
+                ))
+            
+            conn.close()
+            
+            self.status_bar.config(text=f"Загружено {len(records)} записей")
+            
+        except Exception as e:
+            messagebox.showerror("Ошибка", f"Не удалось загрузить историю: {str(e)}")
+    
+    def mask_password(self, password):
+        if not password:
+            return "****"
+        if len(password) <= 4:
+            return "****"
+        return password[:2] + "*" * (len(password) - 4) + password[-2:]
+    
+    def on_history_select(self, event):
+        selection = self.history_tree.selection()
+        if selection:
+            item = self.history_tree.item(selection[0])
+            item_id = item['values'][0]
+            
+            conn = sqlite3.connect('passwords.db')
+            cursor = conn.cursor()
+            cursor.execute('SELECT password FROM passwords WHERE id = ?', (item_id,))
+            result = cursor.fetchone()
+            conn.close()
+            
+            if result:
+                password = result[0]
+                self.current_password = password
+                self.password_var.set(password)
+                self.status_bar.config(text=f"Выбран пароль ID: {item_id}")
+    
+    def clear_history_confirm(self):
+        if messagebox.askyesno("Подтверждение", 
+                              "Вы уверены, что хотите очистить всю историю паролей?"):
+            self.clear_history()
+    
+    def clear_history(self):
+        try:
+            conn = sqlite3.connect('passwords.db')
+            cursor = conn.cursor()
+            cursor.execute("DELETE FROM passwords")
+            conn.commit()
+            conn.close()
+            
+            self.load_history()
+            messagebox.showinfo("Успех", "История паролей очищена!")
+            
+        except Exception as e:
+            messagebox.showerror("Ошибка", f"Не удалось очистить историю: {str(e)}")
+    
+    def check_strength(self):
+        if not self.current_password:
+            messagebox.showwarning("Предупреждение", "Сначала сгенерируйте пароль!")
+            return
+        
+        strength = self.calculate_strength(self.current_password)
+        color = self.get_strength_color(strength)
+        
+        analysis = self.analyze_password(self.current_password)
+        
+        messagebox.showinfo("Анализ пароля", 
+                          f"Надежность: {strength}\n\n"
+                          f"Длина: {len(self.current_password)} символов\n"
+                          f"Заглавные буквы: {'Есть' if any(c.isupper() for c in self.current_password) else 'Нет'}\n"
+                          f"Строчные буквы: {'Есть' if any(c.islower() for c in self.current_password) else 'Нет'}\n"
+                          f"Цифры: {'Есть' if any(c.isdigit() for c in self.current_password) else 'Нет'}\n"
+                          f"Спецсимволы: {'Есть' if any(c in string.punctuation for c in self.current_password) else 'Нет'}\n\n"
+                          f"{analysis}")
+    
+    def analyze_password(self, password):
+        suggestions = []
+        
+        if len(password) < 12:
+            suggestions.append("• Увеличьте длину пароля (рекомендуется 12+ символов)")
+        
+        if not any(c.isupper() for c in password):
+            suggestions.append("• Добавьте заглавные буквы")
+            
+        if not any(c.islower() for c in password):
+            suggestions.append("• Добавьте строчные буквы")
+            
+        if not any(c.isdigit() for c in password):
+            suggestions.append("• Добавьте цифры")
+            
+        if not any(c in string.punctuation for c in password):
+            suggestions.append("• Добавьте специальные символы")
+        
+        if len(set(password)) / len(password) < 0.6:
+            suggestions.append("• Используйте больше уникальных символов")
+        
+        if suggestions:
+            return "Рекомендации:\n" + "\n".join(suggestions)
+        else:
+            return "Пароль соответствует всем рекомендациям безопасности!"
+    
+    def export_history(self):
+        try:
+            conn = sqlite3.connect('passwords.db')
+            cursor = conn.cursor()
+            cursor.execute('SELECT * FROM passwords ORDER BY id DESC')
+            records = cursor.fetchall()
+            conn.close()
+            
+            if not records:
+                messagebox.showwarning("Предупреждение", "Нет данных для экспорта!")
+                return
+            
+            filename = f"passwords_export_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"
+            
+            with open(filename, 'w', encoding='utf-8') as f:
+                f.write("Экспорт паролей\n")
+                f.write("=" * 50 + "\n\n")
+                
+                for record in records:
+                    f.write(f"ID: {record[0]}\n")
+                    f.write(f"Пароль: {record[1]}\n")
+                    f.write(f"Надежность: {record[2]}\n")
+                    f.write(f"Создан: {record[3]}\n")
+                    f.write("-" * 30 + "\n")
+            
+            self.status_bar.config(text=f"Данные экспортированы в файл: {filename}")
+            messagebox.showinfo("Успех", f"Данные успешно экспортированы в файл:\n{filename}")
+            
+        except Exception as e:
+            messagebox.showerror("Ошибка", f"Ошибка экспорта: {str(e)}")
+    
+    def export_all_passwords(self):
+        self.export_history()
+    
+    def show_statistics(self):
+        try:
+            conn = sqlite3.connect('passwords.db')
+            cursor = conn.cursor()
+            
+            cursor.execute("SELECT COUNT(*) FROM passwords")
+            total = cursor.fetchone()[0]
+            
+            cursor.execute("SELECT COUNT(*) FROM passwords WHERE strength = 'Очень надежный'")
+            very_strong = cursor.fetchone()[0]
+            
+            cursor.execute("SELECT COUNT(*) FROM passwords WHERE strength = 'Надежный'")
+            strong = cursor.fetchone()[0]
+            
+            cursor.execute("SELECT COUNT(*) FROM passwords WHERE strength = 'Средний'")
+            medium = cursor.fetchone()[0]
+            
+            cursor.execute("SELECT COUNT(*) FROM passwords WHERE strength = 'Слабый'")
+            weak = cursor.fetchone()[0]
+            
+            cursor.execute("SELECT MIN(timestamp), MAX(timestamp) FROM passwords")
+            dates = cursor.fetchone()
+            
+            conn.close()
+            
+            stats_text = f"""
+            📊 Статистика паролей
+            
+            Всего паролей: {total}
+            
+            По надежности:
+            • Очень надежные: {very_strong}
+            • Надежные: {strong}
+            • Средние: {medium}
+            • Слабые: {weak}
+            
+            Временной диапазон:
+            • Первый пароль: {dates[0] if dates[0] else 'Нет данных'}
+            • Последний пароль: {dates[1] if dates[1] else 'Нет данных'}
+            """
+            
+            messagebox.showinfo("Статистика", stats_text)
+            
+        except Exception as e:
+            messagebox.showerror("Ошибка", f"Не удалось получить статистику: {str(e)}")
+    
+    def set_history_limit(self):
+        dialog = tk.Toplevel(self.root)
+        dialog.title("Ограничение истории")
+        dialog.geometry("300x150")
+        
+        tk.Label(dialog, text="Максимальное количество записей:").pack(pady=10)
+        
+        limit_var = tk.IntVar(value=self.history_limit)
+        spinbox = tk.Spinbox(dialog, from_=5, to=100, textvariable=limit_var, width=10)
+        spinbox.pack(pady=10)
+        
+        def apply_limit():
+            self.history_limit = limit_var.get()
+            self.load_history()
+            dialog.destroy()
+            messagebox.showinfo("Успех", f"Лимит истории установлен: {self.history_limit}")
+        
+        tk.Button(dialog, text="Применить", command=apply_limit).pack(pady=10)
+    
+    def save_config(self):
+        config = {
+            'length': self.length_var.get(),
+            'letters': self.letters_var.get(),
+            'digits': self.digits_var.get(),
+            'punctuation': self.punctuation_var.get(),
+            'exclude_similar': self.exclude_similar.get(),
+            'exclude_ambiguous': self.exclude_ambiguous.get(),
+            'history_limit': self.history_limit
+        }
+        
+        try:
+            with open(self.config_file, 'w') as f:
+                json.dump(config, f)
+            
+            self.status_bar.config(text="Настройки сохранены")
+            messagebox.showinfo("Успех", "Настройки успешно сохранены!")
+            
+        except Exception as e:
+            messagebox.showerror("Ошибка", f"Не удалось сохранить настройки: {str(e)}")
+    
+    def load_config(self):
+        if not os.path.exists(self.config_file):
+            return
+        
+        try:
+            with open(self.config_file, 'r') as f:
+                config = json.load(f)
+            
+            self.length_var.set(config.get('length', 16))
+            self.letters_var.set(config.get('letters', True))
+            self.digits_var.set(config.get('digits', True))
+            self.punctuation_var.set(config.get('punctuation', True))
+            self.exclude_similar.set(config.get('exclude_similar', True))
+            self.exclude_ambiguous.set(config.get('exclude_ambiguous', False))
+            self.history_limit = config.get('history_limit', 15)
+            
+            self.status_bar.config(text="Настройки загружены")
+            
+        except Exception as e:
+            messagebox.showerror("Ошибка", f"Не удалось загрузить настройки: {str(e)}")
+    
+    def reset_settings(self):
+        if messagebox.askyesno("Подтверждение", 
+                              "Вы уверены, что хотите сбросить все настройки к значениям по умолчанию?"):
+            self.length_var.set(16)
+            self.letters_var.set(True)
+            self.digits_var.set(True)
+            self.punctuation_var.set(True)
+            self.exclude_similar.set(True)
+            self.exclude_ambiguous.set(False)
+            self.history_limit = 15
+            
+            messagebox.showinfo("Успех", "Настройки сброшены к значениям по умолчанию!")
+    
+    def show_about(self):
+        about_text = """
+        🔐 Генератор надежных паролей PRO
+        
+        Версия: 2.0
+        Разработчик: Password Security Team
+        
+        Функции:
+        • Генерация паролей с настройками
+        • Проверка надежности паролей
+        • Сохранение истории паролей
+        • Экспорт данных
+        • Статистика использования
+        
+        Используйте этот инструмент для создания
+        безопасных паролей для всех ваших аккаунтов!
+        """
+        messagebox.showinfo("О программе", about_text)
+    
+    def show_tips(self):
+        tips = """
+        💡 Советы по созданию надежных паролей:
+        
+        1. Используйте длину не менее 12 символов
+        2. Сочетайте разные типы символов
+        3. Избегайте личной информации
+        4. Не используйте один пароль на всех сайтах
+        5. Регулярно меняйте важные пароли
+        6. Используйте менеджер паролей
+        7. Включайте двухфакторную аутентификацию
+        
+        Помните: Лучший пароль - тот, который вы не можете запомнить,
+        но ваш менеджер паролей может!
+        """
+        messagebox.showinfo("Рекомендации по безопасности", tips)
+    
+    def run(self):
+        self.load_history()
+        self.root.mainloop()
+
+
+if __name__ == "__main__":
+    app = PasswordGenerator()
+    app.run()
+
+
+