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. 运行程序
  • python AES-256-CBC.py(代码命令)

详细操作指南

主界面功能

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

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

加密操作流程

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事件循环

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

長琹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值