效果如下:

起因:一些NSFW的话题,不想在经常使用的即时通讯工具中发送明文。之前写了传话机V1.0,但是是对称加密,传话机软件暴露了就可以被解密了Q Q

设计思路:生成随机公私钥对 -> A提供公钥 -> B拿A的公钥进行加密 -> B把密文给A -> A用私钥进行解密

设计界面:公钥生成文本框、别人提供的公钥文本框、加密文本框、解密文本框、相应功能按钮

功能代码:

from PySide2.QtWidgets import QApplication, QMessageBox
from PySide2.QtUiTools import QUiLoader
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import rsa, padding

class Stats:
    def __init__(self):
        self.ui = QUiLoader().load('rsa.ui')
        self.ui.setFixedSize(703, 566)
        self.ui.gongyao.clicked.connect(self.gongyao)
        self.ui.copy_gongyao.clicked.connect(self.copy_gongyao)
        self.ui.other_jiami.clicked.connect(self.other_jiami)
        self.ui.other_fuzhi.clicked.connect(self.other_fuzhi)
        self.ui.siyao_jiemi.clicked.connect(self.siyao_jiemi)
        self.ui.siyao_copy.clicked.connect(self.siyao_copy)
        self.private_key_bytes = None

    def gongyao(self):
        private_key = rsa.generate_private_key(
            public_exponent=65537,
            key_size=2048,
            backend=default_backend()
        )
        public_key = private_key.public_key()
        public_key_bytes = public_key.public_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PublicFormat.SubjectPublicKeyInfo
        )
        self.private_key_bytes = private_key.private_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PrivateFormat.TraditionalOpenSSL,
            encryption_algorithm=serialization.NoEncryption()
        )
        self.ui.gongyao_text.clear()
        self.ui.gongyao_text.append(str(public_key_bytes.decode("utf-8")))

    def copy_gongyao(self):
        gongyao_to_copy = self.ui.gongyao_text.toPlainText()
        clipboard = QApplication.clipboard()
        clipboard.setText(gongyao_to_copy)
        
    def other_jiami(self):
        plaintext_to_encrypt = self.ui.other_text.toPlainText()
        other_public_key_str = self.ui.other_gongyao.toPlainText()
        try:
            other_public_key = serialization.load_pem_public_key(
                other_public_key_str.encode('utf-8'),
                backend=default_backend()
            )
            encrypted_text = other_public_key.encrypt(
                plaintext_to_encrypt.encode('utf-8'),
                padding.OAEP(
                mgf=padding.MGF1(algorithm=hashes.SHA256()),
                algorithm=hashes.SHA256(),
                label=None
                )
            )
            self.ui.other_text.clear()
            self.ui.other_text.append(str(encrypted_text.hex()))
        except Exception as e:
            QMessageBox.about(self.ui, '错误', f'加密失败:{str(e)}')
            
    def other_fuzhi(self):
        jiami_to_copy = self.ui.other_text.toPlainText()
        clipboard = QApplication.clipboard()
        clipboard.setText(jiami_to_copy)
        
    def siyao_jiemi(self):
        ciphertext_to_decrypt = self.ui.siyao_text.toPlainText()
        private_key = str(self.private_key_bytes.decode("utf-8"))
        try:
            private_key_obj = serialization.load_pem_private_key(
                self.private_key_bytes,
                password=None,
                backend=default_backend()
            )
            decrypted_text = private_key_obj.decrypt(
                bytes.fromhex(ciphertext_to_decrypt),
                padding.OAEP(
                    mgf=padding.MGF1(algorithm=hashes.SHA256()),
                    algorithm=hashes.SHA256(),
                    label=None
                )
            )
            self.ui.siyao_text.clear()
            self.ui.siyao_text.append(decrypted_text.decode('utf-8'))
        except Exception as e:
            QMessageBox.about(self.ui, '错误', f'解密失败:{str(e)}')

    def siyao_copy(self):
        jiami_to_copy = self.ui.siyao_text.toPlainText()
        clipboard = QApplication.clipboard()
        clipboard.setText(jiami_to_copy)
        
app = QApplication([])
stats = Stats()
stats.ui.show()
app.exec_()

QSS美化:默认的感觉不太好看,创建了一个geek_style.qss。用chatgpt生成了一个有极客风格的样式表。

/* Main Window */
QWidget {
    background-color: #121212;
    color: #b8b8b8;
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

/* Buttons */
QPushButton {
    background-color: #4CAF50;
    color: #FFFFFF;
    border: 2px solid #4CAF50;
    border-radius: 6px;
}

QPushButton:hover {
    background-color: #45a049;
}

/* Text Edit Boxes */
QTextEdit {
    background-color: #212121;
    color: #b8b8b8;
    border: 2px solid #4CAF50;
    border-radius: 4px;
    padding: 5px;
}

/* Labels */
QLabel {
    color: #4CAF50;
}

/* Message Box */
QMessageBox {
    background-color: #212121;
    color: #b8b8b8;
    border: 2px solid #4CAF50;
}

/* Message Box Buttons */
QMessageBox QPushButton {
    background-color: #4CAF50;
    color: #FFFFFF;
    border: 2px solid #4CAF50;
    border-radius: 6px;
    padding: 4px 8px;
}

QMessageBox QPushButton:hover {
    background-color: #45a049;
}

引入:

...
from PySide2.QtCore import QFile, QTextStream
...

class Stats:
    def __init__(self):
        # ... (省略原有代码)
        self.apply_geek_style()
        self.private_key_bytes = None
        
    def apply_geek_style(self):
        # 读取自定义样式表
        style_file = QFile('geek_style.qss')
        if style_file.open(QFile.ReadOnly | QFile.Text):
            style_stream = QTextStream(style_file)
            style_sheet = style_stream.readAll()
            self.ui.setStyleSheet(style_sheet)
            style_file.close()
            
    ...
        
app = QApplication([])
stats = Stats()
stats.ui.show()
app.exec_()

生成exe:使用pyinstaller+upx,添加了图标(logo.png)。主程序图标设定:

from PySide2.QtGui import QIcon

app = QApplication([])
# 加载 icon
app.setWindowIcon(QIcon('logo.png'))

打包:

pyinstaller.exe -F -w -i logo.png rsa.py

upx --force rsa.exe

使用Inno Setup将exe、qss、ui、logo这堆都打包成一个安装程序。

特别感谢:EDS、Chatgpt