在互联网时代,无论文件存储在本地端还是云端,安全性问题都是不容忽略的重要考虑因素。尤其对于重要的学习资料,我们可以在存储前对其进行加密处理,并在需要时使用解密进行恢复。然而,面对大文件,比如约4GB的文件,一次性进行Hash计算及加解密似乎存在一些难题,可能导致内存溢出等问题。因此,设计一种高效的大文件Hash计算及加解密流程显得尤为重要。
先简单了解下本文涉及的两种安全算法:
SHA-256
加密哈希函数,设计用于产生固定长度的哈希值,通常用于确保数据的完整性。分组大小为64字节。
AES/CBC/PKCS5Padding
对称密钥加密算法,被广泛用于保护数据的机密性。CBC模式使用前一个块的密文与当前块的明文进行异或操作,增强了安全性。需要一个初始化向量(IV)来加密第一个块。分组大小为16字节,不足时需要进行填充。
具体流程
本文中使用SHA-256来计算文件的Hash来确保数据的完整性,使用AES/CBC/PKCS5Padding对文件进行加解密。
对文件进行分块时,要选择合适的块大小BLOCK_SIZE,64B的倍数,本文选取2MB,选择过小,AES填充会使加密后的文件体积增加较大。
需要注意的是,对16B倍数的块,AES填充会增加AES.block_size数据,因此加密时BLOCK_SIZE读取文件,解密需要BLOCK_SIZE+AES.block_size读取文件。
具体代码实现如下,本人电脑配置加解密4GB大小文件均耗时10s左右。
pip install pycryptodome
from hashlib import sha256
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from time import time
from datetime import datetime
import json
import os
# ASE:16B
# SHA256:64B
# 加密块大小 2MB
BLOCK_SIZE = 1024 * 1024 * 2
def log(text: str) -> None:
"""
日志输出
"""
format = "%Y-%m-%d %H:%M:%S"
format_text = f"[{datetime.now().strftime(format)}] {text}"
print(format_text)
def simple_aes_decrypt(key: str, data: bytes) -> bytes:
"""
简单AES/CBC/PKCS5Padding解密
"""
# 创建cipher对象
cipher = AES.new(key=key.encode(), mode=AES.MODE_CBC, iv=key.encode())
# 对输入数据进行解密
decrypted_data = cipher.decrypt(data)
return unpad(decrypted_data, AES.block_size)
def simple_aes_encrypt(key: str, data: bytes) -> bytes:
"""
简单AES/CBC/PKCS5Padding加密
"""
# 创建cipher对象
cipher = AES.new(key=key.encode(), mode=AES.MODE_CBC, iv=key.encode())
# 对输入数据进行加密
encrypted_data = cipher.encrypt(pad(data, AES.block_size))
return encrypted_data
def encrypt_file(file_path: str, key: str, output_name: str = None) -> str:
"""
AES/CBC/PKCS5Padding加密文件
"""
# 检查文件是否存在
if not os.path.exists(file_path):
raise FileNotFoundError("文件不存在")
# 文件属性
file_input = open(file_path, "rb")
file_input_name = os.path.basename(file_path)
file_input_size = os.path.getsize(file_path)
# 如果没有指定输出文件名,则使用20位Hash值作为文件名
if not output_name:
output_name = sha256(file_input_name.encode()).hexdigest()[:20]
file_output = open(output_name, "wb")
# 同时进行Hash计算和AES加密
log("开始加密...")
cipher_hash = sha256()
cipher_encrypt = AES.new(key=key.encode(), mode=AES.MODE_CBC, iv=key.encode())
while True:
bytes = file_input.read(BLOCK_SIZE)
if not bytes:
break
cipher_hash.update(bytes)
# size = BLOCK_SIZE+AES.block_size
encrypt_bytes = cipher_encrypt.encrypt(pad(bytes, AES.block_size))
file_output.write(encrypt_bytes)
file_input.close()
file_output.close()
hash = cipher_hash.hexdigest()
# 保存参数,用于解密时恢复文件名、完整性验证
config = {
"filename": file_input_name,
"size": file_input_size,
"hash": hash,
"time": int(time() * 1000),
}
config = json.dumps(config, ensure_ascii=False)
config = simple_aes_encrypt(key, config.encode())
file_config_name = output_name + ".config"
file_config = open(file_config_name, "wb")
file_config.write(config)
file_config.close()
log("加密完成")
return output_name
def decrypt_file(file_path: str, key: str, output_name: str = None) -> None:
"""
AES/CBC/PKCS5Padding解密文件
"""
# 检查文件是否存在
if not os.path.exists(file_path):
raise FileNotFoundError("文件不存在")
# 检查配置文件是否存在
config = None
file_config_name = file_path + ".config"
if not os.path.exists(file_config_name):
log("配置文件不存在")
else:
file_config = open(file_config_name, "rb")
config = file_config.read()
file_config.close()
config = simple_aes_decrypt(key, config)
config = json.loads(config.decode())
if not output_name:
output_name = config["filename"]
file_output = open(output_name, "wb")
file_input = open(file_path, "rb")
# 同时进行Hash计算和AES加密
log("开始解密...")
cipher_hash = sha256()
cipher_decrypt = AES.new(key=key.encode(), mode=AES.MODE_CBC, iv=key.encode())
while True:
# size = BLOCK_SIZE+AES.block_size
bytes = file_input.read(BLOCK_SIZE + AES.block_size)
if not bytes:
break
decrypt_bytes = unpad(cipher_decrypt.decrypt(bytes), AES.block_size)
cipher_hash.update(decrypt_bytes)
file_output.write(decrypt_bytes)
file_input.close()
file_output.close()
log("解密完成")
# 验证文件完整性
if config:
hash = cipher_hash.hexdigest()
if hash != config["hash"]:
raise Exception("文件完整性验证失败")
else:
log("文件完整性验证成功")
else:
log("缺少配置文件,未验证文件完整性")
return output_name
if __name__ == "__main__":
file_path = r"C:\Users\xxxx\Downloads\xxxxx.zip"
key = "1234567890123456" # 16字节/32字节
output_name = encrypt_file(file_path, key)
decrypt_file(output_name, key)
本文讲述了一种大文件Hash计算及加解密流程,可以根据实际需要替换其中算法和优化流程,完善成命令行工具应在日常中使用。