Создаю менеджер паролей
два файла в папке password_manager: gui.py и password_manager.py

gui.py:

gui.py
import tkinter as tk
from tkinter import messagebox, ttk
from PIL import Image, ImageTk
import pyperclip
from password_manager import (
load_data, save_data, add_password, get_all_passwords,
delete_password_entry_by_id, generate_password
)

FILE_PATH = 'data.enc'

class PasswordManagerApp:
def init(self, root):
self.root = root
self.root.title("Менеджер Паролей")
self.root.geometry("800x600")
self.root.resizable(False, False)
self.data = None
self.master_password = None


    # Переменная для отслеживания открытия окна добавления пароля
    self.add_password_window_open = False

    # Настройка стилей
    self.setup_styles()

    # Загрузка фона
    self.load_background()

    self.create_login_window()

def setup_styles(self):
    style = ttk.Style()
    style.theme_use('clam')  # Вы можете выбрать 'clam', 'alt', 'default', 'classic'

    # Настройка заголовков
    style.configure("TLabel", font=("Arial", 12), foreground="white")
    style.configure("Header.TLabel", font=("Arial", 20, "bold"), foreground="#00BFFF")

    # Настройка кнопок
    style.configure("TButton",
                    font=("Arial", 12),
                    padding=6,
                    relief="flat",
                    background="#00BFFF",
                    foreground="white")
    style.map("TButton",
              background=[('active', '#1E90FF')],
              foreground=[('active', 'white')])

    # Настройка Treeview
    style.configure("Treeview.Heading", font=("Arial", 12, "bold"), foreground="#00BFFF")
    style.configure("Treeview", font=("Arial", 10),
                    rowheight=25,
                    fieldbackground="#2C3E50",
                    background="#34495E",
                    foreground="white",
                    highlightthickness=0,
                    bd=0)
    style.map('Treeview', background=[('selected', '#1ABC9C')], foreground=[('selected', 'white')])

def load_background(self):
    try:
        # Загрузите ваше фоновое изображение
        self.background_image = Image.open("background.png")
        self.background_image = self.background_image.resize((800, 600), Image.ANTIALIAS)
        self.bg_photo = ImageTk.PhotoImage(self.background_image)

        self.background_label = tk.Label(self.root, image=self.bg_photo)
        self.background_label.place(x=0, y=0, relwidth=1, relheight=1)
    except Exception as e:
        print(f"Не удалось загрузить фоновое изображение: {e}")
        self.background_label = None

def create_login_window(self):
    self.clear_window()

    frame = ttk.Frame(self.root, padding="40 40 40 40", style="TFrame")
    frame.place(relx=0.5, rely=0.5, anchor=tk.CENTER)

    header = ttk.Label(frame, text="### Менеджер Паролей ###", style="Header.TLabel")
    header.pack(pady=(0, 20))

    password_label = ttk.Label(frame, text="Введите мастер-пароль:")
    password_label.pack(pady=(0, 10))

    self.password_entry = ttk.Entry(frame, show="*", width=30, font=("Arial", 12))
    self.password_entry.pack(pady=(0, 20))

    # Загрузка иконки для кнопки "Войти"
    try:
        login_icon = Image.open("view_icon.png")
        login_icon = login_icon.resize((20, 20), Image.ANTIALIAS)
        self.login_icon_photo = ImageTk.PhotoImage(login_icon)
    except Exception as e:
        print(f"Не удалось загрузить иконку для кнопки 'Войти': {e}")
        self.login_icon_photo = None

    login_button = ttk.Button(frame, text="Войти", image=self.login_icon_photo, compound=tk.LEFT, command=self.login)
    if self.login_icon_photo:
        login_button.image = self.login_icon_photo  # Сохраняем ссылку на изображение
    login_button.pack()

def login(self):
    password = self.password_entry.get()
    if not password:
        messagebox.showerror("Ошибка", "Мастер-пароль не может быть пустым.", parent=self.root)
        return
    try:
        self.data = load_data(FILE_PATH, password)
        self.master_password = password
        self.create_main_window()
    except ValueError as ve:
        messagebox.showerror("Ошибка", str(ve), parent=self.root)
    except Exception as e:
        messagebox.showerror("Ошибка", f"Произошла ошибка: {str(e)}", parent=self.root)

def create_main_window(self):
    self.clear_window()

    header = ttk.Label(self.root, text="### Менеджер Паролей ###", font=("Arial", 16, "bold"))
    header.pack(pady=20)

    # Загрузка иконок для кнопок
    try:
        add_icon = Image.open("add_icon.png")
        add_icon = add_icon.resize((20, 20), Image.ANTIALIAS)
        self.add_icon_photo = ImageTk.PhotoImage(add_icon)
    except Exception as e:
        print(f"Не удалось загрузить иконку для кнопки 'Добавить Пароль': {e}")
        self.add_icon_photo = None

    try:
        view_icon = Image.open("view_icon.png")
        view_icon = view_icon.resize((20, 20), Image.ANTIALIAS)
        self.view_icon_photo = ImageTk.PhotoImage(view_icon)
    except Exception as e:
        print(f"Не удалось загрузить иконку для кнопки 'Просмотреть Пароли': {e}")
        self.view_icon_photo = None

    try:
        exit_icon = Image.open("exit_icon.png")
        exit_icon = exit_icon.resize((20, 20), Image.ANTIALIAS)
        self.exit_icon_photo = ImageTk.PhotoImage(exit_icon)
    except Exception as e:
        print(f"Не удалось загрузить иконку для кнопки 'Выход': {e}")
        self.exit_icon_photo = None

    # Buttons Frame
    buttons_frame = ttk.Frame(self.root)
    buttons_frame.pack(pady=10)

    # Кнопка "Добавить Пароль"
    add_button = ttk.Button(buttons_frame, text="Добавить Пароль", width=20, command=self.add_password_window)
    if self.add_icon_photo:
        add_button.config(image=self.add_icon_photo, compound=tk.LEFT)
        add_button.image = self.add_icon_photo  # Сохраняем ссылку на изображение
    add_button.grid(row=0, column=0, padx=10, pady=10)

    # Кнопка "Просмотреть Пароли"
    view_button = ttk.Button(buttons_frame, text="Просмотреть Пароли", width=20, command=self.view_passwords_window)
    if self.view_icon_photo:
        view_button.config(image=self.view_icon_photo, compound=tk.LEFT)
        view_button.image = self.view_icon_photo  # Сохраняем ссылку на изображение
    view_button.grid(row=0, column=1, padx=10, pady=10)

    # Кнопка "Выход"
    exit_button = ttk.Button(buttons_frame, text="Выход", width=20, command=self.root.quit)
    if self.exit_icon_photo:
        exit_button.config(image=self.exit_icon_photo, compound=tk.LEFT)
        exit_button.image = self.exit_icon_photo  # Сохраняем ссылку на изображение
    exit_button.grid(row=0, column=2, padx=10, pady=10)

def add_password_window(self):
    if self.add_password_window_open:
        # Если окно уже открыто, просто поднимаем его на передний план
        self.add_win.focus_force()
        return

    self.add_password_window_open = True  # Устанавливаем флаг

    add_win = tk.Toplevel(self.root)
    add_win.title("Добавить Пароль")
    add_win.geometry("500x450")
    add_win.resizable(False, False)

    # Обработка закрытия окна
    def on_close():
        self.add_password_window_open = False
        add_win.destroy()

    add_win.protocol("WM_DELETE_WINDOW", on_close)

    frame = ttk.Frame(add_win, padding="20 20 20 20")
    frame.pack(expand=True, fill=tk.BOTH)

    header = ttk.Label(frame, text="Добавление нового пароля", style="Header.TLabel")
    header.pack(pady=(0, 20))

    # Название сервиса
    service_label = ttk.Label(frame, text="Название сервиса:")
    service_label.pack(pady=(0, 5), anchor=tk.W)
    service_entry = ttk.Entry(frame, width=40, font=("Arial", 12))
    service_entry.pack(pady=(0, 15), anchor=tk.W)

    # Имя пользователя с галочкой
    self.no_username_var = tk.IntVar()
    no_username_check = ttk.Checkbutton(
        frame,
        text="Без имени пользователя",
        variable=self.no_username_var,
        command=lambda: self.toggle_username_entry(username_entry)
    )
    no_username_check.pack(pady=(0, 5), anchor=tk.W)

    username_label = ttk.Label(frame, text="Имя пользователя:")
    username_label.pack(pady=(10, 5), anchor=tk.W)
    username_entry = ttk.Entry(frame, width=40, font=("Arial", 12))
    username_entry.pack(pady=(0, 15), anchor=tk.W)

    # Пароль
    password_label = ttk.Label(frame, text="Пароль:")
    password_label.pack(pady=(0, 5), anchor=tk.W)
    password_entry = ttk.Entry(frame, width=40, show="*", font=("Arial", 12))
    password_entry.pack(pady=(0, 10), anchor=tk.W)

    # Кнопки: Показать/Скрыть и Генерация пароля
    buttons_frame = ttk.Frame(frame)
    buttons_frame.pack(pady=(0, 20), anchor=tk.W)

    def toggle_password():
        if password_entry.cget('show') == '':
            password_entry.config(show="*")
            toggle_button.config(text="Показать")
        else:
            password_entry.config(show='')
            toggle_button.config(text="Скрыть")

    toggle_button = ttk.Button(buttons_frame, text="Показать", width=12, command=toggle_password)
    toggle_button.grid(row=0, column=0, padx=(0, 10))

    def generate_and_set_password():
        generated = generate_password()
        password_entry.delete(0, tk.END)
        password_entry.insert(0, generated)

    generate_button = ttk.Button(buttons_frame, text="Сгенерировать Пароль", command=generate_and_set_password)
    generate_button.grid(row=0, column=1, padx=(0, 10))

    # Загрузка иконки для кнопки "Сохранить"
    try:
        save_icon = Image.open("add_icon.png")
        save_icon = save_icon.resize((20, 20), Image.ANTIALIAS)
        self.save_icon_photo = ImageTk.PhotoImage(save_icon)
    except Exception as e:
        print(f"Не удалось загрузить иконку для кнопки 'Сохранить': {e}")
        self.save_icon_photo = None

    # Кнопка сохранения
    save_button = ttk.Button(frame, text="Сохранить", command=lambda: self.save_password(
        service_entry.get().strip(),
        username_entry.get().strip(),
        password_entry.get().strip(),
        add_win
    ))
    if self.save_icon_photo:
        save_button.config(image=self.save_icon_photo, compound=tk.LEFT)
        save_button.image = self.save_icon_photo  # Сохраняем ссылку на изображение
    save_button.pack(pady=10)

def toggle_username_entry(self, username_entry):
    """
    Отключает или включает поле ввода имени пользователя в зависимости от состояния галочки.
    """
    if self.no_username_var.get():
        username_entry.config(state='disabled')
        username_entry.delete(0, tk.END)
    else:
        username_entry.config(state='normal')

def save_password(self, service, username, password, window):
    if not service or not password:
        messagebox.showerror("Ошибка", "Поля 'Название сервиса' и 'Пароль' обязательно для заполнения.", parent=window)
        return

    if self.no_username_var.get():
        username = ""  # Можно установить None или оставить как пустую строку

    try:
        add_password(self.data, service, username, password)
        save_data(FILE_PATH, self.master_password, self.data)
        messagebox.showinfo("Успех", "Пароль добавлен успешно.", parent=window)
        window.destroy()
        self.add_password_window_open = False  # Сбрасываем флаг
    except Exception as e:
        messagebox.showerror("Ошибка", f"Не удалось сохранить пароль: {str(e)}", parent=window)

def view_passwords_window(self):
    view_win = tk.Toplevel(self.root)
    view_win.title("Просмотр Паролей")
    view_win.geometry("800x500")
    view_win.resizable(False, False)

    frame = ttk.Frame(view_win, padding="20 20 20 20")
    frame.pack(expand=True, fill=tk.BOTH)

    header = ttk.Label(frame, text="Список всех паролей", style="Header.TLabel")
    header.pack(pady=(0, 20))

    # Настройка Treeview
    columns = ("Service", "Username", "Password")
    tree = ttk.Treeview(frame, columns=columns, show='headings', selectmode="browse")
    for col in columns:
        tree.heading(col, text=col)
        tree.column(col, width=250, anchor='center')
    tree.pack(fill=tk.BOTH, expand=True)

    # Полосы прокрутки
    scrollbar_y = ttk.Scrollbar(tree, orient=tk.VERTICAL, command=tree.yview)
    scrollbar_x = ttk.Scrollbar(tree, orient=tk.HORIZONTAL, command=tree.xview)
    tree.configure(yscroll=scrollbar_y.set, xscroll=scrollbar_x.set)
    scrollbar_y.pack(side=tk.RIGHT, fill=tk.Y)
    scrollbar_x.pack(side=tk.BOTTOM, fill=tk.X)

    # Загрузка паролей в Treeview
    self.populate_treeview(tree)

    # Загрузка иконок для кнопок
    try:
        copy_icon = Image.open("copy_icon.png")
        copy_icon = copy_icon.resize((20, 20), Image.ANTIALIAS)
        self.copy_icon_photo = ImageTk.PhotoImage(copy_icon)
    except Exception as e:
        print(f"Не удалось загрузить иконку для кнопки 'Скопировать Пароль': {e}")
        self.copy_icon_photo = None

    try:
        delete_icon = Image.open("delete_icon.png")
        delete_icon = delete_icon.resize((20, 20), Image.ANTIALIAS)
        self.delete_icon_photo = ImageTk.PhotoImage(delete_icon)
    except Exception as e:
        print(f"Не удалось загрузить иконку для кнопки 'Удалить Пароль': {e}")
        self.delete_icon_photo = None

    try:
        refresh_icon = Image.open("refresh_icon.png")
        refresh_icon = refresh_icon.resize((20, 20), Image.ANTIALIAS)
        self.refresh_icon_photo = ImageTk.PhotoImage(refresh_icon)
    except Exception as e:
        print(f"Не удалось загрузить иконку для кнопки 'Обновить Список': {e}")
        self.refresh_icon_photo = None

    # Кнопки для копирования, удаления и обновления паролей
    buttons_frame = ttk.Frame(frame)
    buttons_frame.pack(pady=10)

    def copy_password():
        selected_item = tree.focus()
        if selected_item:
            password = tree.item(selected_item)["values"][2]
            pyperclip.copy(password)
            messagebox.showinfo("Скопировано", "Пароль скопирован в буфер обмена.", parent=view_win)
        else:
            messagebox.showerror("Ошибка", "Выберите пароль для копирования.", parent=view_win)

    def delete_password():
        selected_item = tree.focus()
        if not selected_item:
            messagebox.showerror("Ошибка", "Выберите запись для удаления.", parent=view_win)
            return
        values = tree.item(selected_item)["values"]
        service, username, password = values

        confirm = messagebox.askyesno("Подтверждение",
                                      f"Вы уверены, что хотите удалить пароль для сервиса '{service}' и пользователя '{username}'?",
                                      parent=view_win)
        if confirm:
            try:
                success = delete_password_entry_by_id(self.data, selected_item)
                if success:
                    save_data(FILE_PATH, self.master_password, self.data)
                    tree.delete(selected_item)
                    messagebox.showinfo("Успех", "Пароль удалён успешно.", parent=view_win)
                else:
                    messagebox.showerror("Ошибка", "Не удалось удалить пароль. Возможно, запись уже была удалена.",
                                         parent=view_win)
            except Exception as e:
                messagebox.showerror("Ошибка", f"Не удалось удалить пароль: {str(e)}", parent=view_win)

    def refresh_treeview():
        self.populate_treeview(tree)
        messagebox.showinfo("Обновлено", "Список паролей обновлён.", parent=view_win)

    copy_button = ttk.Button(buttons_frame, text="Скопировать Пароль", command=copy_password)
    if self.copy_icon_photo:
        copy_button.config(image=self.copy_icon_photo, compound=tk.LEFT)
        copy_button.image = self.copy_icon_photo  # Сохраняем ссылку на изображение
    copy_button.grid(row=0, column=0, padx=10)

    delete_button = ttk.Button(buttons_frame, text="Удалить Пароль", command=delete_password)
    if self.delete_icon_photo:
        delete_button.config(image=self.delete_icon_photo, compound=tk.LEFT)
        delete_button.image = self.delete_icon_photo  # Сохраняем ссылку на изображение
    delete_button.grid(row=0, column=1, padx=10)

    refresh_button = ttk.Button(buttons_frame, text="Обновить Список", command=refresh_treeview)
    if self.refresh_icon_photo:
        refresh_button.config(image=self.refresh_icon_photo, compound=tk.LEFT)
        refresh_button.image = self.refresh_icon_photo  # Сохраняем ссылку на изображение
    refresh_button.grid(row=0, column=2, padx=10)

def populate_treeview(self, tree):
    """
    Заполняет Treeview паролями из данных.
    """
    # Очистка текущих данных
    for item in tree.get_children():
        tree.delete(item)

    # Загрузка данных
    try:
        passwords = get_all_passwords(self.data)
        for service, creds in passwords.items():
            for cred in creds:
                # Убедитесь, что каждая запись имеет уникальный 'id'
                if 'id' not in cred:
                    continue  # Пропуск записей без 'id'
                # Для отображения отсутствующего имени пользователя используем дефолтное значение
                display_username = cred["username"] if cred["username"] else "—"
                tree.insert("", tk.END, iid=cred["id"], values=(service, display_username, cred["password"]))
    except KeyError:
        messagebox.showerror("Ошибка", "Формат данных некорректен.", parent=self.root)

def clear_window(self):
    for widget in self.root.winfo_children():
        widget.destroy()
def main():
root = tk.Tk()
app = PasswordManagerApp(root)
root.mainloop()

if name == "main":
main()

password_manager.py:
# password_manager.py

import os
import json
import base64
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.fernet import Fernet, InvalidToken
import secrets
import string
import uuid  # Для генерации уникальных идентификаторов


def derive_key(password: str, salt: bytes) -> bytes:
    """
    Генерирует криптографический ключ из пароля и соли.
    """
    kdf_instance = PBKDF2HMAC(
        algorithm=hashes.SHA256(),
        length=32,
        salt=salt,
        iterations=100_000,
        backend=default_backend()
    )
    return base64.urlsafe_b64encode(kdf_instance.derive(password.encode()))


def load_data(file_path: str, password: str) -> dict:
    """
    Загружает и дешифрует данные из файла.
    Если файл не существует или повреждён, создаёт новый с пустыми данными.
    """
    if not os.path.exists(file_path):
        # Генерируем новую соль и создаём структуру данных
        new_salt = base64.urlsafe_b64encode(os.urandom(16)).decode()
        new_data = {"salt": new_salt, "data": {}}
        print("Файл данных не найден. Создаётся новый файл.")
        # Сохраняем новый файл
        save_data(file_path, password, new_data)
        return new_data

    with open(file_path, 'rb') as file:
        file_content = file.read()
        try:
            decoded = json.loads(file_content)
            print("Файл данных успешно загружен.")
        except json.JSONDecodeError:
            # Если файл повреждён, создаём новый
            raise ValueError("Файл данных повреждён или имеет неверный формат.")

        # Проверка наличия необходимых ключей
        if 'salt' not in decoded or 'data' not in decoded:
            raise ValueError("Файл данных имеет неверную структуру.")

        salt = base64.urlsafe_b64decode(decoded['salt'])
        key = derive_key(password, salt)
        fernet = Fernet(key)
        try:
            decrypted_data = fernet.decrypt(decoded['data'].encode())
            return {"salt": decoded['salt'], "data": json.loads(decrypted_data)}
        except InvalidToken:
            raise ValueError("Неверный мастер-пароль!")


def save_data(file_path: str, password: str, data: dict):
    """
    Шифрует и сохраняет данные в файл.
    """
    salt = base64.urlsafe_b64decode(data['salt'])
    key = derive_key(password, salt)
    fernet = Fernet(key)
    encrypted_data = fernet.encrypt(json.dumps(data["data"]).encode())
    to_save = {
        "salt": data['salt'],
        "data": encrypted_data.decode()
    }
    with open(file_path, 'w') as file:
        json.dump(to_save, file)
    print("Данные успешно сохранены.")


def generate_password(length: int = 16) -> str:
    """
    Генерирует случайный сильный пароль.
    """
    characters = string.ascii_letters + string.digits + string.punctuation
    return ''.join(secrets.choice(characters) for _ in range(length))


def add_password(data: dict, service: str, username: str, password: str):
    """
    Добавляет новый пароль в хранилище.
    """
    if service not in data['data']:
        data['data'][service] = []

    new_entry = {
        "id": str(uuid.uuid4()),  # Генерация уникального ID
        "username": username,
        "password": password
    }

    data['data'][service].append(new_entry)
    print(f"Добавлена запись: ID={new_entry['id']}, Service='{service}', Username='{username}', Password='{password}'")


def get_all_passwords(data: dict) -> dict:
    """
    Возвращает все хранящиеся пароли.
    """
    return data['data']


def delete_password_entry_by_id(data: dict, entry_id: str) -> bool:
    """
    Удаляет конкретную запись пароля из хранилища по ID.
    Возвращает True, если успешно удалено, иначе False.
    """
    for service, creds in list(data['data'].items()):
        for cred in creds:
            if cred.get('id') == entry_id:
                creds.remove(cred)
                print(f"Удалена запись ID='{entry_id}' для сервиса '{service}', пользователя '{cred['username']}'.")
                if not creds:
                    del data['data'][service]
                    print(f"Удалён сервис '{service}', так как у него больше нет записей.")
                return True
    print(f"Запись ID='{entry_id}' не найдена.")
    return False