TVP

# 哈希

Hash，一般翻译做“散列”，也有直接音译为“哈希”的，就是把任意长度的输入（又叫做预映射， pre-image），通过散列算法，变换成固定长度的输出，该输出就是散列值。

# MD5算法

MD5算法的详细描述在RFC1321中有详细描述，感兴趣的可以自己去翻阅文档。

# MD5数据填充过程

## 填充规则

``````def padding_message(msg: bytes) -> bytes:
orig_len_in_bits = (8 * len(msg)) & 0xffffffffffffffff
msg += bytes([0x80])
while len(msg) % 64 != 56:
msg += bytes([0x00])
msg += orig_len_in_bits.to_bytes(8, byteorder = 'little')
return msg
``````

# MD5哈希运算流程

``````A: int = 0x67452301
B: int = 0xefcdab89
D: int = 0x10325476
``````

# 哈希长度拓展攻击

## 哈希长度扩展攻击的条件

• 攻击者具有某个特定的明文
• 攻击者获取了这个特定明文消息的加盐哈希值
• 攻击者获取了盐的长度，但是不知道盐的具体内容 举一个例子，假设服务端现在有一个长度为10字节的盐，其默认的合法的明文为：`hello,world`
``````    # 服务端根据正常的数据预先计算出来的哈希值
key: bytes = b"1234567890"
data: bytes = b"hello,world"
attack_data: bytes = b"attack data"
h = hashlib.md5(key + data)
md5_value = h.hexdigest()
print("exposed md5 hash value:{}, key len:{}".format(md5_value, len(key)))
``````

• 明文：hello,world
• 盐的长度：10

## 攻击目的

• 明文：hello,worldxxxxxxxxx
• 验证哈希：a1b2c3d4f5g6h7j8k9l0a1b2c3d4f5g6

## MD5实现

``````# 参考：https://github.com/bkfish/Script-DES-Crypto/blob/master/MD5/python3/md5.py

import hashlib
import math
from typing import Any, Dict, List

rotate_amounts = [7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21]

constants = [int(abs(math.sin(i + 1)) * 2 ** 32) & 0xFFFFFFFF for i in range(64)]

functions = 16 * [lambda b, c, d: (b & c) | (~b & d)] + \
16 * [lambda b, c, d: (d & b) | (~d & c)] + \
16 * [lambda b, c, d: b ^ c ^ d] + \
16 * [lambda b, c, d: c ^ (b | ~d)]

index_functions = 16 * [lambda i: i] + \
16 * [lambda i: (5 * i + 1) % 16] + \
16 * [lambda i: (3 * i + 5) % 16] + \
16 * [lambda i: (7 * i) % 16]

def get_init_values(A: int = 0x67452301, B: int = 0xefcdab89, C: int = 0x98badcfe, D: int = 0x10325476) -> List[int]:
return [A, B, C, D]

def left_rotate(x, amount):
x &= 0xFFFFFFFF
return ((x << amount) | (x >> (32 - amount))) & 0xFFFFFFFF

"""
在MD5算法中，首先需要对输入信息进行填充，使其位长对512求余的结果等于448，并且填充必须进行，即使其位长对512求余的结果等于448。
因此，信息的位长（Bits Length）将被扩展至N*512+448，N为一个非负整数，N可以是零。
填充的方法如下：
1) 在信息的后面填充一个1和无数个0，直到满足上面的条件时才停止用0对信息的填充。
2) 在这个结果后面附加一个以64位二进制表示的填充前信息长度（单位为Bit），如果二进制表示的填充前信息长度超过64位，则取低64位。
经过这两步的处理，信息的位长=N*512+448+64=(N+1）*512，即长度恰好是512的整数倍。这样做的原因是为满足后面处理中对信息长度的要求。
"""
orig_len_in_bits = (8 * len(msg)) & 0xffffffffffffffff
msg += bytes([0x80])
while len(msg) % 64 != 56:
msg += bytes([0x00])
msg += orig_len_in_bits.to_bytes(8, byteorder = 'little')
return msg

def md5(message: bytes, A: int = 0x67452301, B: int = 0xefcdab89, C: int = 0x98badcfe, D: int = 0x10325476) -> int:
hash_pieces = get_init_values(A, B, C, D)[:]
for chunk_ofst in range(0, len(message), 64):
a, b, c, d = hash_pieces
chunk = message[chunk_ofst:chunk_ofst + 64]
for i in range(64):
f = functions[i](b, c, d)
g = index_functions[i](i)
to_rotate = a + f + constants[i] + int.from_bytes(chunk[4 * g:4 * g + 4], byteorder = 'little')
new_b = (b + left_rotate(to_rotate, rotate_amounts[i])) & 0xFFFFFFFF
a, b, c, d = d, new_b, b, c
for i, val in enumerate([a, b, c, d]):
hash_pieces[i] += val
hash_pieces[i] &= 0xFFFFFFFF

return sum(x << (32 * i) for i, x in enumerate(hash_pieces))

def md5_to_hex(digest: int) -> str:
raw = digest.to_bytes(16, byteorder = 'little')
return '{:032x}'.format(int.from_bytes(raw, byteorder = 'big'))

def get_md5(message: bytes, A: int = 0x67452301, B: int = 0xefcdab89, C: int = 0x98badcfe, D: int = 0x10325476) -> str:
return md5_to_hex(md5(message, A, B, C, D))
``````

``````    def test_md5_value(self):
for i in range(1024):
data: bytes = bytes([random.randint(1, 255) for _ in range(i)])
self.assertEqual(hashlib.md5(data).hexdigest(), md5_study.get_md5(data))
``````

## 攻击实现

• 根据这个哈希还原下一次计算的幻数（A、B、C、D）
• 重新计算添加了攻击数据后的哈希值
• 计算传递给服务端的明文数据，使其满足：
• 在添加了攻击数据后，正常的加盐哈希仍然能够计算出来
• 添加了攻击数据后，服务端计算出来的加盐哈希与攻击者期望的一致（这里其实必定是一致的）
``````def md5_attack(message: bytes, A: int = 0x67452301, B: int = 0xefcdab89, C: int = 0x98badcfe,
D: int = 0x10325476) -> int:
hash_pieces = get_init_values(A, B, C, D)[:]
for chunk_ofst in range(0, len(message), 64):
a, b, c, d = hash_pieces
chunk = message[chunk_ofst:chunk_ofst + 64]
for i in range(64):
f = functions[i](b, c, d)
g = index_functions[i](i)
to_rotate = a + f + constants[i] + int.from_bytes(chunk[4 * g:4 * g + 4], byteorder = 'little')
new_b = (b + left_rotate(to_rotate, rotate_amounts[i])) & 0xFFFFFFFF
a, b, c, d = d, new_b, b, c
for i, val in enumerate([a, b, c, d]):
hash_pieces[i] += val
hash_pieces[i] &= 0xFFFFFFFF

return sum(x << (32 * i) for i, x in enumerate(hash_pieces))

def get_init_values_from_hash_str(real_hash: str) -> List[int]:
"""

Args:
real_hash: 真实的hash结算结果

Returns: 哈希初始化值[A, B, C, D]

"""
str_list: List[str] = [real_hash[i * 8:(i + 1) * 8] for i in range(4)]
# 先按照小端字节序将十六进制字符串转换成整数，然后按照大端字节序重新读取这个数字
return [int.from_bytes(int('0x' + s, 16).to_bytes(4, byteorder = 'little'), byteorder = 'big') for s in str_list]

def get_md5_attack_materials(origin_msg: bytes, key_len: int, real_hash: str, append_data: bytes) -> Dict[str, Any]:
"""

Args:
origin_msg: 原始的消息字节流
key_len: 原始密钥（盐）的长度
real_hash: 计算出的真实的hash值
append_data: 需要添加的攻击数据

Returns: 发起攻击需要的物料信息
{
'attack_fake_msg': bytes([...]),
'attack_hash_value': str(a1b2c3d4...)
}

"""
init_values = get_init_values_from_hash_str(real_hash)
# print(['{:08x}'.format(x) for x in init_values])
# 只知道key的长度，不知道key的具体内容时，任意填充key的内容
fake_key: bytes = bytes([0xff for _ in range(key_len)])
# 计算出加了append_data后的真实填充数据
# 攻击者提前计算添加了攻击数据的哈希
A = init_values[0],
B = init_values[1],
C = init_values[2],
D = init_values[3]))
attack_fake_msg = origin_msg + fake_padding_data + append_data
return {'attack_fake_msg': attack_fake_msg, 'attack_hash_value': attack_hash_value}
``````

## 攻击效果

``````if __name__ == '__main__':
# 服务端根据正常的数据预先计算出来的哈希值
key: bytes = b"1234567890"
data: bytes = b"hello,world"
attack_data: bytes = b"attack data"
h = hashlib.md5(key + data)
md5_value = h.hexdigest()
print("exposed md5 hash value:{}, key len:{}".format(md5_value, len(key)))

# 预备发起攻击，先计算构造碰撞相关的参数
attack_materials = get_md5_attack_materials(data, len(key), md5_value, attack_data)

# 服务端验证
h = hashlib.md5(key + attack_materials['attack_fake_msg'])
server_checked_md5_value = h.hexdigest()
print("server checked md5 hash value:", server_checked_md5_value,
'\nexpected hash value:', attack_materials['attack_hash_value'])
if server_checked_md5_value == attack_materials['attack_hash_value']:
print("attack success!!!")
else:
print("attack failed***")
``````

# 更好的消息验证方式

• 可以将消息摘要的值再进行消息摘要，这样就可以避免用户控制message了。

• 将secret放置在消息末尾也能防止这种攻击。

HMAC后面单独写一篇，敬请期待！

## 参考文档

• https://www.cnblogs.com/ProbeN1/p/14875284.html
• https://blog.csdn.net/yalecaltech/article/details/88753684
• http://blog.nsfocus.net/hash-length-extension-attack/
• https://zhuanlan.zhihu.com/p/410338280

bowenerchen

0 条评论

• ### 哈希长度扩展攻击

0x00 简介 Hash，一般翻译做“散列”，也有直接音译为“哈希”的，就是把任意长度的输入（又叫做预映射， pre-image），通过散列算法，变换成固定长...

• ### 哈希碰撞与生日攻击

所谓哈希（hash），就是将不同的输入映射成独一无二的、固定长度的值（又称"哈希值"）。它是最常见的软件运算之一。

• ### PHP哈希表碰撞攻击原理

哈希表碰撞攻击（Hashtable collisions as DOS attack）的话题不断被提起，各种语言纷纷中招。本文结合PHP内核源码，聊一聊这种攻击...

• ### phpwind 利用哈希长度扩展攻击进行getshell

一哥新发的漏洞，还是蛮屌的： http://www.wooyun.org/bugs/wooyun-2016-0210850。分析补丁（ http://www.p...

• ### 【首发】记DedeCMS一处由哈希长度拓展攻击引起的越权漏洞

漏洞影响：Dedecms(织梦CMS) V5.7.72 正式版20180109 (最新版)

• ### 科普哈希长度扩展攻击(Hash Length Extension Attacks)

貌似大多数渗透师都很少测试密码学方面的漏洞。我一直都对密码学颇有兴趣，于是决定研究web应用开发者误用加密算法的情况，以及如何利用这些漏洞。 一月份的时候，我研...

• ### 内网渗透之哈希传递攻击

大多数渗透测试人员都听说过哈希传递(Pass The Hash)攻击。该方法通过找到与账户相关的密码散列值(通常是 NTLM Hash)来进行攻击。在域环境中，...

• ### 写给开发人员的实用密码学 - Hash算法

说到Hash（哈希），开发人员应该不陌生，比如Hash表是一种非常常用的数据结构，通过Hash表能够根据键值快速找到数据。哈希函数将文本（或其他数据）映射为整数...

• ### MD5现在还有人用么？

说到密码学，阿粉现在真的是非常的头大，为啥呢？因为密码学真的是有点难度呀，各种各样的加密手段，各种各样的解密手段，像 MD5 呀，还有 RSA 呀，还有 DES...

• ### 哈希Hash竞猜游戏系统开发详解技术丨哈希竞猜游戏系统开发方案解析

Ron Rivest设计，生成128位消息摘要值，用于高速计算要求的软件系统，针对微处理器进行了优化。

• ### 幸运哈希竞猜游戏系统开发加密哈希算法

哈希算法（Hash function）又称散列算法，是一种从任何数据（文件、字符等）中创建小的数字“指纹”的方法。哈希算法只需满足把一个散列对象映射到另一个区间...

• ### 为什么要用BLAKE2替换SHA-1？| 密码学分析

当哈希碰撞成为现实 如果你在过去的十二年里不是生活在原始森林的话，那么你一定知道密码学哈希函数SHA-1是存在问题的，简而言之，SHA-1没有我们想象中的那么...

• ### 漫画：如何破解MD5算法？

在之前的漫画中，我们介绍了MD5算法的基本概念和底层原理，没看过的小伙伴们可以点击下面的链接：

• ### 一文读懂 MD5 算法

消息摘要算法是密码学算法中非常重要的一个分支，它通过对所有数据提取指纹信息以实现数据签名、数据完整性校验等功能，由于其不可逆性，有时候会被用做敏感信息的加密。消...

• ### 3分钟学会--摘要算法

摘要算法又称哈希算法，它表示输入任意长度的数据，输出固定长度的数据，相同的输入数据始终得到相同的输出，不同的输入数据尽量得到不同的输出。

• ### 深入浅出彩虹表原理

一言以蔽之，彩虹表是一种破解用户密码的辅助工具。彩虹表以时空折中理论为基础，但并不是简单地“以空间换时间”，而是一种“双向交易”，在二者之间达到...

• ### MD5摘要算法的几种破解方法！

昨天微信群里又热闹了起来，我一看消息，原来是有人在讨论：“如果突然有一天 MD5 算法被破解了，可逆了怎么办？”

• ### 为什么说用 MD5 存储密码非常危险，这些你该清楚

很多软件工程师都认为 MD5 是一种加密算法，然而这种观点其实是大错特错并且十分危险的，作为一个 1992 年第一次被公开的算法，到今天为止已经被发现了一些致命...

10元无门槛代金券