AES加密算法详细加密步骤代码实现--身份证号码加解密系统

系统概述

本系统是一个基于AES-256-CBC加密算法的身份证号码加解密工具(手搓底层步骤),针对的是上一篇文章对的AES加密原理的讲解,虽说是演示,但功能完善,可单独提供接口给项目调用,采用Python开发,具有图形化用户界面。系统实现了完整的AES-256加密算法,支持多密钥管理,并提供了安全可靠的身份证号码加密和解密功能。

源码请查看顶部绑定资源完整源码文件

或底部源码自行拷贝

上一篇文章链接:

AES加密算法原理讲解--基于AES-256加密算法实现身份证号码的加解密程序项目讲解_aes256 64位key反算密码如何实现-CSDN博客

主要特性

  • 高强度加密:采用AES-256-CBC加密模式,提供高级别的数据安全保护
  • 完整实现:从底层实现AES算法的所有组件,包括S盒、轮常量、密钥扩展等
  • 多密钥支持:支持生成、导入和管理多个加密密钥
  • 密钥指纹识别:通过SHA256哈希生成密钥指纹,确保密钥匹配
  • 历史记录管理:自动保存加解密历史,避免重复操作
  • 现代化界面:基于CustomTkinter的美观用户界面,支持明暗主题切换

技术架构

核心算法组件

1. AES-256核心实现

AES256类是系统的核心加密引擎,实现了完整的AES-256算法:

  • 字节替换(SubBytes):使用标准S盒进行非线性字节替换
  • 行移位(ShiftRows):对状态矩阵的行进行循环移位
  • 列混淆(MixColumns):通过矩阵乘法实现列间数据扩散
  • 轮密钥加(AddRoundKey):将轮密钥与状态矩阵异或
  • 密钥扩展:将256位主密钥扩展为15个128位轮密钥
2. CBC模式封装

AES256_CBC类实现了密码块链接模式:

  • PKCS7填充:自动处理数据块对齐
  • 初始向量(IV):每次加密生成随机16字节IV
  • 链式加密:前一个密文块参与下一个明文块的加密
3. 密钥管理系统
  • JSON格式存储:密钥以JSON格式持久化存储
  • 时间戳记录:记录每个密钥的生成时间
  • 指纹验证:使用SHA256前4字节作为密钥指纹
  • 自动匹配:解密时自动匹配正确的密钥

系统安装与运行

环境要求

必需的Python包,但是只有最后一个需要安装,其余的不需要(内置)环境为python3.7以上

import datetime

import json

import os

import base64

import hashlib

import tkinter as tk

from tkinter import messagebox, simpledialog, ttk

import customtkinter as ctk

安装步骤

  1. 安装CustomTkinter
  • pip install customtkinter
  1. 运行程序

详细操作指南

主界面功能

启动程序后,您将看到简洁的主界面,包含两个主要功能按钮:

  • 加密身份证:进入身份证号码加密功能
  • 解密身份证:进入密文解密功能

加密操作流程

1. 进入加密界面

点击"加密身份证"按钮,进入加密功能界面。界面包含以下区域:

  • 密钥管理区域:显示当前密钥指纹,提供密钥生成和导入功能
  • 输入区域:身份证号码输入框
  • 输出区域:显示加密结果
  • 密钥历史区域:显示所有历史密钥,支持双击切换
2. 密钥管理

生成新密钥

  • 点击"生成新密钥"按钮
  • 系统自动生成256位随机密钥
  • 密钥自动保存到aes_keys.json文件
  • 界面显示新密钥的指纹信息

导入自定义密钥

  • 点击"导入密钥"按钮
  • 在弹出对话框中输入64位十六进制密钥(32字节)
  • 系统验证密钥格式并检查是否重复
  • 成功导入后更新当前使用的密钥

切换历史密钥

  • 在密钥历史表格中双击任意密钥记录
  • 系统自动切换到选中的密钥
  • 当前密钥指纹显示更新
3. 执行加密
  1. 输入身份证号码
    • 在输入框中输入18位身份证号码
    • 系统自动验证格式(前17位数字,第18位数字或X)
  2. 点击加密按钮
    • 系统生成随机16字节初始向量(IV)
    • 使用当前密钥执行AES-256-CBC加密
    • 组合IV、密钥指纹和密文
    • 进行Base64编码便于存储和传输
  3. 查看结果
    • 加密成功后,密文显示在输出区域
    • 密文自动保存到ciphertext1.txt文件
    • 每个密文占用一行,便于批量管理
4. 加密数据格式

生成的密文采用以下格式:

16字节IV\] + \[4字节密钥指纹\] + \[实际密文数据

经过Base64编码后的最终格式便于存储和传输。

解密操作流程

1. 进入解密界面

点击"解密身份证"按钮,进入解密功能界面。界面包含:

  • 密钥文件路径显示:显示密钥存储文件的绝对路径
  • 当前密钥指纹显示:显示正在使用的密钥信息
  • 文件操作区域:密文文件加载和输入清空功能
  • 密文列表:显示已保存的所有密文
  • 密文输入区域:手动输入或显示选中的密文
  • 解密按钮区域:默认密钥和自定义密钥解密选项
  • 结果输出区域:显示解密结果
2. 加载密文数据

从文件加载

  • 点击"选择密文文件"按钮
  • 系统自动读取ciphertext1.txt文件
  • 所有密文显示在列表中(显示前30个字符)
  • 双击列表项可将完整密文加载到输入区域

手动输入

  • 直接在密文输入区域粘贴或输入Base64编码的密文
  • 支持多行密文输入
3. 执行解密

使用默认密钥解密

  1. 确保密文已输入到输入区域
  2. 点击"使用默认密钥解密"按钮
  3. 系统自动:
    • 解析密文中的密钥指纹
    • 在密钥库中查找匹配的密钥
    • 提取IV和实际密文数据
    • 执行AES-256-CBC解密
    • 显示解密后的身份证号码

使用自定义密钥解密

  1. 点击"使用自定义密钥解密"按钮
  2. 在弹出对话框中输入64位十六进制密钥
  3. 系统验证密钥与密文指纹是否匹配
  4. 匹配成功则执行解密并显示结果
4. 解密结果处理
  • 成功解密:在结果区域显示"解密成功!身份证号码: [号码]"
  • 自动记录 :解密记录保存到plaintext_history.json文件
  • 重复检测:系统检测是否已解密过相同密文,避免重复记录
  • 错误处理:密钥不匹配或数据损坏时显示具体错误信息

文件系统说明

核心文件

  1. AES-256-CBC.py:主程序文件,包含所有功能实现
  2. aes_keys.json:密钥存储文件,JSON格式
  3. ciphertext1.txt:加密后的密文存储文件
  4. plaintext_history.json:解密历史记录文件

密钥文件格式

{ "timestamp": "2024-01-15 10:30:45", "key": "64位十六进制密钥字符串", "fingerprint": "8位十六进制指纹" }

解密历史格式

{ "timestamp": "2024-01-15 10:35:20", "plaintext": "解密后的身份证号码", "ciphertext": "原始密文", "key_fingerprint": "使用的密钥指纹", "key_type": "default或custom" }

安全特性

加密安全性

  1. AES-256标准:采用美国国家标准技术研究所(NIST)认证的AES-256算法
  2. CBC模式:密码块链接模式提供更强的安全性
  3. 随机IV:每次加密使用不同的初始向量,防止相同明文产生相同密文
  4. 密钥指纹:SHA256哈希确保密钥完整性验证

数据保护

  1. 本地存储:所有数据存储在本地,不涉及网络传输
  2. 密钥分离:密钥与密文分别存储,提高安全性
  3. 格式验证:严格的输入格式验证防止无效数据
  4. 错误处理:完善的异常处理机制保护系统稳定性

常见问题解答

Q1: 忘记密钥怎么办?

:如果忘记了自定义密钥,可以通过以下方式找回:

  1. 查看aes_keys.json文件中的历史密钥
  2. 根据密钥指纹匹配正确的密钥
  3. 如果密钥文件丢失,则无法恢复加密数据

Q2: 密文文件损坏如何处理?

  1. 检查密文是否为完整的Base64编码
  2. 确认密文长度是否正确(至少20字节的Base64编码)
  3. 验证使用的密钥是否与加密时一致
  4. 如果数据确实损坏,则无法恢复

Q3: 如何备份重要数据?

:建议定期备份以下文件:

  1. aes_keys.json - 密钥文件(最重要)
  2. ciphertext1.txt - 密文文件
  3. plaintext_history.json - 解密历史

Q4: 程序运行出错怎么办?

  1. 检查Python环境和依赖包是否正确安装
  2. 确认CustomTkinter版本兼容性
  3. 查看错误提示信息,通常会指出具体问题
  4. 删除损坏的配置文件,程序会自动重新创建

使用建议

最佳实践

  1. 定期备份密钥 :将aes_keys.json文件备份到安全位置
  2. 使用强密钥:导入自定义密钥时确保随机性和复杂度
  3. 验证结果:加密后立即测试解密确保数据完整性
  4. 安全存储:将密钥文件存储在加密的存储设备中

性能优化

  1. 批量操作:对于大量数据,建议分批处理
  2. 内存管理:处理大文件时注意内存使用情况
  3. 文件清理:定期清理不需要的历史记录文件

注意:本系统仅用于合法的数据保护目的,请遵守相关法律法规,不得用于非法用途。加密的身份证信息应当妥善保管,防止泄露。

源码:

复制代码
import datetime
import json
import os
import base64
import hashlib
import tkinter as tk
from tkinter import messagebox, simpledialog, ttk
import customtkinter as ctk

# 明文保存文件路径 - 用于存储解密后的明文记录
PLAINTEXT_FILE = "plaintext_history.json"

# 设置CustomTkinter的外观模式和默认颜色主题
ctk.set_appearance_mode("System")  # 系统模式,自动适应明暗主题
ctk.set_default_color_theme("blue")  # 使用蓝色主题

# ====================== AES-256 完整实现 ======================
# ----------------------- 常量定义 -----------------------
# 加密字节代换表(S盒),AES标准定义的非线性替换表,用于加密时的字节替换
# 每个字节(0x00-0xFF)对应一个替换值,提供非线性变换特性
S_BOX = (
    0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
    0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
    0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
    0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
    0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
    0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
    0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
    0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
    0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
    0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
    0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
    0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
    0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
    0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
    0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
    0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16
)

# 解密字节代换表(逆S盒),用于解密时的字节逆替换
# 与S_BOX互为逆操作,确保sbox[inv_sbox[x]] = x
INV_S_BOX = (
    0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
    0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
    0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
    0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,
    0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,
    0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,
    0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,
    0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,
    0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,
    0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,
    0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,
    0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,
    0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,
    0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
    0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
    0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D
)

# 轮常量表(Round Constants),用于密钥扩展算法中生成轮密钥
# 每个元素对应GF(2⁸)上的x^(i-1)值,用于消除密钥的对称性
RCON = (0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36)

# 加密列混淆左乘矩阵(标准AES定义)
# 用于加密时对每列进行矩阵乘法,实现扩散效果
MIX_MATRIX = [
    [0x02, 0x03, 0x01, 0x01],
    [0x01, 0x02, 0x03, 0x01],
    [0x01, 0x01, 0x02, 0x03],
    [0x03, 0x01, 0x01, 0x02]
]
# 解密列混淆左乘矩阵(标准AES逆矩阵)
# 用于解密时恢复原始列数据
INV_MIX_MATRIX = [
    [0x0E, 0x0B, 0x0D, 0x09],
    [0x09, 0x0E, 0x0B, 0x0D],
    [0x0D, 0x09, 0x0E, 0x0B],
    [0x0B, 0x0D, 0x09, 0x0E]
]


# ----------------------- 核心算法类 -----------------------
class AES256:
    def __init__(self, key):
        # 初始化时生成轮密钥,key为32字节(256位)原始密钥
        self.round_keys = self.key_expansion(key)

    @staticmethod
    def sub_bytes(s):
        """加密字节替换:使用S盒替换状态矩阵中的每个字节"""
        for i in range(4):  # 遍历4行
            for j in range(4):  # 遍历4列
                s[i][j] = S_BOX[s[i][j]]  # 查表替换

    @staticmethod
    def inv_sub_bytes(s):
        """解密字节逆替换:使用逆S盒恢复原始字节"""
        for i in range(4):
            for j in range(4):
                s[i][j] = INV_S_BOX[s[i][j]]

    @staticmethod
    def shift_rows(s):
        """加密行移位:对每行进行循环左移操作"""
        # 第0行不移位
        s[1] = s[1][1:] + s[1][:1]  # 第1行左移1字节
        s[2] = s[2][2:] + s[2][:2]  # 第2行左移2字节
        s[3] = s[3][3:] + s[3][:3]  # 第3行左移3字节

    @staticmethod
    def inv_shift_rows(s):
        """解密行逆移位:恢复原始行位置"""
        # 第0行不变
        s[1] = s[1][3:] + s[1][:3]  # 第1行右移1字节(等同左移3字节)
        s[2] = s[2][2:] + s[2][:2]  # 第2行右移2字节(等同左移2字节)
        s[3] = s[3][1:] + s[3][:1]  # 第3行右移3字节(等同左移1字节)

    def mix_columns(self, s):
        """加密列混淆:对每列进行矩阵乘法"""
        for i in range(4):  # 处理每一列
            col = [s[0][i], s[1][i], s[2][i], s[3][i]]  # 提取列数据
            new_col = [0] * 4  # 初始化新列
            # 矩阵乘法(4x4矩阵 * 4x1列向量)
            for row in range(4):
                for elem in range(4):
                    # 累加伽罗瓦域乘法结果
                    new_col[row] ^= self.ffmul(MIX_MATRIX[row][elem], col[elem])
            # 更新状态矩阵
            s[0][i] = new_col[0]
            s[1][i] = new_col[1]
            s[2][i] = new_col[2]
            s[3][i] = new_col[3]

            # 下面为手动展开计算可以消除循环判断的开销。
        # for i in range(4):
        #     s0 = s[0][i]
        #     s1 = s[1][i]
        #     s2 = s[2][i]
        #     s3 = s[3][i]
        #     s[0][i] = self.ffmul(0x02, s0) ^ self.ffmul(0x03, s1) ^ s2 ^ s3
        #     s[1][i] = s0 ^ self.ffmul(0x02, s1) ^ self.ffmul(0x03, s2) ^ s3
        #     s[2][i] = s0 ^ s1 ^ self.ffmul(0x02, s2) ^ self.ffmul(0x03, s3)
        #     s[3][i] = self.ffmul(0x03, s0) ^ s1 ^ s2 ^ self.ffmul(0x02, s3)

    def inv_mix_columns(self, s):
        """解密列混淆:使用逆矩阵恢复原始列数据"""
        for i in range(4):  # 处理每一列
            col = [s[0][i], s[1][i], s[2][i], s[3][i]]
            new_col = [0] * 4
            for row in range(4):
                for elem in range(4):
                    new_col[row] ^= self.ffmul(INV_MIX_MATRIX[row][elem], col[elem])
            # 更新状态矩阵
            s[0][i] = new_col[0]
            s[1][i] = new_col[1]
            s[2][i] = new_col[2]
            s[3][i] = new_col[3]
        # for i in range(4):
        #     s0 = s[0][i]
        #     s1 = s[1][i]
        #     s2 = s[2][i]
        #     s3 = s[3][i]
        #     s[0][i] = self.ffmul(0x0e, s0) ^ self.ffmul(0x0b, s1) ^ self.ffmul(0x0d, s2) ^ self.ffmul(0x09, s3)
        #     s[1][i] = self.ffmul(0x09, s0) ^ self.ffmul(0x0e, s1) ^ self.ffmul(0x0b, s2) ^ self.ffmul(0x0d, s3)
        #     s[2][i] = self.ffmul(0x0d, s0) ^ self.ffmul(0x09, s1) ^ self.ffmul(0x0e, s2) ^ self.ffmul(0x0b, s3)
        #     s[3][i] = self.ffmul(0x0b, s0) ^ self.ffmul(0x0d, s1) ^ self.ffmul(0x09, s2) ^ self.ffmul(0x0e, s3)

    @staticmethod
    def ffmul(a, b):
        """伽罗瓦域乘法(GF(2⁸)):计算a和b在有限域上的乘积"""
        p = 0
        for _ in range(8):  # 遍历b的每一位
            if b & 1:  # 如果最低位为1
                p ^= a  # 异或累加
            a <<= 1  # 左移相当于乘以x
            if a & 0x100:  # 处理模溢出
                a ^= 0x11b  # 异或不可约多项式x⁸ + x⁴ + x³ + x + 1(0x11b)
            b >>= 1  # 处理下一位
        return p

    def key_expansion(self, key):
        """密钥扩展:将32字节密钥扩展为15个轮密钥(每个16字节)"""
        key_bytes = list(key)  # 将密钥转换为字节列表
        nk = 8  # AES-256的初始密钥字数(256位=8字)
        nr = 14  # AES-256的总轮数

        # 初始密钥复制
        expanded_key = key_bytes.copy()

        # 扩展循环(生成后续轮密钥)
        for i in range(nk, 4 * (nr + 1)):
            temp = expanded_key[4 * i - 4: 4 * i]  # 获取前4字节

            # 每8个字的特殊处理(AES-256特有)
            if i % nk == 0:
                # RotWord:循环左移1字节
                temp = temp[1:] + temp[:1]
                # SubWord:S盒替换
                temp = [S_BOX[b] for b in temp]
                # 异或轮常数
                temp[0] ^= RCON[i // nk - 1]
            elif nk == 8 and i % nk == 4:
                # 中间轮的特殊处理(每8个字进行一次S盒替换)
                temp = [S_BOX[b] for b in temp]

            # 生成新字:与前nk个字异或
            new_word = [
                expanded_key[4 * (i - nk)] ^ temp[0],
                expanded_key[4 * (i - nk) + 1] ^ temp[1],
                expanded_key[4 * (i - nk) + 2] ^ temp[2],
                expanded_key[4 * (i - nk) + 3] ^ temp[3]
            ]
            expanded_key.extend(new_word)  # 添加到扩展密钥

        # 分割为15个轮密钥(每个16字节)
        return [expanded_key[i * 16: (i + 1) * 16] for i in range(nr + 1)]

    def encrypt_block(self, plaintext):
        """加密单个数据块(16字节)"""
        # 将输入字节转换为4x4状态矩阵
        state = [list(plaintext[i:i + 4]) for i in range(0, 16, 4)]
        # 初始轮密钥加
        self.add_round_key(state, self.round_keys[0])

        # 主轮次(共14轮)
        for i in range(1, 14):
            self.sub_bytes(state)  # 字节替换
            self.shift_rows(state)  # 行移位
            self.mix_columns(state)  # 列混淆
            self.add_round_key(state, self.round_keys[i])  # 轮密钥加

        # 最终轮(无列混淆)
        self.sub_bytes(state)
        self.shift_rows(state)
        self.add_round_key(state, self.round_keys[14])

        # 将状态矩阵转换回字节序列
        return bytes(sum(state, []))

    def decrypt_block(self, ciphertext):
        """解密单个数据块(16字节)"""
        state = [list(ciphertext[i:i + 4]) for i in range(0, 16, 4)]
        # 初始轮密钥加(使用最后一个轮密钥)
        self.add_round_key(state, self.round_keys[14])  # 轮密钥加
        self.inv_shift_rows(state)  # 逆行移位
        self.inv_sub_bytes(state)  # 逆字节替换

        # 主轮次(逆序处理)
        for i in range(13, 0, -1):
            self.add_round_key(state, self.round_keys[i])  # 轮密钥加
            self.inv_mix_columns(state)  # 逆列混淆
            self.inv_shift_rows(state)  # 逆行移位
            self.inv_sub_bytes(state)  # 逆字节替换

        # 最终轮密钥加
        self.add_round_key(state, self.round_keys[0])
        return bytes(sum(state, []))

    @staticmethod
    def add_round_key(s, k):
        """轮密钥加:将轮密钥与状态矩阵异或"""
        for i in range(4):
            for j in range(4):
                # 按列顺序访问轮密钥
                s[i][j] ^= k[i + j * 4]


# ----------------------- CBC 模式封装 -----------------------
class AES256_CBC:
    def __init__(self, key):
        # 初始化AES-256核心算法实例
        self.aes = AES256(key)  # 传入密钥初始化加密器

    @staticmethod
    def pkcs7_pad(data):
        """PKCS7填充:将数据填充至16字节的倍数"""
        pad_len = 16 - (len(data) % 16)  # 计算需要填充的字节数
        return data + bytes([pad_len] * pad_len)  # 填充重复的字节值为pad_len

    @staticmethod
    def pkcs7_unpad(data):
        """PKCS7去除填充:验证并去除填充数据"""
        pad_len = data[-1]  # 最后一个字节为填充长度
        return data[:-pad_len]  # 去除填充部分

    def encrypt(self, plaintext, iv):
        """CBC模式加密:处理整个明文并生成密文"""
        padded = self.pkcs7_pad(plaintext)  # 填充数据
        # 将数据分割为16字节的块
        blocks = [padded[i:i + 16] for i in range(0, len(padded), 16)]
        cipher_blocks = []  # 存储加密后的块
        prev = iv  # 初始向量作为首个块的异或值
        for block in blocks:
            # CBC模式核心:与前一个密文块(或IV)异或后再加密
            xored = bytes([b1 ^ b2 for b1, b2 in zip(prev, block)])
            encrypted = self.aes.encrypt_block(xored)
            cipher_blocks.append(encrypted)
            prev = encrypted  # 更新前一个块为当前密文
        return b''.join(cipher_blocks)  # 拼接所有密文块

    def decrypt(self, ciphertext, iv):
        """CBC模式解密:处理密文并恢复原始明文"""
        # 分割密文为16字节块
        blocks = [ciphertext[i:i + 16] for i in range(0, len(ciphertext), 16)]
        plain_blocks = []
        prev = iv  # 初始向量用于首个块
        for block in blocks:
            # 解密当前块
            decrypted = self.aes.decrypt_block(block)
            # 与前一个密文块异或得到原始明文块
            plain = bytes([b1 ^ b2 for b1, b2 in zip(prev, decrypted)])
            plain_blocks.append(plain)
            prev = block  # 更新前一个块为当前密文(注意是原始密文,非解密结果)
        # 拼接并去除填充
        return self.pkcs7_unpad(b''.join(plain_blocks))


# ====================== 应用程序接口 ======================
KEY_FILE = "aes_keys.json"  # JSON格式存储多密钥
CIPHER_FILE = "ciphertext1.txt"  # 密文存储文件名


def generate_aes_key():
    """生成并保存随机AES-256密钥到JSON文件"""
    import datetime
    key = os.urandom(32)
    print(f'当前使用的初始密钥为:{key.hex()}')
    timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    key_hex = key.hex()
    fingerprint = hashlib.sha256(key).digest()[:4].hex()

    # 读取现有密钥或初始化空列表
    try:
        with open(KEY_FILE, "r") as f:
            keys = json.load(f)
    except (FileNotFoundError, json.JSONDecodeError):
        keys = []

    # 添加新密钥条目
    keys.append({
        "timestamp": timestamp,
        "key": key_hex,
        "fingerprint": fingerprint
    })

    # 保存到文件
    with open(KEY_FILE, "w") as f:
        json.dump(keys, f, indent=2)

    return key


def import_custom_key(key_str):
    """导入自定义密钥到JSON文件"""
    # 验证输入格式:必须是64位十六进制字符(32字节)
    if len(key_str) != 64 or not all(c in "0123456789abcdef" for c in key_str):
        raise ValueError("密钥必须是64位十六进制字符")  # 64位十六进制也就是32字节

    # 将十六进制字符串转换为字节对象(32字节)
    key = bytes.fromhex(key_str)

    # 生成时间戳(用于记录密钥创建时间)
    timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    # 生成密钥指纹(SHA256哈希前4字节的十六进制)
    fingerprint = hashlib.sha256(key).digest()[:4].hex()

    # 读取现有密钥文件
    try:
        with open(KEY_FILE, "r") as f:
            keys = json.load(f)  # 尝试加载JSON格式的密钥列表
    except (FileNotFoundError, json.JSONDecodeError):
        keys = []  # 文件不存在或格式错误时初始化空列表

    # 检查密钥是否已存在(通过十六进制字符串比对)
    if any(entry["key"] == key_str for entry in keys):
        raise ValueError("该密钥已存在")

    # 添加新密钥条目到列表
    keys.append({
        "timestamp": timestamp,  # 记录创建时间
        "key": key_str,  # 原始十六进制密钥字符串
        "fingerprint": fingerprint  # 指纹用于快速识别
    })

    print(f'当前使用的初始密钥为:{key_str}')
    # 写回更新后的密钥列表到文件
    with open(KEY_FILE, "w") as f:
        json.dump(keys, f, indent=2)  # 带缩进的JSON格式便于阅读

    return key  # 返回字节类型的密钥对象供后续使用


def load_aes_key():
    """加载最新密钥,没有则自动生成
    返回:
        bytes: 最新密钥的字节形式(32字节)
    处理逻辑:
        1. 检查密钥文件是否存在,不存在则生成新密钥
        2. 读取并解析JSON格式的密钥文件
        3. 异常处理:文件损坏时删除并生成新密钥
        4. 按时间戳降序排列,返回最新密钥
    """
    if not os.path.exists(KEY_FILE):
        return generate_aes_key()  # 无密钥文件时生成新密钥

    try:
        with open(KEY_FILE, "r") as f:
            keys = json.load(f)  # 加载JSON格式的密钥列表

        if not keys:  # 空文件检查
            return generate_aes_key()

        # 按时间戳降序排序(最新密钥在前)
        keys_sorted = sorted(keys, key=lambda x: x["timestamp"], reverse=True)
        return bytes.fromhex(keys_sorted[0]["key"])  # 转换最新密钥为字节
    except Exception as e:  # 捕获JSON解析错误等异常
        messagebox.showwarning("错误", f"密钥文件损坏: {str(e)}")
        os.remove(KEY_FILE)  # 删除损坏文件
        return generate_aes_key()  # 生成替代密钥


def get_all_keys():
    """获取所有密钥信息
    返回:
        list: 包含所有密钥字典的列表,每个字典包含:
            - timestamp: 生成时间字符串
            - key: 64位十六进制密钥字符串
            - fingerprint: 4字节指纹的十六进制
    异常处理:
        - 文件不存在返回空列表
        - JSON解析失败视为空列表
    """
    try:
        with open(KEY_FILE, "r") as f:
            return json.load(f)  # 直接返回解析后的密钥列表
    except (FileNotFoundError, json.JSONDecodeError):  # 处理文件异常
        return []  # 返回空列表保持程序健壮性


def get_key_fingerprint(key):
    """生成密钥指纹:SHA256哈希的前4字节
    参数:
        key (bytes): 原始密钥字节
    返回:
        bytes: 4字节指纹(用于密文组合)
    说明:
        - print用于调试显示当前指纹
        - 前4字节在保证唯一性的前提下缩短数据长度
    """
    print(f'当前指纹为:{hashlib.sha256(key).digest()[:4].hex()}')  # 调试输出
    return hashlib.sha256(key).digest()[:4]  # 取哈希值前4字节作为指纹


def encrypt_id_number(plaintext, key):
    """加密身份证号:生成IV并返回Base64编码的完整密文"""
    iv = os.urandom(16)  # 生成16字节随机初始向量,每块为16字节,所以生成16字节的iv,用于与第一块异或
    print(f'本次加密的初始化向量为:{iv.hex()}')
    cipher = AES256_CBC(key)
    ciphertext = cipher.encrypt(plaintext.encode(), iv)  # 执行加密
    # 组合IV、密钥指纹和密文
    combined = iv + get_key_fingerprint(key) + ciphertext
    print(f'未进行base64编码前的组合:{combined.hex()}')
    return base64.b64encode(combined).decode()  # Base64编码便于存储


def decrypt_id_number(ciphertext_b64):
    """解密Base64编码的密文:验证指纹并返回明文"""
    combined = base64.b64decode(ciphertext_b64)  # 解码
    # 拆分各部分:前16字节IV,接下来4字节指纹,剩余为密文
    iv, fingerprint, ciphertext = combined[:16], combined[16:20], combined[20:]

    keys = get_all_keys()
    for entry in keys:
        if bytes.fromhex(entry["fingerprint"]) == fingerprint:  # 匹配指纹
            cipher = AES256_CBC(bytes.fromhex(entry["key"]))
            key = bytes.fromhex(entry["key"])  # 所用密钥
            return cipher.decrypt(ciphertext, iv).decode(), key  # 执行解密转换为字符串并返回结果,同时返回密钥

    raise ValueError("未找到匹配的密钥")


def decrypt_with_key(ciphertext_b64, key):
    """使用指定密钥解密Base64编码的密文:验证指纹并返回明文"""
    combined = base64.b64decode(ciphertext_b64)  # 解码
    # 拆分各部分:前16字节IV,接下来4字节指纹,剩余为密文
    iv, fingerprint, ciphertext = combined[:16], combined[16:20], combined[20:]
    # 验证密钥指纹匹配
    if fingerprint != get_key_fingerprint(key):
        raise ValueError("密钥与密文不匹配")
    cipher = AES256_CBC(key)
    plaintext = cipher.decrypt(ciphertext, iv)  # 执行解密
    return plaintext.decode()  # 转换为字符串


# ====================== GUI 界面部分 ======================
class MainPage(ctk.CTkFrame):
    '''主界面'''

    def __init__(self, master, show_encrypt, show_decrypt):
        super().__init__(master)

        # 创建标题标签
        title_label = ctk.CTkLabel(
            self,
            text="身份证号加解密系统",
            font=ctk.CTkFont(family="微软雅黑", size=24, weight="bold"),
            text_color=("gray10", "gray90")
        )
        title_label.pack(pady=30)

        # 创建按钮容器
        button_frame = ctk.CTkFrame(self, fg_color="transparent")
        button_frame.pack(pady=20)

        # 加密按钮
        encrypt_btn = ctk.CTkButton(
            button_frame,
            text="🔒 加密身份证",
            command=show_encrypt,
            width=200,
            height=40,
            font=ctk.CTkFont(size=14),
            fg_color=("blue", "blue"),
            hover_color=("darkblue", "darkblue"),
            corner_radius=10
        )
        encrypt_btn.pack(pady=10)

        # 解密按钮
        decrypt_btn = ctk.CTkButton(
            button_frame,
            text="🔓 解密身份证",
            command=show_decrypt,
            width=200,
            height=40,
            font=ctk.CTkFont(size=14),
            fg_color=("green", "green"),
            hover_color=("darkgreen", "darkgreen"),
            corner_radius=10
        )
        decrypt_btn.pack(pady=10)


class KeyManagementMixin:
    """密钥管理功能混入类
    功能:
        - 提供密钥生成、导入的GUI控件
        - 管理密钥显示和状态更新
        - 集成到需要密钥管理功能的页面中
    特性:
        - 可复用组件,通过继承方式为其他界面添加密钥管理能力
        - 自动同步密钥状态到主应用实例
    """

    def create_key_controls(self, parent):
        """创建密钥管理界面组件
        参数:
            parent: 父容器控件
        构建:
            1. 创建包含两个操作按钮的水平工具栏(生成/导入)
            2. 创建密钥指纹显示标签
            3. 布局到父容器中
        """
        key_frame = ctk.CTkFrame(parent, fg_color=("gray90", "gray15"), corner_radius=10)

        # 密钥操作按钮容器(水平排列)
        btn_frame = ctk.CTkFrame(key_frame, fg_color="transparent")

        # 生成新密钥按钮
        ctk.CTkButton(
            btn_frame,
            text="生成新密钥",
            command=self.generate_new_key,
            width=120,
            height=35,
            font=ctk.CTkFont(size=14),
            fg_color=("blue", "blue"),
            hover_color=("darkblue", "darkblue"),
            corner_radius=8
        ).pack(side="left", padx=5)

        # 导入密钥按钮
        ctk.CTkButton(
            btn_frame,
            text="导入密钥",
            command=self.import_key_dialog,
            width=120,
            height=35,
            font=ctk.CTkFont(size=14),
            fg_color=("green", "green"),
            hover_color=("darkgreen", "darkgreen"),
            corner_radius=8
        ).pack(side="left", padx=5)

        btn_frame.pack(pady=10)

        # 密钥指纹显示标签
        self.fingerprint_label = ctk.CTkLabel(
            key_frame,
            text="",
            font=ctk.CTkFont(size=14),
            anchor="w"
        )
        self.fingerprint_label.pack(anchor="w", padx=10, pady=5)

        # 将密钥管理面板打包到父容器
        key_frame.pack(fill="x", padx=10, pady=10)

    def generate_new_key(self):
        """执行密钥生成流程
        流程:
            1. 调用generate_aes_key生成新密钥(同时写入文件)
            2. 更新主应用实例的当前密钥
            3. 刷新界面显示
            4. 弹出操作成功提示
        """
        new_key = generate_aes_key()  # 生成并持久化新密钥
        self.master.key = new_key  # 更新主应用的密钥引用
        self.update_key_display(new_key)  # 刷新指纹显示
        messagebox.showinfo("成功", "新密钥已生成并保存!")  # 用户反馈

    def import_key_dialog(self):
        """处理密钥导入操作
        流程:
            1. 弹出输入对话框获取密钥字符串
            2. 输入为空时直接返回
            3. 调用import_custom_key验证并存储密钥
            4. 成功更新主应用密钥,失败显示错误详情
        """
        key_str = simpledialog.askstring("导入密钥", "请输入64位十六进制密钥:")  # 64位十六进制也就是32字节
        if not key_str: return  # 用户取消输入

        try:
            # 转换输入为小写并验证格式
            key = import_custom_key(key_str.lower())  # 可能抛出ValueError
            self.master.key = key  # 更新主应用密钥
            self.update_key_display(key)  # 刷新显示
            messagebox.showinfo("成功", "密钥导入成功!")
        except Exception as e:
            # 显示具体错误信息(如格式错误、已存在等)
            messagebox.showerror("错误", f"密钥导入失败:{str(e)}")

    def update_key_display(self, key):
        """更新密钥指纹显示
        参数:
            key: bytes类型密钥(32字节)
        操作:
            1. 计算密钥的SHA256指纹(取前4字节)
            2. 转换为十六进制字符串
            3. 更新标签文本
        """
        fingerprint = get_key_fingerprint(key).hex()  # 获取4字节指纹的hex字符串
        self.fingerprint_label.config(text=f"当前密钥指纹:{fingerprint}")  # 更新显示


class EncryptPage(KeyManagementMixin, ctk.CTkFrame):
    """加密功能主界面,继承密钥管理功能和Frame容器特性"""

    def __init__(self, master, key, go_back):
        # 初始化父类框架和混入类
        super().__init__(master, fg_color="transparent")
        # 保持对主应用窗口的引用
        self.master = master
        # 存储当前使用的密钥(bytes类型)
        self.key = key
        # 构建界面元素
        self.build_ui(go_back)
        # 初始化显示当前密钥信息
        self.update_key_display(key)

    def build_ui(self, go_back):
        """构建界面布局"""
        # 返回按钮(右上角定位)
        ctk.CTkButton(
            self,
            text="← 返回",
            command=go_back,
            width=100,
            height=30,
            corner_radius=8,
            fg_color=("gray60", "gray30"),
            hover_color=("gray50", "gray40")
        ).pack(anchor="ne", padx=10)

        # 创建密钥管理控件(来自KeyManagementMixin)
        self.create_key_controls(self)

        # 输入区域容器
        input_frame = ctk.CTkFrame(self, fg_color="transparent")

        # 身份证号标签
        ctk.CTkLabel(
            input_frame,
            text="身份证号码(18位):",
            font=ctk.CTkFont(size=14)
        ).pack(anchor="w")

        # 带实时验证的输入框
        self.entry = ctk.CTkEntry(
            input_frame,
            width=300,
            height=35,
            placeholder_text="请输入18位身份证号",
            font=ctk.CTkFont(size=14),
            corner_radius=8
        )
        self.entry.pack(pady=5)

        # 加密按钮
        ctk.CTkButton(
            input_frame,
            text="加密",
            command=self.encrypt,
            width=120,
            height=35,
            font=ctk.CTkFont(size=14),
            fg_color=("blue", "blue"),
            hover_color=("darkblue", "darkblue"),
            corner_radius=8
        ).pack(pady=10)

        input_frame.pack(pady=10)

        # 输出区域
        self.output = ctk.CTkTextbox(
            self,
            height=100,
            width=600,
            font=ctk.CTkFont(size=13),
            corner_radius=8,
            border_width=1
        )
        self.output.pack(pady=10)
        self.output.configure(state="disabled")

        # 创建密钥历史面板
        self.create_key_history()

    def create_key_history(self):
        """初始化密钥历史记录面板"""
        # 创建带标题的容器框架
        self.history_frame = ctk.CTkFrame(
            self,
            fg_color=("gray90", "gray15"),
            corner_radius=10
        )

        # 添加标题标签
        ctk.CTkLabel(
            self.history_frame,
            text="密钥历史(双击选择)",
            font=ctk.CTkFont(size=16, weight="bold")
        ).pack(pady=(10, 5), padx=10, anchor="w")

        # 由于CustomTkinter没有直接对应的Treeview组件,我们使用原生tkinter的Treeview
        # 但是调整其样式以匹配CustomTkinter的外观
        self.tree = tk.ttk.Treeview(
            self.history_frame,
            columns=("timestamp", "fingerprint", "key_preview"),
            show="headings",
            selectmode="browse",
            height=6
        )

        # 列配置
        self.tree.column("timestamp", width=180, anchor="w")
        self.tree.column("fingerprint", width=100, anchor="w")
        self.tree.column("key_preview", width=120, anchor="w")

        # 表头文本设置
        self.tree.heading("timestamp", text="生成时间")
        self.tree.heading("fingerprint", text="指纹")
        self.tree.heading("key_preview", text="密钥预览")

        # 创建垂直滚动条
        scrollbar = tk.ttk.Scrollbar(
            self.history_frame,
            orient="vertical",
            command=self.tree.yview
        )
        self.tree.configure(yscrollcommand=scrollbar.set)

        # 布局表格和滚动条
        self.tree.pack(side="left", fill="both", expand=True, padx=10, pady=10)
        scrollbar.pack(side="right", fill="y", pady=10)
        self.history_frame.pack(fill="both", expand=True, padx=10, pady=10)

        # 绑定鼠标双击事件
        self.tree.bind("<Double-1>", self.on_key_selected)

        # 初始加载历史数据
        self.update_key_history()

    def on_key_selected(self, event):
        """处理密钥选择事件"""
        # 获取选中的第一行(唯一选择)
        item = self.tree.selection()[0]
        # 从条目tag属性获取完整HEX密钥
        key_hex = self.tree.item(item, "tags")[0]

        try:
            # 转换HEX字符串为字节对象
            self.master.key = bytes.fromhex(key_hex)
            # 更新当前页面显示
            self.update_key_display(self.master.key)

            # 设置选中状态(蓝色高亮)
            self.tree.selection_set(item)
            # 聚焦到选中行(灰色背景)
            self.tree.focus(item)
            # 操作成功提示
            messagebox.showinfo("成功", "已切换加密密钥!")
        except Exception as e:
            # 捕获转换失败等异常
            messagebox.showerror("错误", f"密钥加载失败: {str(e)}")

    def update_key_display(self, key):
        """更新密钥信息显示"""
        # 生成4字节指纹(HEX字符串)
        fingerprint = hashlib.sha256(key).digest()[:4].hex()
        # 截取前8位字符+省略号
        key_preview = key.hex()[:8] + "..."
        # 更新标签显示
        self.fingerprint_label.configure(
            text=f"当前加密密钥:\n指纹: {fingerprint}\n密钥预览: {key_preview}"
        )

    def update_key_history(self):
        """刷新密钥历史记录"""
        # 清空现有条目
        for item in self.tree.get_children():
            self.tree.delete(item)

        # 获取所有密钥并按时间倒序排列
        keys = sorted(get_all_keys(), key=lambda x: x["timestamp"], reverse=True)

        # 遍历插入新数据
        for entry in keys:
            # 生成密钥预览(前8位+省略号)
            key_preview = f"{entry['key'][:8]}..."
            # 插入新行,tag存储完整HEX密钥
            self.tree.insert(
                "",  # 根节点
                "end",  # 插入位置
                values=(  # 三列数据
                    entry["timestamp"],
                    entry["fingerprint"],
                    key_preview
                ),
                tags=(entry["key"],)  # 隐藏数据存储
            )

    def generate_new_key(self):
        """生成新密钥(覆盖父类方法)"""
        super().generate_new_key()  # 调用父类生成逻辑
        app = self.winfo_toplevel()  # 获取Application实例
        app.load_key()  # 重新加载最新密钥
        self.key = app.key  # 同步当前页面引用
        self.update_key_history()  # 刷新历史记录
        self.update_key_display(app.key)  # 更新显示

    def import_key_dialog(self):
        """导入密钥(覆盖父类方法)"""
        super().import_key_dialog()  # 调用父类导入逻辑
        app = self.winfo_toplevel()  # 获取Application实例
        app.load_key()  # 重新加载密钥
        self.update_key_history()  # 刷新历史面板
        self.update_key_display(app.key)  # 更新显示

    def validate_input(self, text):
        """实时验证身份证输入
        参数:
            text: 当前输入框内容
        返回:
            bool: 是否有效
        """
        # 长度超过18无效
        # if len(text) > 18:
        #     return False
        # 空输入允许(中间输入状态)
        # if len(text) == 0:
        #     return True
        # 前17位必须为数字
        # if not text[:17].isdigit():
        #     return False
        # 第18位校验(数字或X)
        # if len(text) == 18 and not (text[17].isdigit() or text[17].lower() == 'x'):
        #     return False
        return True

    def encrypt(self):
        """执行加密操作"""
        # 获取输入并转为大写
        id_num = self.entry.get().upper()
        # 最终长度验证
        if len(id_num) != 18:
            messagebox.showerror("错误", "请输入有效的18位身份证号码")
            return
        if not id_num[:17].isdigit():
            messagebox.showerror("错误", "前17位必须为数字")
            return
        if not (id_num[17].isdigit() or id_num[17] in ['X', 'x']):
            messagebox.showerror("错误", "第18位必须为数字或X")
            return

        try:
            # 获取Application实例(替代直接使用self.master,解决CTkFrame没有key属性的问题)
            app = self.winfo_toplevel()
            # 使用Application实例中的密钥进行加密
            ciphertext = encrypt_id_number(id_num, app.key)
            # 追加写入密文文件,每个密文占一行
            with open(CIPHER_FILE, "a") as f:
                f.write(ciphertext + "\n")
            # 显示操作结果,包括生成的密文
            self.show_output(f"加密成功!密文已保存\n{ciphertext}")
        except Exception as e:
            # 捕获并显示加密过程中的任何错误
            messagebox.showerror("错误", str(e))

    def show_output(self, text):
        """更新输出区域内容"""
        self.output.configure(state="normal")
        self.output.delete("1.0", "end")
        self.output.insert("end", text)
        self.output.configure(state="disabled")


class DecryptPage(ctk.CTkFrame):
    """解密功能主界面
    功能:
        - 显示密钥存储路径
        - 加载并选择加密文件
        - 支持默认密钥和自定义密钥解密
        - 显示解密结果
        - 保存解密记录到JSON文件
    """

    PLAINTEXT_FILE = "plaintext_history.json"  # 明文保存文件路径

    def check_ciphertext_exists(self, ciphertext):
        """检查密文是否已存在于历史记录中
        参数:
            ciphertext: 要检查的密文
        返回:
            bool: 如果存在返回True,否则返回False
        """
        try:
            # 检查明文历史记录文件是否存在
            if not os.path.exists(self.PLAINTEXT_FILE):
                return False

            # 读取历史记录文件内容
            with open(self.PLAINTEXT_FILE, "r") as f:
                history = json.load(f)

            # 遍历历史记录,检查密文是否存在
            for record in history:
                if record.get("ciphertext") == ciphertext:
                    return True

            # 未找到匹配的密文
            return False
        except Exception:
            # 发生任何错误时返回False,确保程序继续运行
            return False

    def save_plaintext_record(self, plaintext, key_fingerprint, key_type, ciphertext):
        """保存明文记录到JSON文件
        参数:
            plaintext: 解密后的明文
            key_fingerprint: 使用的密钥指纹
            key_type: 密钥类型("default"或"custom")
            ciphertext: 原始密文
        """
        try:
            # 获取当前时间并格式化为指定格式
            current_time = datetime.datetime.now()
            # 将时间格式化为 "YYYY-MM-DD HH:MM:SS" 格式
            formatted_time = current_time.strftime("%Y-%m-%d %H:%M:%S")

            # 创建包含所有必要信息的记录字典
            record = {
                "timestamp": formatted_time,  # 格式化的时间戳
                "plaintext": plaintext,  # 解密后的明文
                "ciphertext": ciphertext,  # 原始密文
                "key_fingerprint": key_fingerprint,  # 使用的密钥指纹
                "key_type": key_type  # 密钥类型(默认或自定义)
            }

            # 读取现有历史记录或创建新的记录列表
            if os.path.exists(self.PLAINTEXT_FILE):
                # 如果文件存在,读取现有记录
                with open(self.PLAINTEXT_FILE, "r") as f:
                    history = json.load(f)
            else:
                # 如果文件不存在,创建空列表
                history = []

            # 将新记录添加到历史记录列表中
            history.append(record)

            # 将更新后的历史记录写回文件,使用缩进格式化JSON
            with open(self.PLAINTEXT_FILE, "w") as f:
                json.dump(history, f, indent=2)

        except Exception as e:
            # 如果保存过程中出现错误,显示错误消息
            messagebox.showerror("错误", f"保存明文记录失败: {str(e)}")

    def __init__(self, master, key, go_back):
        """初始化解密页面
        参数:
            master: 父容器(应用主窗口)
            key: 初始密钥(bytes)
            go_back: 返回主界面的回调函数
        """
        super().__init__(master, fg_color="transparent")
        self.last_custom_key = None  # 缓存最后一次成功使用的自定义密钥

        # 显示密钥文件绝对路径
        ctk.CTkLabel(
            self,
            text=f"密钥文件路径: {os.path.abspath(KEY_FILE)}",
            font=ctk.CTkFont(size=12),
            anchor="w"
        ).pack(anchor="nw", padx=10, pady=5)

        self.cipher_list = []  # 存储从文件加载的原始密文数据
        self.key = key  # 当前使用的默认密钥
        self.fingerprint_label = None  # 指纹显示标签的引用占位
        self.build_ui(go_back)  # 构建界面元素
        self.update_fingerprint(self.key)  # 初始化显示默认密钥指纹

    def build_ui(self, go_back):
        """构建界面布局"""
        # 返回按钮(右上角定位)
        ctk.CTkButton(
            self,
            text="← 返回",
            command=go_back,
            width=100,
            height=30,
            corner_radius=8,
            fg_color=("gray60", "gray30"),
            hover_color=("gray50", "gray40")
        ).pack(anchor="ne", padx=10)

        # 密钥指纹显示标签
        self.fingerprint_label = ctk.CTkLabel(
            self,
            text="",
            font=ctk.CTkFont(size=14),
            anchor="w"
        )
        self.fingerprint_label.pack(anchor="nw", padx=10, pady=5)

        # 操作按钮容器
        input_frame = ctk.CTkFrame(self, fg_color="transparent")

        # 文件选择按钮
        ctk.CTkButton(
            input_frame,
            text="选择密文文件",
            command=self.load_file,
            width=120,
            height=35,
            font=ctk.CTkFont(size=14),
            fg_color=("blue", "blue"),
            hover_color=("darkblue", "darkblue"),
            corner_radius=8
        ).pack(side="left", padx=5)

        # 清空输入按钮
        ctk.CTkButton(
            input_frame,
            text="清空输入",
            command=self.clear_input,
            width=120,
            height=35,
            font=ctk.CTkFont(size=14),
            fg_color=("gray60", "gray30"),
            hover_color=("gray50", "gray40"),
            corner_radius=8
        ).pack(side="left", padx=5)

        input_frame.pack(pady=10)

        # 密文列表框
        list_frame = ctk.CTkFrame(self, fg_color=("gray90", "gray15"), corner_radius=10)
        list_frame.pack(pady=10, padx=20, fill="x")

        ctk.CTkLabel(
            list_frame,
            text="已保存的密文:",
            font=ctk.CTkFont(size=14)
        ).pack(anchor="w", padx=10, pady=5)

        # 使用传统Listbox,因为CustomTkinter没有直接对应的组件
        self.listbox = tk.Listbox(
            list_frame,
            height=6,
            width=70,
            bg=("gray95" if ctk.get_appearance_mode() == "light" else "gray20"),
            fg=("black" if ctk.get_appearance_mode() == "light" else "white"),
            font=("Segoe UI", 10),
            borderwidth=0,
            highlightthickness=1,
            highlightcolor=("gray70" if ctk.get_appearance_mode() == "light" else "gray40")
        )
        self.listbox.bind("<<ListboxSelect>>", self.select_cipher)
        self.listbox.pack(pady=5, padx=10, fill="x")

        # 密文输入/展示区域
        input_area_frame = ctk.CTkFrame(self, fg_color="transparent")
        input_area_frame.pack(pady=10, fill="x", padx=20)

        ctk.CTkLabel(
            input_area_frame,
            text="密文输入:",
            font=ctk.CTkFont(size=14)
        ).pack(anchor="w")

        self.input = ctk.CTkTextbox(
            input_area_frame,
            height=60,  # 进一步减小高度
            width=600,
            font=ctk.CTkFont(size=13),
            corner_radius=8,
            border_width=1
        )
        self.input.pack(pady=5, fill="x")

        # 操作按钮容器
        button_frame = ctk.CTkFrame(self, fg_color="transparent")
        button_frame.pack(pady=10)

        # 默认密钥解密按钮
        ctk.CTkButton(
            button_frame,
            text="使用默认密钥解密",
            command=self.decrypt,
            width=180,
            height=35,
            font=ctk.CTkFont(size=14),
            fg_color=("blue", "blue"),
            hover_color=("darkblue", "darkblue"),
            corner_radius=8
        ).pack(side="left", padx=10)

        # 自定义密钥解密按钮
        ctk.CTkButton(
            button_frame,
            text="使用自定义密钥解密",
            command=self.decrypt_with_custom_key,
            width=180,
            height=35,
            font=ctk.CTkFont(size=14),
            fg_color=("green", "green"),
            hover_color=("darkgreen", "darkgreen"),
            corner_radius=8
        ).pack(side="left", padx=10)

        # 结果输出区域
        output_frame = ctk.CTkFrame(self, fg_color="transparent")
        output_frame.pack(pady=10, fill="x", padx=20)

        ctk.CTkLabel(
            output_frame,
            text="解密结果:",
            font=ctk.CTkFont(size=14)
        ).pack(anchor="w")

        self.output = ctk.CTkTextbox(
            output_frame,
            height=170,
            width=600,
            font=ctk.CTkFont(size=13),
            corner_radius=8,
            border_width=1
        )
        self.output.pack(pady=5, fill="x")
        self.output.configure(state="disabled")

    def update_fingerprint(self, key):
        """更新当前密钥指纹显示"""
        fingerprint = get_key_fingerprint(key).hex()
        self.fingerprint_label.configure(text=f"当前密钥指纹: {fingerprint}")

    def decrypt_with_custom_key(self):
        """使用自定义密钥解密流程"""
        ciphertext = self.input.get("1.0", "end").strip()
        if not ciphertext:
            messagebox.showerror("错误", "请输入或选择要解密的密文")
            return

        if not self.last_custom_key:
            self.last_custom_key = self._get_valid_custom_key()
            if not self.last_custom_key:
                return

        # 检查密文是否已在历史记录中存在
        if self.check_ciphertext_exists(ciphertext):
            # 显示提示信息,告知用户密文已解密过
            messagebox.showinfo("提示", "当前密文已解密过,请查看历史记录")
            try:
                # 仍然解密以显示结果,但不保存记录
                plaintext = decrypt_with_key(ciphertext, self.last_custom_key)
                # 更新密钥指纹显示
                self.update_fingerprint(self.last_custom_key)
                # 显示解密结果
                self.show_output(f"解密成功!\n身份证号码: {plaintext}")
            except Exception as e:
                # 解密失败时清除无效的自定义密钥缓存
                self.last_custom_key = None
                # 恢复默认密钥显示
                self.update_fingerprint(self.key)
                # 显示错误信息
                self.show_output(f"解密失败: {str(e)}")
            # 提前返回,不保存记录(避免重复记录)
            return

        try:
            # 使用自定义密钥解密
            plaintext = decrypt_with_key(ciphertext, self.last_custom_key)
            # 获取密钥指纹的十六进制表示
            fingerprint = get_key_fingerprint(self.last_custom_key).hex()
            # 更新密钥指纹显示
            self.update_fingerprint(self.last_custom_key)
            # 显示解密结果
            self.show_output(f"解密成功!\n身份证号码: {plaintext}")
            # 保存解密记录到JSON文件
            self.save_plaintext_record(plaintext, fingerprint, "custom", ciphertext)
        except Exception as e:
            # 解密失败时清除无效的自定义密钥缓存
            self.last_custom_key = None
            # 恢复默认密钥显示
            self.update_fingerprint(self.key)
            # 显示错误信息
            self.show_output(f"解密失败: {str(e)}")

    def _get_valid_custom_key(self):
        """获取有效自定义密钥"""
        while True:
            key_str = simpledialog.askstring("自定义密钥", "请输入64位十六进制密钥:")
            if not key_str:
                return None

            try:
                if len(key_str) != 64 or not all(c in "0123456789abcdef" for c in key_str.lower()):
                    raise ValueError("密钥必须是64位十六进制字符")
                return bytes.fromhex(key_str.lower())
            except Exception as e:
                messagebox.showerror("输入错误", f"无效密钥: {str(e)}")
                continue

    def load_file(self):
        """加载密文文件到列表控件"""
        try:
            with open(CIPHER_FILE, "r") as f:
                self.cipher_list = [line.strip() for line in f if line.strip()]
            self.listbox.delete(0, "end")
            for c in self.cipher_list:
                self.listbox.insert("end", f"{c[:30]}...")
        except Exception as e:
            messagebox.showerror("错误", f"文件读取失败: {str(e)}")

    def select_cipher(self, event):
        """处理列表项选择事件"""
        idx = self.listbox.curselection()
        if not idx:
            return
        full_text = self.cipher_list[idx[0]]
        self.input.delete("1.0", "end")
        self.input.insert("1.0", full_text)

    def clear_input(self):
        """清空输入框内容"""
        self.input.delete("1.0", "end")

    def decrypt(self):
        """使用默认密钥的解密流程"""
        # 获取输入框中的密文并去除首尾空白
        ciphertext = self.input.get("1.0", "end").strip()
        # 检查密文是否为空
        if not ciphertext:
            messagebox.showerror("错误", "请输入或选择要解密的密文")
            return

        # 检查密文是否已在历史记录中存在
        if self.check_ciphertext_exists(ciphertext):
            # 显示提示信息,告知用户密文已解密过
            messagebox.showinfo("提示", "当前密文已解密过,请查看历史记录")
            try:
                # 仍然解密以显示结果,但不保存记录
                plaintext, used_key = decrypt_id_number(ciphertext)
                # 更新当前使用的密钥
                self.key = used_key
                # 更新密钥指纹显示
                self.update_fingerprint(self.key)
                # 显示解密结果
                self.show_output(f"解密成功!\n身份证号码: {plaintext}")
            except Exception as e:
                # 解密失败时更新显示并显示错误信息
                self.update_fingerprint(self.key)
                self.show_output(f"解密失败: {str(e)}")
            # 提前返回,不保存记录(避免重复记录)
            return

        try:
            # 使用默认密钥解密
            plaintext, used_key = decrypt_id_number(ciphertext)
            # 更新当前使用的密钥
            self.key = used_key
            # 获取密钥指纹的十六进制表示
            fingerprint = get_key_fingerprint(used_key).hex()
            # 更新密钥指纹显示
            self.update_fingerprint(self.key)
            # 显示解密结果
            self.show_output(f"解密成功!\n身份证号码: {plaintext}")
            # 保存解密记录到JSON文件
            self.save_plaintext_record(plaintext, fingerprint, "default", ciphertext)
        except Exception as e:
            # 解密失败时更新显示并显示错误信息
            self.update_fingerprint(self.key)
            self.show_output(f"解密失败: {str(e)}")

    def show_output(self, text):
        """更新结果输出区域"""
        self.output.configure(state="normal")
        self.output.delete("1.0", "end")
        self.output.insert("1.0", text)
        self.output.configure(state="disabled")


class Application(ctk.CTk):
    """主应用程序类,继承自CustomTkinter的CTk窗口"""

    def __init__(self):
        """初始化应用程序实例"""
        super().__init__()  # 调用父类CTk的构造函数

        # 配置窗口
        self.title("身份证号加解密系统 v3.0")
        self.geometry("800x650")

        # 设置主题
        ctk.set_appearance_mode("system")  # 系统主题(自动适应明暗)
        ctk.set_default_color_theme("blue")  # 使用蓝色主题

        # 配置窗口样式
        self.configure(fg_color=("gray95", "gray10"))  # 背景颜色(亮/暗模式)

        # 加载密钥
        self.key = load_aes_key()

        # 初始化界面
        self.init_ui()

    def init_ui(self):
        """初始化用户界面组件"""
        # 创建主容器
        self.container = ctk.CTkFrame(self, fg_color="transparent")
        self.container.pack(fill="both", expand=True, padx=20, pady=20)

        # 创建主页面实例
        self.main_page = MainPage(self.container, self.show_encrypt, self.show_decrypt)
        self.encrypt_page = None
        self.decrypt_page = None

        # 显示主页面
        self.show_main()

    def show_main(self):
        """显示主页面"""
        self._hide_pages()
        self.main_page.pack(fill="both", expand=True)

    def show_encrypt(self):
        """显示加密页面"""
        self._hide_pages()
        self.encrypt_page = EncryptPage(self.container, self.key, self.show_main)
        self.encrypt_page.pack(fill="both", expand=True)

    def show_decrypt(self):
        """显示解密页面"""
        self._hide_pages()
        self.decrypt_page = DecryptPage(self.container, self.key, self.show_main)
        self.decrypt_page.pack(fill="both", expand=True)

    def _hide_pages(self):
        """隐藏所有Frame类型的子组件"""
        for widget in self.container.winfo_children():
            widget.pack_forget()

    def load_key(self):
        """重新加载当前密钥"""
        self.key = load_aes_key()

    def generate_new_key(self):
        """生成全新密钥并更新状态"""
        generate_aes_key()
        self.load_key()


if __name__ == "__main__":
    """程序入口点"""
    app = Application()  # 创建应用程序实例
    app.mainloop()  # 启动Tkinter事件循环
相关推荐
魔都吴所谓2 小时前
【Python】根据开始时间、结束时间计算中间时间
开发语言·windows·python
大明者省2 小时前
pycharm解释器使用anaconda建立的虚拟环境里面的python,无需系统里面安装python。
linux·python·pycharm
一只鱼丸yo2 小时前
70B大模型也能在笔记本上跑?揭秘让AI“瘦身”的黑科技
人工智能·科技·机器学习·语言模型
言之。3 小时前
Django REST framework:SimpleRouter 使用指南
python·django·sqlite
极客智造3 小时前
OpenCV C++ 核心:Mat 与像素操作全解析
c++·人工智能·opencv
劳尔的狙击镜3 小时前
CT影像寻找皮肤轮廓预处理
python·opencv·findcontours·ct·皮肤轮廓·皮肤表皮建模·医学影像处理
极客智造3 小时前
OpenCV C++ 色彩空间详解:转换、应用与 LUT 技术
c++·人工智能·opencv
湫兮之风3 小时前
OpenCV: cv::warpAffine()逆仿射变换详解
人工智能·opencv·计算机视觉
非优秀程序员3 小时前
开发人员如何使用在自己的系统中对接 Nano Banana 【完整教程】
人工智能