crypto/md5
MD5 简介
MD5 信息摘要算法(英语:MD5 Message-Digest Algorithm),一种被广泛使用的密码散列函数,可以产生出一个 128 位(16 字节)的散列值(hash value),用于确保信息传输完整一致。MD5 由美国密码学家罗纳德·李维斯特(Ronald Linn Rivest)设计,于 1992 年公开,用以取代MD4算法。这套算法的程序在 RFC 1321 标准中被加以规范。1996 年后该算法被证实存在弱点,可以被加以破解,对于需要高度安全性的数据,专家一般建议改用其他算法,如SHA-2。2004 年,证实 MD5 算法无法防止碰撞(collision),因此不适用于安全性认证,如SSL公开密钥认证或是数字签名等用途。
以上出自百度百科
MD5 处理流程
初始化第一个分组所需要的 MD5 值(4 个 uint32 的魔数)
整个待签名的数据需要先做分组处理,每 64 个 byte 为一组,如果末尾不足 64 个,需要进行填充
将上一分组的 MD5 值(第一组没有上一分组就用初始化的 MD5)和本分组数据作为输入,经过 4 轮位移计算生成新的 MD5 值,如果是最后的分组,该值就是最终的输出
Go 提供的 MD5 生成方式
Go 文档中提供了两种生成 MD5 的方式
一种是调用
New
方法,生成一个digest
结构体,然后调用Write
方法进行数据写入,最后调用digest
结构体所属的Sum
方法进行输出一种是直接调用包级别的
Sum
方法,传入待签名数据,返回 MD5 值
既然有两种使用方式,那就应该有对应的应用场景,要不为什么要写两种?
Go 中 MD5 的实现方式
1. digest 结构体
Go 语言中 MD5 的计算都依赖于digest
结构体
如图digest
结构体包含四个部分,标注的色块为 MD5 处理流程示意图中的相关位置
digest.s
:存储的是 MD5 的值
digest.x
:存储的是分组后待计算的当前分组内容
digest.nx
:存储的是当前待处理分组中字符串的长度,也就是len(digest.x)
的值
digest.len
:存储的是待签名字符串的总长度
2. 方法调用流程
Go 语言提供的两种方式底层调用流程基本是一样的,主要是通过三个函数进行签名的
Reset
:对digest
结构体中的变量附初始值
Write
:写入数据并生成中间分组的 md5 签名,可以多次调用,追加写入,凑够分组就进行一次 MD5 计算,不够进行计算的数据存入digest.x
中进行缓存,等待下次调用 Write 写入
checkSum
:计算最后分组的填充数据,调用Write
填充数据,格式化digest.s
中数据输出 MD5 值
3. 两种方式应用场景
从源码实现角度分析两种方式的应用场景
md5.New()方式
:通过Write
方法追加写入,对于像请求签名这样需要拼合字符串的场景可以减少内存分配。再Sum
方法的调用过程中会拷贝digest
结构体及 MD5 返回值,优点是可以在生成 MD5 后继续追加数据生成新的值,缺点就是有多余的拷贝
md5.Sum方式
:需要开发人员自己拼合[]byte 后调用,在预先分配内存的场景下速度更快,但是伴随的就会有内存回收的问题
MD5 长度扩展攻击
MD5 虽然已经被证明有安全问题,但是在现行的接口签名中还是有部分使用。相应的签名格式定义问题就可能引发长度扩展攻击
一般情况下在一个 openapi 平台注册之后,会生成 AppID 和 AppKey。请求的时候需要明文传输 AppID 和其他请求参数,并且将请求串拼上 AppKey 后进行 MD5 签名,并传输 MD5 签名,服务端接收后按照同样的规则进行签名对比 MD5 值,确认数据是否被篡改
常见的签名格式如下,其中第一种就可能会被长度扩展攻击
MD5(AppKey+请求串)
MD5(AppKey+请求串+AppKey)
MD5(请求串+AppKey)
假设现在有一个根据订单 id 批量获取订单信息的接口,签名拼接方式如下
请求接口时参数如下
由于 openapi 的签名算法,数据拼接格式,以及 AppKey 对外开放的,也就是已知的。假定我们要在参数中追加两个 id,22222,33333
,具体的攻击步骤如下
根据明文和 AppKey 的长度可以推测整个签名字符串的长度,并且可以根据 md5 算法计算出最后分组的填充字符串
我们篡改后的明文就会变成
将现有的 sign 值(745ae5112958a62414d673befafe2626)初始化到
digest.s
中,将,22222,33333
追加写入待计算的digest.x
然后计算 MD5 值就能得到延长后的 MD5 值篡改后的请求如下
这样服务端是可以通过验签操作的,至于能不能成功返回查询就要看服务端怎么解析请求参数了
实现代码如下
版权声明: 本文为 InfoQ 作者【吐核hú】的原创文章。
原文链接:【http://xie.infoq.cn/article/fe0a535153494f3dd4d64ddeb】。文章转载请联系作者。
评论