Python编程:大文件Hash计算及加解密

一.前言

在互联网时代,无论文件存储在本地端还是云端,安全性问题都是不容忽略的重要考虑因素。尤其对于重要的学习资料,我们可以在存储前对其进行加密处理,并在需要时使用解密进行恢复。然而,面对大文件,比如约4GB的文件,一次性进行Hash计算及加解密似乎存在一些难题,可能导致内存溢出等问题。因此,设计一种高效的大文件Hash计算及加解密流程显得尤为重要。

二.实现

先简单了解下本文涉及的两种安全算法:

SHA-256

加密哈希函数,设计用于产生固定长度的哈希值,通常用于确保数据的完整性。分组大小为64字节。

AES/CBC/PKCS5Padding

对称密钥加密算法,被广泛用于保护数据的机密性。CBC模式使用前一个块的密文与当前块的明文进行异或操作,增强了安全性。需要一个初始化向量(IV)来加密第一个块。分组大小为16字节,不足时需要进行填充。

具体流程

本文中使用SHA-256来计算文件的Hash来确保数据的完整性,使用AES/CBC/PKCS5Padding对文件进行加解密。

对文件进行分块时,要选择合适的块大小BLOCK_SIZE64B的倍数,本文选取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计算及加解密流程,可以根据实际需要替换其中算法和优化流程,完善成命令行工具应在日常中使用。

  • 7
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
以下是一个使用Python编写Diffie-Hellman算法,并在计算出会话密钥后使用cryptography库进行数据加密的示例代码: ```python from cryptography.hazmat.primitives.asymmetric import dh from cryptography.hazmat.primitives import serialization, hashes from cryptography.hazmat.primitives.ciphers.aead import AESGCM # 生成 Diffie-Hellman 密钥对 parameters = dh.generate_parameters(generator=2, key_size=2048) private_key = parameters.generate_private_key() public_key = private_key.public_key() # 将公钥序列化为字节串,并发送给对方 public_key_bytes = public_key.public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo ) # 接收对方的公钥,并反序列化为 PublicKey 对象 peer_public_key = serialization.load_pem_public_key( peer_public_key_bytes, backend=default_backend() ) # 计算会话密钥 shared_key = private_key.exchange(peer_public_key) session_key = hashes.Hash(hashes.SHA256(), backend=default_backend()) session_key.update(shared_key) session_key = session_key.finalize() # 使用会话密钥加密数据 plaintext = b"Hello, world!" aad = b"authenticated but unencrypted data" nonce = b"\x00" * 12 # 必须是 12 字节长的随机数 cipher = AESGCM(session_key) ciphertext = cipher.encrypt(nonce, plaintext, aad) ``` 需要注意的是,上述代码中仅给出了加密数据的部分,实际使用时,还需要考虑如何将密文传输给对方、如何进行解密等问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

开发大观园

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

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

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

打赏作者

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

抵扣说明:

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

余额充值