如何在Python3中替换SHA256初始值实现HMAC*长度扩展攻击
嘿,这个问题我刚好做过类似的大学实验!完全不用重写SHA256,Python里有两种靠谱的方法,我给你一步步讲清楚:
核心思路先理清楚
你的场景是典型的长度扩展攻击:已知h = SHA256(k || m),要构造h' = SHA256(k || m || pad(k||m) || a),其中pad是SHA256的标准填充规则。关键是让SHA256算法跳过前面k||m||pad(k||m)的计算,直接从已知的哈希值h作为初始状态,继续哈希后续的a。
方法一:利用CPython hashlib的内部状态(简单直接)
CPython的hashlib模块里的SHA256对象有几个私有属性可以直接修改,用来替换初始哈希状态和已处理的字节数,完全不用重写算法。
实现代码
import hashlib import struct def sha256_continue(prev_hash: bytes, data: bytes, total_processed_bytes: int) -> bytes: """ 从已知的SHA256哈希结果继续哈希后续数据 :param prev_hash: 之前的SHA256哈希值(32字节bytes) :param data: 要追加的新数据 :param total_processed_bytes: 之前已经处理的总字节数(即k||m的长度 + pad(k||m)的长度,必须是64的整数倍) :return: 新的SHA256哈希值 """ # 初始化一个SHA256对象 sha = hashlib.sha256() # 将已知哈希值拆分为SHA256的8个32位大端初始状态值 sha._h = struct.unpack('>8I', prev_hash) # 设置已处理的块数和剩余字节数(因为total_processed_bytes是64的整数倍,剩余字节数为0) block_size = 64 sha._count = (total_processed_bytes // block_size, total_processed_bytes % block_size) # 追加新数据并计算哈希 sha.update(data) return sha.digest()
为什么能行?
SHA256的计算是分块进行的,每个块处理后会更新内部的8个32位状态值。我们直接把这些状态值替换成已知的h,再告诉算法已经处理了多少字节,它就会自动从正确的位置继续计算后续数据的哈希。
方法二:用cryptography的底层接口(更规范)
如果不想依赖CPython的私有属性(毕竟私有属性理论上可能变动),可以用cryptography库的hazmat模块,它提供了更底层的哈希操作接口。
实现代码
首先需要安装库:pip install cryptography
from cryptography.hazmat.primitives import hashes from cryptography.hazmat.backends import default_backend import struct def sha256_continue_crypto(prev_hash: bytes, data: bytes, total_processed_bytes: int) -> bytes: """ 用cryptography库实现从已知哈希继续计算 """ backend = default_backend() # 创建SHA256哈希上下文 ctx = hashes.Hash(hashes.SHA256(), backend=backend) # 复制并修改内部状态 state = ctx._ctx.copy() # 替换初始状态为已知哈希值 state.h = struct.unpack('>8I', prev_hash) # 设置已处理的块数 state.nblocks = total_processed_bytes // 64 # 应用修改后的状态并更新数据 ctx._ctx = state ctx.update(data) return ctx.finalize()
完整攻击步骤示例
光有上面的函数还不够,你还需要生成正确的填充pad(k||m),并计算总处理字节数:
1. 实现SHA256填充函数
def sha256_pad(message_byte_length: int) -> bytes: """ 生成SHA256标准填充 :param message_byte_length: 原始消息(k||m)的字节长度 :return: 填充后的字节数据 """ pad = b'\x80' # 先添加一个0x80字节 # 补0直到总长度模64等于56(留出8字节存长度) pad += b'\x00' * ((56 - (message_byte_length + 1) % 64) % 64) # 添加原始消息的总比特数(大端64位整数) pad += struct.pack('>Q', message_byte_length * 8) return pad
2. 执行攻击
假设你已知h(32字节)、m(原始消息),要追加的消息是a:
# 已知参数 h = b'your_known_hmac_star_value_here' # 32字节 m = b'original_message_m_here' a = b'your_arbitrary_message_a_here' # 枚举密钥k的可能长度(因为k未知,常见长度比如16、32字节,或者0到64字节) for k_length in range(0, 65): # 计算k||m的总长度 km_length = k_length + len(m) # 生成填充 pad = sha256_pad(km_length) # 计算已经处理的总字节数(k||m + pad),这个数一定是64的整数倍 total_processed = km_length + len(pad) # 计算新的哈希h' h_prime = sha256_continue(h, a, total_processed) # 构造新消息m' = m + pad + a m_prime = m + pad + a print(f"猜测k长度为{k_length}时:") print(f"新消息m' = {m_prime}") print(f"新HMAC* h' = {h_prime.hex()}")
这样就能得到对应每个猜测k长度的有效h'和m'了,实际测试时只要找到符合验证的结果即可。
内容的提问来源于stack exchange,提问作者Atilla1899
相关产品推荐
相关产品推荐

