当前位置: 首页 > news >正文

Python 实现高效的 SM4 大文件加密解密实战指南20241024

Python 实现高效的 SM4 大文件加密解密实战指南


引言

在数据安全领域,使用对称加密算法如SM4进行数据保护非常常见。特别是当处理大文件时,合理的内存和块大小管理以及加密解密效率变得尤为重要。本文将分享如何使用Python进行大文件的SM4加密解密操作,并对代码进行优化,加入异常处理和内存管理措施,确保程序在处理大文件时的稳定性。


目录

  • 背景介绍
  • SM4算法概述
  • 挑战与解决方案
  • 填充处理的重要性
  • 大文件的分块处理
  • 完整代码实现
    • 环境准备
    • AlgorithmTool 类的实现
    • SM4FileHelper 类的实现(含详细注释)
  • 代码解析与说明
    • 填充与去填充
    • 按块加密解密逻辑
    • 处理最后一个数据块
  • 最佳实践与注意事项
    • 内存优化
    • 异常处理
    • 安全性考虑
  • 总结

背景介绍

SM4 是我国国家密码管理局制定的对称加密算法,被广泛应用于数据保护领域。然而,在对大文件进行加密解密时,如果不合理管理内存和块的大小,程序可能会崩溃或变得非常缓慢。我们将通过优化代码和加入合适的异常处理机制,提升程序的鲁棒性和性能。


SM4 算法概述

SM4 是一种分组对称加密算法,其块大小和密钥长度均为128位(16字节)。它的安全性和高效性使其成为移动通信、电子政务等领域的首选算法。


挑战与解决方案

初始问题

在使用SM4进行大文件加密解密时,您可能遇到以下问题:

  1. 加密后的文件大小与原始文件大小相同:加密后的文件应该比原文件略大,这是由于填充机制导致的。
  2. 解密后的文件内容不正确,无法还原到原始文件
问题分析

通过代码分析,我们发现了以下问题:

  1. 填充处理不当:加密过程中,最后一个数据块没有正确处理填充,导致加密数据的长度与原始数据不匹配。
  2. 数据块未对齐:SM4 的块大小是16字节,但在加密和解密时使用了不同的块大小,导致数据块未正确对齐。

解决方案

通过以下改进,我们修复了这些问题:

  1. 调整数据块的大小:使用16的倍数作为块大小(例如16 * 1024字节),确保数据块与SM4的块大小对齐。
  2. 正确处理填充:仅对最后一个数据块进行填充,解密时正确去除填充。
  3. 添加异常处理:引入合理的异常处理机制,确保在文件读取、加解密过程中处理意外情况。

完整代码实现(含详细注释)

环境准备

确保安装了gmssl库:

pip install gmssl
AlgorithmTool 类的实现
from gmssl.sm4 import CryptSM4, SM4_ENCRYPT, SM4_DECRYPT# 工具类:提供SM4加解密以及填充处理
class AlgorithmTool:def __init__(self):pass# 将16进制字符串转换为字节数据@staticmethoddef hex_string_to_bytes(data):return bytes.fromhex(data.replace(" ", ""))# PKCS7填充算法,确保数据长度是16字节的整数倍def pkcs7_padding(self, data):pad_len = 16 - (len(data) % 16)  # 计算需要填充的字节数padding = bytes([pad_len] * pad_len)  # 生成填充字节return data + padding  # 将填充字节附加到数据末尾# 去除PKCS7填充,恢复原始数据def pkcs7_unpadding(self, data):pad_len = data[-1]  # 读取最后一个字节,获取填充长度return data[:-pad_len]  # 移除填充字节# 使用SM4进行ECB模式加密def encrypt_sm4(self, key, value):crypt_sm4 = CryptSM4()crypt_sm4.set_key(self.hex_string_to_bytes(key), SM4_ENCRYPT)  # 设置加密密钥return crypt_sm4.crypt_ecb(value)  # 返回加密后的字节数据# 使用SM4进行ECB模式解密def decrypt_sm4(self, key, encrypted_value):crypt_sm4 = CryptSM4()crypt_sm4.set_key(self.hex_string_to_bytes(key), SM4_DECRYPT)  # 设置解密密钥decrypted_data = crypt_sm4.crypt_ecb(encrypted_value)  # 进行解密return self.pkcs7_unpadding(decrypted_data)  # 去除填充,返回解密后的数据
SM4FileHelper 类的实现(含详细注释)
import os
import io# 文件加密辅助类:提供按块加密和解密大文件的功能
class SM4FileHelper:def __init__(self, key):self.ag_tool = AlgorithmTool()  # 使用AlgorithmTool处理加解密self.key = key  # 16进制密钥self.block_size = 16 * 1024  # 块大小为16的倍数# 加密文件,并在文件开头保存原始文件大小信息def encrypt_file(self, input_file_path, output_file_path):"""按块加密文件内容,并在文件开头存储原始文件大小"""try:# 获取输入文件的总大小file_size = os.path.getsize(input_file_path)# 打开输入和输出文件with open(input_file_path, 'rb') as f_in, open(output_file_path, 'wb') as f_out:# 写入文件大小,便于解密时恢复f_out.write(file_size.to_bytes(8, byteorder='big'))# 循环按块读取文件while True:chunk = f_in.read(self.block_size)  # 读取数据块if not chunk:break  # 如果没有数据,则结束循环if len(chunk) < self.block_size:# 如果是最后一个块,进行填充后加密padded_chunk = self.ag_tool.pkcs7_padding(chunk)encrypted_chunk = self.ag_tool.encrypt_sm4(self.key, padded_chunk)else:# 如果是完整的块,直接加密encrypted_chunk = self.ag_tool.encrypt_sm4(self.key, chunk)f_out.write(encrypted_chunk)  # 写入加密后的数据except IOError as e:print(f"文件操作错误: {e}")  # 处理文件读写错误except Exception as e:print(f"加密过程中的错误: {e}")  # 处理加密过程中的其他异常# 解密文件,并恢复原始文件内容def decrypt_file(self, input_file_path, output_file_path):"""按块解密文件内容"""try:# 打开输入和输出文件with open(input_file_path, 'rb') as f_in_raw, open(output_file_path, 'wb') as f_out:f_in = io.BufferedReader(f_in_raw)# 读取文件开头的原始文件大小original_file_size = int.from_bytes(f_in.read(8), byteorder='big')decrypted_size = 0  # 已解密的数据大小# 循环按块解密文件while True:encrypted_chunk = f_in.read(self.block_size + 16)  # 读取加密数据块if not encrypted_chunk:break  # 如果没有数据,则结束循环if f_in.peek(1) == b'':  # 判断是否为最后一个块# 最后一个块,进行解密后去填充,并调整大小decrypted_chunk = self.ag_tool.decrypt_sm4(self.key, encrypted_chunk)remaining_size = original_file_size - decrypted_size  # 计算剩余未写入的大小decrypted_chunk = decrypted_chunk[:remaining_size]  # 截断填充后的多余数据else:# 非最后一个块,直接解密decrypted_chunk = self.ag_tool.decrypt_sm4(self.key, encrypted_chunk)f_out.write(decrypted_chunk)  # 写入解密后的数据decrypted_size += len(decrypted_chunk)  # 更新已解密数据的大小except IOError as e:print(f"文件操作错误: {e}")  # 处理文件读写错误except Exception as e:print(f"解密过程中的错误: {e}")  # 处理解密过程中的其他异常# 比较两个文件的大小,用于验证解密文件与原文件是否一致def compare_file_size(self, original_file_path, decrypted_file_path):"""比较两个文件的大小"""try:# 获取原文件和解密后文件的大小original_size = os.path.getsize(original_file_path)decrypted_size = os.path.getsize(decrypted_file_path)# 输出文件大小对比结果print(f"原文件大小: {original_size} 字节")print(f"解密文件大小: {decrypted_size} 字节")# 检查文件大小是否一致if original_size == decrypted_size:print("文件大小一致。")else:print("文件大小不一致!")except IOError as e:print(f"比较文件大小时的错误: {e}")  # 处理文件操作异常
测试代码
if __name__ == '__main__':key = '5A3F323272CEA0640C711129E0B6043B'  # 您的16进制密钥sm4_helper = SM4FileHelper(key)input_file = 'testbig.jpg'  # 要加密的文件encrypted_file = 'encrypted_testbig.sm4'  # 保存加密文件decrypted_file = 'decrypted_testbig.png'  # 解密后的文件# 加密文件sm4_helper.encrypt_file(input_file, encrypted_file)print(f"文件 {input_file} 已加密保存到 {encrypted_file}")# 解密文件sm4_helper.decrypt_file(encrypted_file, decrypted_file)print(f"文件 {encrypted_file} 已解密保存到 {decrypted_file}")# 比较文件大小sm4_helper.compare_file_size(input_file, decrypted_file)

代码解析与说明

填充与去填充
  • PKCS7填充:在AlgorithmTool类中实现,用于确保数据长度是块大小的整数倍。
  • 填充过程
    • 计算填充字节数pad_len = 16 - (len(data) % 16)
    • 生成填充字节padding = bytes([pad_len] * pad_len)
    • 将填充字节附加到原数据末尾。
  • 去填充过程
    • 读取最后一个字节,获取填充长度pad_len = data[-1]
    • 移除填充字节data[:-pad_len]
按块加密解密逻辑
  • 块大小选择16 * 1024字节,满足SM4的块大小为16的倍数,同时平衡处理效率和内存占用。
  • 加密逻辑
    • 按块读取文件,检查是否是最后一个块:
      • 最后一个块进行填充后加密。
      • 非最后一个块直接加密。
    • 写入加密后的数据到输出文件。
  • 解密逻辑
    • 按块读取加密文件,检查是否是最后一个块:
      • 最后一个块解密后去除填充,并根据原始文件大小截断多余字节。
      • 非最后一个块直接解密。
处理最后一个数据块
  • 加密时的填充
    • 只有最后一个数据块需要填充,确保数据长度为16字节的倍数。
  • 解密时的去填充
    • 仅对最后一个数据块进行去填充处理,并截断多余字节,确保解密文件与原文件一致。

最佳实践与注意事项

内存优化
  • 按块处理:通过分块读取,避免内存占用过高。
  • 块大小调整:根据具体情况灵活调整block_size
异常处理
  • 文件操作异常:添加try-except块,处理文件读写错误。
  • 加解密异常:捕获加解密过程中可能的错误,如密钥错误或数据格式问题。
安全性考虑
  • 密钥管理:妥善存储和传输加密密钥,避免密钥泄露。
  • 敏感信息保护:不要在日志中记录密钥或未加密数据。

总结

通过这次实践,成功解决了SM4加密和解密过程中遇到的问题,深入理解了对称加密算法的细节。正确处理数据块的填充和按块读取处理确保了内存占用的优化和加解密结果的准确性。

http://www.lryc.cn/news/468465.html

相关文章:

  • 数据结构~红黑树
  • 【ROS GitHub使用】
  • 批量处理文件权限:解决‘/usr/bin/chmod: Argument list too long’的有效方法
  • 数据结构——树——二叉树——大小堆
  • Android Junit 单元测试 | 依赖配置和编译报错解决
  • ffmpeg视频滤镜: 裁剪-crop
  • 身份证归属地查询接口-在线身份证归属地查询-身份证归属地查询API
  • ESP32 S3 怎么开发基于ESP-RTC的音视频实时交互的应用,用语AI陪伴的领域
  • 车载测试分享:UDS诊断、ECU刷写、CAN一致性测试、网络通讯测试、CANoe使用、报文解析、问题定位分析
  • 预算不够,怎么跟KOL砍价?(内附砍价模板)
  • C#从零开始学习(GameObject实例)(unity Lab3)
  • 谷歌地图 | 与 Android 版导航 SDK 集成的最佳实践
  • 什么是 VolTE 中的 Slient Redial?它和 CSFB 什么关系?
  • docker 部署单节点的etcd以及 常用使用命令
  • 华为开放式耳机测评,南卡 、华为、Cleer开放式耳机超深度横评
  • 【Power Query】List.Select 筛选列表
  • Spring--4
  • django celery 定时任务 Crontab 计划格式
  • 动态应用程序安全测试 (DAST) 工具 Fortify WebInspect
  • 深入解析东芝TB62261FTG,步进电机驱动方案
  • Vue 常用的狗钩子函数
  • 【机器学习基础】激活函数
  • nnMamba用于糖尿病视网膜病变检测测试
  • 【Spring MVC】创建项目和建立请求连接
  • 台达A2伺服
  • ReactOS系统中搜索给定长度的空间地址区间中的二叉树
  • Postgresql中和时间相关的字段类型及其适用场景
  • 储能蓝海:技术革新与成本骤降引爆市场
  • java抽象类和接口
  • 法治在沃刷积分-刷文章浏览数