系统概述
本系统是一个基于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
安装步骤
- 安装CustomTkinter:
- pip install customtkinter
- 运行程序:
- python AES-256-CBC.py(代码命令)
详细操作指南
主界面功能
启动程序后,您将看到简洁的主界面,包含两个主要功能按钮:
- 加密身份证:进入身份证号码加密功能
- 解密身份证:进入密文解密功能

加密操作流程
1. 进入加密界面
点击"加密身份证"按钮,进入加密功能界面。界面包含以下区域:
- 密钥管理区域:显示当前密钥指纹,提供密钥生成和导入功能
- 输入区域:身份证号码输入框
- 输出区域:显示加密结果
- 密钥历史区域:显示所有历史密钥,支持双击切换

2. 密钥管理
生成新密钥:
- 点击"生成新密钥"按钮
- 系统自动生成256位随机密钥
- 密钥自动保存到aes_keys.json文件
- 界面显示新密钥的指纹信息
导入自定义密钥:
- 点击"导入密钥"按钮
- 在弹出对话框中输入64位十六进制密钥(32字节)
- 系统验证密钥格式并检查是否重复
- 成功导入后更新当前使用的密钥
切换历史密钥:
- 在密钥历史表格中双击任意密钥记录
- 系统自动切换到选中的密钥
- 当前密钥指纹显示更新
3. 执行加密
- 输入身份证号码 :
- 在输入框中输入18位身份证号码
- 系统自动验证格式(前17位数字,第18位数字或X)
- 点击加密按钮 :
- 系统生成随机16字节初始向量(IV)
- 使用当前密钥执行AES-256-CBC加密
- 组合IV、密钥指纹和密文
- 进行Base64编码便于存储和传输
- 查看结果 :
- 加密成功后,密文显示在输出区域
- 密文自动保存到ciphertext1.txt文件
- 每个密文占用一行,便于批量管理
4. 加密数据格式
生成的密文采用以下格式:
16字节IV\] + \[4字节密钥指纹\] + \[实际密文数据
经过Base64编码后的最终格式便于存储和传输。
解密操作流程
1. 进入解密界面
点击"解密身份证"按钮,进入解密功能界面。界面包含:
- 密钥文件路径显示:显示密钥存储文件的绝对路径
- 当前密钥指纹显示:显示正在使用的密钥信息
- 文件操作区域:密文文件加载和输入清空功能
- 密文列表:显示已保存的所有密文
- 密文输入区域:手动输入或显示选中的密文
- 解密按钮区域:默认密钥和自定义密钥解密选项
- 结果输出区域:显示解密结果

2. 加载密文数据
从文件加载:
- 点击"选择密文文件"按钮
- 系统自动读取ciphertext1.txt文件
- 所有密文显示在列表中(显示前30个字符)
- 双击列表项可将完整密文加载到输入区域
手动输入:
- 直接在密文输入区域粘贴或输入Base64编码的密文
- 支持多行密文输入
3. 执行解密
使用默认密钥解密:
- 确保密文已输入到输入区域
- 点击"使用默认密钥解密"按钮
- 系统自动:
- 解析密文中的密钥指纹
- 在密钥库中查找匹配的密钥
- 提取IV和实际密文数据
- 执行AES-256-CBC解密
- 显示解密后的身份证号码
使用自定义密钥解密:
- 点击"使用自定义密钥解密"按钮
- 在弹出对话框中输入64位十六进制密钥
- 系统验证密钥与密文指纹是否匹配
- 匹配成功则执行解密并显示结果
4. 解密结果处理
- 成功解密:在结果区域显示"解密成功!身份证号码: [号码]"
- 自动记录 :解密记录保存到plaintext_history.json文件
- 重复检测:系统检测是否已解密过相同密文,避免重复记录
- 错误处理:密钥不匹配或数据损坏时显示具体错误信息
文件系统说明
核心文件
- AES-256-CBC.py:主程序文件,包含所有功能实现
- aes_keys.json:密钥存储文件,JSON格式
- ciphertext1.txt:加密后的密文存储文件
- 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" }
安全特性
加密安全性
- AES-256标准:采用美国国家标准技术研究所(NIST)认证的AES-256算法
- CBC模式:密码块链接模式提供更强的安全性
- 随机IV:每次加密使用不同的初始向量,防止相同明文产生相同密文
- 密钥指纹:SHA256哈希确保密钥完整性验证
数据保护
- 本地存储:所有数据存储在本地,不涉及网络传输
- 密钥分离:密钥与密文分别存储,提高安全性
- 格式验证:严格的输入格式验证防止无效数据
- 错误处理:完善的异常处理机制保护系统稳定性
常见问题解答
Q1: 忘记密钥怎么办?
答:如果忘记了自定义密钥,可以通过以下方式找回:
- 查看aes_keys.json文件中的历史密钥
- 根据密钥指纹匹配正确的密钥
- 如果密钥文件丢失,则无法恢复加密数据
Q2: 密文文件损坏如何处理?
答:
- 检查密文是否为完整的Base64编码
- 确认密文长度是否正确(至少20字节的Base64编码)
- 验证使用的密钥是否与加密时一致
- 如果数据确实损坏,则无法恢复
Q3: 如何备份重要数据?
答:建议定期备份以下文件:
- aes_keys.json - 密钥文件(最重要)
- ciphertext1.txt - 密文文件
- plaintext_history.json - 解密历史
Q4: 程序运行出错怎么办?
答:
- 检查Python环境和依赖包是否正确安装
- 确认CustomTkinter版本兼容性
- 查看错误提示信息,通常会指出具体问题
- 删除损坏的配置文件,程序会自动重新创建
使用建议
最佳实践
- 定期备份密钥 :将aes_keys.json文件备份到安全位置
- 使用强密钥:导入自定义密钥时确保随机性和复杂度
- 验证结果:加密后立即测试解密确保数据完整性
- 安全存储:将密钥文件存储在加密的存储设备中
性能优化
- 批量操作:对于大量数据,建议分批处理
- 内存管理:处理大文件时注意内存使用情况
- 文件清理:定期清理不需要的历史记录文件
注意:本系统仅用于合法的数据保护目的,请遵守相关法律法规,不得用于非法用途。加密的身份证信息应当妥善保管,防止泄露。
源码:
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事件循环