希尔密码加解密(Python)

文章目录

    • 前言
    • 算法原理
    • 加解密代码
    • 图形界面代码
      • 运行效果图
    • 遇到的问题及解决方案

前言

这是之前的密码学课设,因为一直在忙期末考试,所以到现在才发出来。exe程序可以去希尔密码(Hill密码)转换工具.exe下载。

算法原理

每个字母当作26进制数字:A=0, B=1, C=2… 一串字母当成n维向量,跟一个n×n的矩阵相乘,再将得出的结果模26。
注意用作加密的矩阵(即密钥)必须是可逆的,否则就不可能解码。只有矩阵的行列式和26互质,才是可逆的。

希尔密码加解密(Python)_第1张图片

加解密代码

import numpy as np
import sys


# 判断矩阵是否存在逆矩阵
def judge_inverse_matrix(matrix):
    try:
        np.linalg.inv(matrix)
    except:
        return False
    return True


# 输入列表并转换为矩阵
def inputmatrix():
    row_num = int(input("请输入矩阵的行数:"))
    all_list = []
    for i in range(1, row_num + 1):
        row = input(f"请输入加密矩阵第{i}行(以空格为分隔):")
        if row[0] == ' ':
            print("输入有误,第一位不该为空格")
            sys.exit()
        else:
            row_list = row.split(' ')
        # 将列表中str转换为int
        if len(row_list) == row_num:
            for n in row_list:
                row_list[row_list.index(n)] = int(row_list[row_list.index(n)])
            all_list.append(row_list)
        else:
            print("前后输入的行数不一致,请重修输入")
            break
    encrypt_matrix = np.array(all_list)
    if not judge_inverse_matrix(encrypt_matrix):
        print("该矩阵不存在逆矩阵,请重修输入")
    return encrypt_matrix


# 生成矩阵的逆矩阵。如果逆矩阵含有小数,就四舍五入
def generate_inverse_matrix(matrix):
    inverse_matrix = np.linalg.inv(matrix)
    for row in inverse_matrix:
        for num in row:
            num = round(num)
    print("加密矩阵的逆矩阵为:")
    for array in inverse_matrix:
        print(array)
    return inverse_matrix


# 生成字母-数字对应的字典
def alphabet_number():
    alphabet_number_dict = {}
    for i in range(97, 123):
        alphabet_number_dict[chr(i)] = i % 97
    return alphabet_number_dict


def encrypt():
    # 明文字母转换成对应数字
    input_plaintext = input("请输入明文:")
    num_list = []
    dic = alphabet_number()
    for i in input_plaintext:
        num_list.append(dic[i])

    # 如果矩阵行数不能整除明文,则用'z'的数字25补全
    matrix = inputmatrix()
    row_num = len(matrix)
    supple_num = row_num - (len(num_list) % row_num)
    if len(num_list) % row_num != 0:
        for n in range(1, supple_num + 1):
            num_list.append(25)
    print(f"\n添加了{supple_num}个z补全明文")

    # 分组加密
    group_num = int(len(num_list) / row_num)
    whole_encrypt_num_list = []
    for g in range(0, group_num):
        plaintext_matrix = np.array(num_list[0 + g * row_num: (g + 1) * row_num])
        encrypt_num_list = np.matmul(plaintext_matrix, matrix)
        for num in encrypt_num_list:
            whole_encrypt_num_list.append(num)

    # 将加密后的数字转换为字母
    ciphertext = ""
    for ennum in whole_encrypt_num_list:
        # 对超出范围的数字取模
        if ennum > 25:
            ennum = ennum % 26
        for k in dic:
            if dic[k] == ennum:
                ciphertext = ciphertext + k
    print("加密后密文为:", ciphertext, '\n')


def decrypt():
    # 输入密文并转换为对应数字
    input_ciphertext = input("请输入密文:")
    num_list2 = []
    dic2 = alphabet_number()
    for i in input_ciphertext:
        num_list2.append(dic2[i])

    # 解密就不添加'z'来补全密文了
    matrix = inputmatrix()
    row_num2 = len(matrix)
    supple_num2 = row_num2 - (len(num_list2) % row_num2)

    # 用逆矩阵分组解密
    inserve_matrix = generate_inverse_matrix(matrix)
    group_num2 = int(len(num_list2) / row_num2)
    whole_decrypt_num_list = []
    for g in range(0, group_num2):
        plaintext_matrix = np.array(num_list2[0 + g * row_num2: (g + 1) * row_num2])
        decrypt_num_list = np.matmul(plaintext_matrix, inserve_matrix)
        for num in decrypt_num_list:
            whole_decrypt_num_list.append(num)

    # 将解密后的数字转换为对应字母
    plaintext = ""
    for denum in whole_decrypt_num_list:
        if denum > 25 or denum < -26:
            denum = denum % 26

        # 防止取模后是负数,字典中找不到对应的字母
        if denum < 0:
            denum = denum + 26
        # 字典中寻找与数字对应的字母
        for k in dic2:
            if dic2[k] == denum:
                plaintext = plaintext + k
    print("解密后明文为:", plaintext, '\n')


if __name__ == '__main__':
    while True:
        print("========Hill密码========\n")
        print("1.加密\n2.解密\n")
        print("注意:如果输入矩阵的逆矩阵中含有小数,采用四舍五入的方法\n")
        pattern = input("请选择模式:")
        if pattern == '1':
            encrypt()
        elif pattern == '2':
            decrypt()
        else:
            print("输入有误,请重修输入")
  • 这里主要是采用了numpy模块来实现判断输入矩阵的逆矩阵存在与否以及矩阵间的乘法;
  • 同时,因为输入的明文可能在分组后的列无法与矩阵的行数相等,所以当不相等时,自动在明文后用字母z补全;
  • 还有一个主要的问题就是输入矩阵的逆矩阵可能存在小数,因此这里采用了四舍五入的方法,当输入的矩阵数字过大时,可能会造成解密结果不准确。

图形界面代码

import numpy as np
import sys
from tkinter import *


class MY_GUI():
    def __init__(self, init_window_name):
        self.init_window_name = init_window_name

    # 设置窗口
    def set_init_window(self):
        self.init_window_name.title("Hill密码转换工具")  # 窗口名
        self.init_window_name.geometry('700x500+300+120')
        self.init_window_name["bg"] = "Lavender"

        # 标签
        self.crypto_string_label = Label(self.init_window_name, text="加/解密字符串", font=13)
        self.crypto_string_label.place(x=80, y=60)

        self.row_num_label = Label(self.init_window_name, text="矩阵行数", font=13)
        self.row_num_label.place(x=120, y=120)

        self.encrypt_matrix_label = Label(self.init_window_name, text="加密矩阵", font=13)
        self.encrypt_matrix_label.place(x=120, y=220)

        self.encrypt_matrix_label = Label(self.init_window_name, text="(空格分隔)", font=13)
        self.encrypt_matrix_label.place(x=110, y=240)

        self.crypto_result_label = Label(self.init_window_name, text="加/解密结果", font=13)
        self.crypto_result_label.place(x=100, y=330)

        self.supple_string_label = Label(self.init_window_name, text="字符增补记录", font=13)
        self.supple_string_label.place(x=90, y=400)

        # 文本框
        self.crypto_string_Text = Text(self.init_window_name, width=40, height=2)  # 加/解密字符串
        self.crypto_string_Text.place(x=220, y=60)

        self.row_num_Text = Text(self.init_window_name, width=40, height=2)  # 矩阵行数
        self.row_num_Text.place(x=220, y=120)

        self.encrypt_matrix_Text = Text(self.init_window_name, width=40, height=9)  # 加密矩阵
        self.encrypt_matrix_Text.place(x=220, y=180)

        self.crypto_result_Text = Text(self.init_window_name, width=40, height=2)  # 加/解密结果
        self.crypto_result_Text.place(x=220, y=330)

        self.supple_string_Text = Text(self.init_window_name, width=40, height=2)
        self.supple_string_Text.place(x=220, y=400)

        # 按钮
        self.encrypt_button = Button(self.init_window_name, text="加密", bg="LavenderBlush", width=10,
                                     command=self.encrypt)
        self.encrypt_button.place(x=580, y=150)

        self.decrypt_button = Button(self.init_window_name, text="解密", bg="LavenderBlush", width=10,
                                     command=self.decrypt)
        self.decrypt_button.place(x=580, y=300)

    # 判断矩阵是否存在逆矩阵
    def judge_inverse_matrix(self, matrix):
        try:
            np.linalg.inv(matrix)
        except:
            return False
        return True

    # 输入列表并转换为矩阵
    def inputmatrix(self):
        row_num = int(self.row_num_Text.get("1.0", "end"))
        all_list = []
        num_list2 = []
        num_list = self.encrypt_matrix_Text.get("1.0", "end").split(' ')
        for a in num_list:
            # 文本框输入的最后一位有换行符,需要去掉
            num_list2.append(int(a.strip('\n')))

        # 生成矩阵
        for num in num_list2:
            num_list2[num_list2.index(num)] = int(num_list2[num_list2.index(num)])
        for i in range(0, len(num_list2), row_num):
            all_list.append(num_list2[i: i + row_num])
        encrypt_matrix = np.array(all_list)
        if not self.judge_inverse_matrix(encrypt_matrix):
            # print("该矩阵不存在逆矩阵,请重修输入")
            self.crypto_result_Text.delete("1.0", "end")
            self.crypto_result_Text.insert("1.0", "该矩阵不存在逆矩阵,请重修输入")
        return encrypt_matrix

    # 生成矩阵的逆矩阵。如果逆矩阵含有小数,就四舍五入
    def generate_inverse_matrix(self, matrix):
        inverse_matrix = np.linalg.inv(matrix)
        print("加密矩阵的逆矩阵为:")
        for array in inverse_matrix:
            print(array)
        return inverse_matrix

    # 生成字母-数字对应的字典
    def alphabet_number(self):
        alphabet_number_dict = {}
        for i in range(97, 123):
            alphabet_number_dict[chr(i)] = i % 97
        return alphabet_number_dict

    def encrypt(self):
        # 按下加密按钮后获取文本框内明文
        input_plaintext = self.crypto_string_Text.get("1.0", "end").strip('\n')

        # 明文字母转换成对应数字
        num_list = []
        dic = self.alphabet_number()
        for i in input_plaintext:
            num_list.append(dic[i])

        # 如果矩阵行数不能整除明文,则用'z'的数字25补全
        matrix = self.inputmatrix()
        row_num = len(matrix)
        supple_num = row_num - (len(num_list) % row_num)
        if len(num_list) % row_num != 0:
            for n in range(1, supple_num + 1):
                num_list.append(25)
        output = f"添加了{supple_num}个z补全明文"
        self.supple_string_Text.delete("1.0", "end")
        self.supple_string_Text.insert("1.0", output)

        # 分组加密
        group_num = int(len(num_list) / row_num)
        whole_encrypt_num_list = []
        for g in range(0, group_num):
            plaintext_matrix = np.array(num_list[0 + g * row_num: (g + 1) * row_num])
            encrypt_num_list = np.matmul(plaintext_matrix, matrix)
            for num in encrypt_num_list:
                whole_encrypt_num_list.append(num)

        # 将加密后的数字转换为字母
        ciphertext = ""
        for ennum in whole_encrypt_num_list:
            # 对超出范围的数字取模
            if ennum > 25:
                ennum = ennum % 26
            for k in dic:
                if dic[k] == ennum:
                    ciphertext = ciphertext + k
        self.crypto_result_Text.delete("1.0", "end")
        self.crypto_result_Text.insert("1.0", ciphertext)
        # print("加密后密文为:", ciphertext, '\n')

    def decrypt(self):
        # 输入密文并转换为对应数字
        input_ciphertext = self.crypto_string_Text.get("1.0", "end").strip('\n')
        num_list2 = []

        dic2 = self.alphabet_number()
        for i in input_ciphertext:
            num_list2.append(dic2[i])

        # 解密就不添加'z'来补全密文了
        matrix = self.inputmatrix()
        row_num2 = len(matrix)
        supple_num2 = row_num2 - (len(num_list2) % row_num2)

        # 用逆矩阵分组解密
        inserve_matrix = self.generate_inverse_matrix(matrix)
        group_num2 = int(len(num_list2) / row_num2)
        whole_decrypt_num_list = []
        for g in range(0, group_num2):
            plaintext_matrix = np.array(num_list2[0 + g * row_num2: (g + 1) * row_num2])
            decrypt_num_list = np.matmul(plaintext_matrix, inserve_matrix)
            for num in decrypt_num_list:
                whole_decrypt_num_list.append(num)

        # 将矩阵中的数字四舍五入
        for j in range(len(whole_decrypt_num_list)):
            whole_decrypt_num_list[j] = round(whole_decrypt_num_list[j])

        # 将解密后的数字转换为对应字母
        plaintext = ""
        for denum in whole_decrypt_num_list:
            if denum > 25 or denum < -26:
                denum = denum % 26

            # 防止取模后是负数,字典中找不到对应的字母
            if denum < 0:
                denum = denum + 26

            # 字典中寻找与数字对应的字母
            for k in dic2:
                if dic2[k] == denum:
                    plaintext = plaintext + k
        self.crypto_result_Text.delete("1.0", "end")
        self.crypto_result_Text.insert("1.0", plaintext)
        # print("解密后明文为:", plaintext, '\n')
        self.supple_string_Text.delete("1.0", "end")


def gui_start():
    init_window = Tk()  # 实例化出一个父窗口
    ZMJ_PORTAL = MY_GUI(init_window)
    # 设置根窗口默认属性
    ZMJ_PORTAL.set_init_window()

    init_window.mainloop()  # 父窗口进入事件循环,可以理解为保持窗口运行,否则界面不展示


if __name__ == '__main__':
    gui_start()

运行效果图

加密:
希尔密码加解密(Python)_第2张图片
解密:
希尔密码加解密(Python)_第3张图片

遇到的问题及解决方案

当时最主要是遇到了以下的几个问题:

  1. 当时用的win10,python自带的pyinstaller编译成exe运行,打包过后程序太大,有160MB?
    答:原因是电脑中的python文件太多了,pyinstaller编译时把所有文件全部编译到exe程序中,导致程序占用空间过大,只需要在全新的虚拟机中编译,并用upx压缩,文件就只剩15MB左右了。

  2. win7电脑运行exe程序时出现api-ms-win-core-path-l1-1-0.dll 丢失?
    答:python3.9(最新版本)已经不支持win7操作系统了,可以选择低一些的版本(python3.7)来重新把py文件编译成exe程序。

  3. 在不同电脑上打开exe程序时,按键、文字分布在不同地方?
    答:原因是每台电脑的分辨率不同。

你可能感兴趣的:(小工具,Python,#,Python基础,密码学,python)