Статья Шифр Цезаря: шифратор/дешифратор с красивым GUI на PySide6

prew.jpg

Приветствую друзья!
В данной статье напишем шифратор/дешифратор Цезаря на Python и прикрутим к нему GUI.
По окончанию работ наше приложение будет выглядеть так:

2023-04-05_11-03-09.png


ОС - WIndows 10. Python 3.7+, я использую 3.11.2 (последний на момент написания статьи)
Писать проект будем в PyCharm
Дизайнить интерфейс в QtDesigner
Свяжем интерфейс с нашим python проектом с помощью PySide6
PySide6
— привязка языка Python к инструментарию Qt, совместимая на уровне API с PyQt. PySide6 доступна для свободного использования как в открытых, так и закрытых, в частности, коммерческих проектах, поскольку лицензирована по LGPL.
Итак, установим необходимые инструменты:

В PyCharm создадим новый проект File -> New Project, я назвал ROT_decoder_GUI
Внутри проекта создадим пару файлов: main.py и cipher_functions.py
Настройки интерпретатора стандартные, переходим в терминал (Alt + F12) и устанавливаем PySide6
pip install pyside6

2023-04-05_11-51-19.png

После установки переходим в директорию ~\PycharmProjects\ROT_decoder_GUI\venv\Lib\site-packages\PySide6\
Там находятся необходимые бинарники для дальнейшей разработки включая designer.exe
Главное окно программы:

2023-04-05_11-57-19.png


Создадим новую форму Файл -> Новый, выберем Main Window

2023-04-05_12-05-56.png


В инспекторе объектов удаляем menubar и statusbar.

2023-04-05_12-06-53.png


Кидаем на форму объекты Layout - 2 вертикальных и 3 горизонтальных. В дальнейшем на них будем накидывать наши элементы.

2023-04-05_12-13-16.png


Устанавливаем необходимые объекты, в скобочках напишу.
1. Ввод текста для шифрования/дешифрования (label, Text Edit)
2. Вывод расшифрованного/ зашифрованного текста (label, Text Edit)
3. Кнопки (Push Button - 3 штуки)
4. Слайдер и спинбокс (Label, Horizontal Slider и Spin Box)
5. Label об авторе, ссылки, год, копирайт и т.д. (label)

Двойным кликом в инспекторе даем осмысленные имена.
В инспекторе выделяем объект central widget и кнопкой на панели инструментов компонуем все по вертикали.

2023-04-05_12-24-45.png


Приступим к стилизации наших объектов. В инспекторе ПКМ по Main WIndows -> Изменить Style Sheet..
Имеется поддержка CSS, что лично для меня очень удобно, добавляем наш код:
CSS:
QTextEdit {
    border: 2px solid gray;
    border-radius: 5px;
}

QPushButton {
    border: 2px solid gray;
    border-radius: 5px;
    font: 700 12pt "Hack";
}
QPushButton:hover {
    border-color: #090;
}

QPushButton:pressed {
    border: 4px solid #090;
    border-radius: 5px;
}
QPushButton:checked {
    color: white;
    background-color: #090;
    border: 2px solid black;
    border-radius: 5px;
}
QSlider::sub-page:horizontal {
    background-color: #090;
}

QSlider::add-page:horizontal {
    background-color: gray;
}
QSlider::handle:horizontal {
    background-color: black;
    width: 16px;
    border-radius:8px;
    margin-top: -5px;
    margin-bottom: -5px;
}
QSlider::groove:horizontal {
    background-color: transparent;
    height: 7px;
}
QLabel {
    font: 500 11pt "Ubuntu";
}
QSpinBox {
    border: 2px solid gray;
    border-radius: 5px;
    background: transparent;
    padding: 2px;
}

QSpinBox:hover {
    border-color: #009900;
}

2023-04-05_12-29-45.png


Название классов элементов также указано в инспекторе объектов.
В редакторе свойств элементов, уберем у спинбокса кнопки, кнопкам Decrypt и Encrypt (элемент Push Button) установим свойство Checkable, у Decrypt поставим Checked.

2023-04-05_12-35-38.png


2023-04-05_12-36-05.png


Слайдеру и спинбоксу поставим шаг и значение по умолчанию, у меня 13 (ROT 13)

2023-04-05_12-40-58.png


Осталось разобраться с ресурсами и добавить наши иконки. Я взял иконки из material design от гугла.

2023-04-05_12-43-41.png


2023-04-05_12-44-27.png


С различными свойствами объектов можете поиграться сами (размер, курсор, шрифт и т.д.) Настроек много.
Превью интерфейса смотрим сочетанием Ctrl + R
Проектирование и прототипирование закончено, можно приступить к коду :)
В файл cipher_functions.py записываем собственно сами функции шифрования/дешифрования по Цезарю.

Python:
upper_english_alp = {key: chr(value) for key, value in zip(range(26), range(65, 91))}
lower_english_alp = {key: chr(value) for key, value in zip(range(26), range(97, 123))}

upper_russian_alp = {key: chr(value) for key, value in zip(range(32), range(1040, 1072))}
lower_russian_alp = {key: chr(value) for key, value in zip(range(32), range(1072, 1104))}


def encrypt(original_string: str, shift_key: int) -> str:
    enc = ''
    for i in original_string:
        if i.isupper() and i.isalpha() and i in upper_english_alp.values():
            enc += upper_english_alp.get(((ord(i) + shift_key - ord('A')) % 26))
        elif i.lower() and i.isalpha() and i in lower_english_alp.values():
            enc += lower_english_alp.get(((ord(i) + shift_key - ord('a')) % 26))
        elif i.isupper() and i.isalpha() and i in upper_russian_alp.values():
            enc += upper_russian_alp.get(((ord(i) + shift_key - ord('А')) % 32))
        elif i.lower() and i.isalpha() and i in lower_russian_alp.values():
            enc += lower_russian_alp.get(((ord(i) + shift_key - ord('а')) % 32))
        else:
            enc += i
    return enc


def decrypt(encrypted_string: str, shift_key: int) -> tuple[str, int, int]:
    dec = ''
    ru = 0
    en = 0
    for i in encrypted_string:
        if i.isupper() and i.isalpha() and i in upper_english_alp.values():
            dec += upper_english_alp.get(((ord(i) - shift_key - ord('A')) % 26))
            en += 1
        elif i.lower() and i.isalpha() and i in lower_english_alp.values():
            dec += lower_english_alp.get(((ord(i) - shift_key - ord('a')) % 26))
            en += 1
        elif i.isupper() and i.isalpha() and i in upper_russian_alp.values():
            dec += upper_russian_alp.get(((ord(i) - shift_key - ord('А')) % 32))
            ru += 1
        elif i.lower() and i.isalpha() and i in lower_russian_alp.values():
            dec += lower_russian_alp.get(((ord(i) - shift_key - ord('а')) % 32))
            ru += 1
        else:
            dec += i
    return dec, ru, en
Расскажу вкратце:
С помощью генератора словарей создаем 4 словаря (2 для английского и 2 для русского алфавитов, lower и upper case)
Ключами выступают числа от 0 до длина алфавита, а значениями сами буквы алфавита.
Сами функции отличаются только тем, что при шифровании мы прибавляем шаг(ROT key) и при расшифровке отнимаем.
Функция encrypt возвращает строку -> str, а decrypt кортеж -> tuple[str, int, int]
В decrypt я считаю количество символов русского и английского алфавитов, потом использую это в функции bruteforce.

Ищем остаток от деления для англ алфавита 26, для нашего 32!, как оказалось в юникоде Ё идет НЕ после Е :)
Можно докрутить, словарь сделать с Ё или заменять ее на Е. В данный момент это не принципиально.
Проводим некоторые проверки if i.isupper() and i.isalpha() and i in upper_english_alp.values()
Регистр, буква ли это и принадлежность к алфавиту. Сделано для того чтобы приложение не трогало цифры, спец. символы, пробелы и т.д.

Теперь нам надо связать интерфейс с приложением.
Каркас приложения - main.py:
Python:
import sys
from cipher_functions import decrypt, encrypt
from PySide6.QtWidgets import QApplication, QMainWindow, QMessageBox
from design import Ui_MainWindow


class RotDecoder(QMainWindow):
    def __init__(self):
        super(RotDecoder, self).__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)


if __name__ == "__main__":
    app = QApplication(sys.argv)

    window = RotDecoder()
    window.show()

    sys.exit(app.exec())

Есть пару вариантов превратить наш design.ui в design.py
1. Из Qt designer - Форма -> Показать код Python, далее копируем или сохраняем в design.py
2. В PyCharm Alt + F12 pyside6-uic .\design.ui -o .\design.py
Ресурсы - pyside6-rcc .\resources.qrc -o resources.py
Ресурсы линкуются в файле design.py, проверяем правильность импорта: import resources

Свяжем spinbox и slider по изменению значений, создадим функцию в классе RotDecoder файла main.py:
Python:
def connection_slider_spinbox(self) -> None:
    self.ui.horizontalSlider.valueChanged.connect(self.ui.spinBox.setValue)
    self.ui.spinBox.valueChanged.connect(self.ui.horizontalSlider.setValue)

Прописываем метод в конструкторе класса:
Python:
class RotDecoder(QMainWindow):
    def __init__(self):
        ...
        ...
        ...
        self.connection_slider_spinbox()

Добавим наш функционал:
Python:
def decrypting(self):
    self.ui.textEdit_2.setText(decrypt(self.ui.textEdit.toPlainText(), self.ui.spinBox.value())[0])

 
def encrypting(self):
    self.ui.textEdit_2.setText(encrypt(self.ui.textEdit.toPlainText(), self.ui.spinBox.value()))
Берем текст из textEdit и помещает его в textEdit_2
Кнопки Decrypt и Encrypt не могут быть в одинаковом состоянии(checked), меняем при нажатии:
Python:
def checked_dec_btn(self):
    self.ui.decrypt_btn.nextCheckState()
    self.encrypting()
    self.ui.spinBox.valueChanged.connect(self.encrypting)
    self.ui.textEdit.textChanged.connect(self.encrypting)

def checked_enc_btn(self):
    self.ui.encrypt_btn.nextCheckState()
    self.decrypting()
    self.ui.spinBox.valueChanged.connect(self.decrypting)
    self.ui.textEdit.textChanged.connect(self.decrypting)

Кнопка BRUTEFORCE:
Python:
def bruteforce(self):
    self.ui.textEdit_2.clear()
    if decrypt(self.ui.textEdit.toPlainText(), 1)[1] > decrypt(self.ui.textEdit.toPlainText(), 1)[2]:
        for i in range(1, 33):
            ls = ['ROT key ' + str(i) + ': ' + decrypt(self.ui.textEdit.toPlainText(), i)[0] + '\n' for i in
                  range(1, 33)]
            with open('bruted.txt', 'w', encoding='UTF-8') as file:
                file.writelines(ls)
                self.ui.textEdit_2.append(f'ROT key {i}:{decrypt(self.ui.textEdit.toPlainText(), i)[0]}')
    else:
        for i in range(1, 27):
            ls = ['ROT key ' + str(i) + ': ' + decrypt(self.ui.textEdit.toPlainText(), i)[0] + '\n' for i in
                  range(1, 27)]
            with open('bruted.txt', 'w', encoding='UTF-8') as file:
                file.writelines(ls)
                self.ui.textEdit_2.append(f'ROT key {i}: {decrypt(self.ui.textEdit.toPlainText(), i)[0]}')
Брутфорс выводит и записывает в файл bruted.txt все возможные комбинации от 1 до конца алфавита.

Заполняем класс нашими методами:
Python:
class RotDecoder(QMainWindow):
    def __init__(self):
        super(RotDecoder, self).__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.connection_slider_spinbox()
        self.decrypting()
        self.ui.spinBox.valueChanged.connect(self.decrypting)
        self.ui.textEdit.textChanged.connect(self.decrypting)
        self.ui.encrypt_btn.clicked.connect(self.checked_dec_btn)
        self.ui.decrypt_btn.clicked.connect(self.checked_enc_btn)
        self.ui.brute_btn.clicked.connect(self.bruteforce)
        self.ui.actionExit.triggered.connect(sys.exit)
        self.ui.actionabout.triggered.connect(self.about)

Сборка приложения под Windows:
Осталось превратить наш проект в исполняемый файл. Компиляцию можно делать разными способами, например PyInstaller или Nuitka.
Я выбрал второе, устанавливаем Nuitka:
pip install nuitka
Чтобы собрать все приложение в один единственный исполняемый EXE файл на понадобится библиотека zstandart:
pip install zstandard

Код:
nuitka --onefile --follow-imports --enable-plugin=pyside6 --windows-disable-console --windows-icon-from-ico=res\key.ico --remove-output -o caesar_cipher.exe main.py
--onefile - собираем в 1 файл
--follow-imports - соблюдаем импорты
--enable-plugin=pyside6 - добавляем плагин PySide6
--windows-disable-console - убираем консольное окно
--windows-icon-from-ico=res\key.ico - ставим иконку из файла
--remove-output - после удаляем все папки генерируемые во время компиляции

2023-04-05_13-45-26.png


Кстати говоря наше приложение получилось кроссплатформенным и при желание его можно собрать под MacOS или Linux!
Прикладываю файлы проекта и скомпилированный исходник. Вот так вот "просто" можно собрать GUI для приложения на Python! :)

Если что-то забыл указать, спрашивайте! Отвечу, исправлю.

caesar_decoder.gif


За сием прощаюсь, до новых встреч!
 
Последнее редактирование модератором:
12.07.2022
15
11
BIT
29
Это отличная статья, конкретно полезная для всех, кому нужен на Python не просто скрипт, а приложение с интерфейсом. "Как прикрутить к Python QT" - это сейчас одна из самых популярных тем, наверное, а тут все предельно понятно и доходчиво расписано. Автор молодца
 
  • Нравится
Реакции: Exited3n

Polyglot

Green Team
25.02.2020
45
57
BIT
235
Статья хорошая, спасибо. PySide6 как раз поинтереснее шифра Цезаря будет, иерархия обьектов в нем, networking и другие вспомогательные
фишки , система сигналов, итд.
 

Exited3n

Red Team
10.05.2022
724
259
BIT
660
Статья хорошая, спасибо. PySide6 как раз поинтереснее шифра Цезаря будет, иерархия обьектов в нем, networking и другие вспомогательные
фишки , система сигналов, итд.
Спасибо.
Цезарь тут для "прикрытия", интересен конечно PySide, не голый же интерфейс лепить.
С сигналами и событиями (qt signals and events) как раз сейчас сам разбираюсь.
Хочу сделать кликабельные картинки, но не с помощью кнопок с иконками и убранным текстом, а помощью QLabel.
Отслеживаю мышку по главному окну и все прекрасно работает, а вот по лейблу пока не получается.
 

Exited3n

Red Team
10.05.2022
724
259
BIT
660
Ну способ нашел, работает.

Python:
def clickable_label(e, url='https://python.org'):
    os.startfile(url)


class AboutBox(QWidget):
    def __init__(self):
        super(AboutBox, self).__init__()
        self.abox = Ui_Form()
        self.abox.setupUi(self)
        self.abox.picture_label.mousePressEvent = clickable_label

self.abox.picture_label.mousePressEvent = clickable_label событие принимает функцию и отрабатывает как надо, по клику на картинке лейблу осуществляется переход по ссылке стандартным браузером. Не знаю, правильно ли это вообще :)
 

fancynex

New member
09.04.2023
1
1
BIT
0
Автор, спасибо! Как раз сейчас делаю пет проект, завязанный на алгоритмах криптографии и Питона)
 
  • Нравится
Реакции: Виктор Зотов

Privatnic

New member
19.07.2020
1
0
BIT
0
Здорово, спасибо. Теперь скинул жене цезаря и общаемся в общем чате кракозябрами)
 
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!