写点什么

crypto/md5

用户头像
吐核hú
关注
发布于: 2 小时前

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 处理流程



  1. 初始化第一个分组所需要的 MD5 值(4 个 uint32 的魔数)

  2. 整个待签名的数据需要先做分组处理,每 64 个 byte 为一组,如果末尾不足 64 个,需要进行填充

  3. 将上一分组的 MD5 值(第一组没有上一分组就用初始化的 MD5)和本分组数据作为输入,经过 4 轮位移计算生成新的 MD5 值,如果是最后的分组,该值就是最终的输出

Go 提供的 MD5 生成方式

Go 文档中提供了两种生成 MD5 的方式


  • 一种是调用New方法,生成一个digest结构体,然后调用Write方法进行数据写入,最后调用digest结构体所属的Sum方法进行输出

  • 一种是直接调用包级别的Sum方法,传入待签名数据,返回 MD5 值


package main
import ( "crypto/md5" "fmt")
func main() { b := []byte("These pretzels are making me thirsty.")
//方法一 h := md5.New() h.Write(b) fmt.Printf("%x\n", h.Sum(nil)) //h.Sum返回[]byte
//方法二 fmt.Printf("%x\n", md5.Sum(b)) //md5.Sum返回[16]byte}
复制代码


既然有两种使用方式,那就应该有对应的应用场景,要不为什么要写两种?

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 值,确认数据是否被篡改


常见的签名格式如下,其中第一种就可能会被长度扩展攻击


  1. MD5(AppKey+请求串)

  2. MD5(AppKey+请求串+AppKey)

  3. MD5(请求串+AppKey)


假设现在有一个根据订单 id 批量获取订单信息的接口,签名拼接方式如下


sign = md5(43f55d8396d0982fd62666883a9b3730appid=111orderIds=11111,999)
复制代码


请求接口时参数如下


appid=111&orderIds=11111,999&sign=745ae5112958a62414d673befafe2626
复制代码


由于 openapi 的签名算法,数据拼接格式,以及 AppKey 对外开放的,也就是已知的。假定我们要在参数中追加两个 id,22222,33333,具体的攻击步骤如下


  1. 根据明文和 AppKey 的长度可以推测整个签名字符串的长度,并且可以根据 md5 算法计算出最后分组的填充字符串

  2. 我们篡改后的明文就会变成


    appid=111&orderIds=11111,999 + 填充字符串 + ,22222,33333
复制代码


  1. 将现有的 sign 值(745ae5112958a62414d673befafe2626)初始化到digest.s中,将,22222,33333追加写入待计算的digest.x然后计算 MD5 值就能得到延长后的 MD5 值

  2. 篡改后的请求如下


    appid=111&orderIds=11111,999 + 填充字符串 + ,22222,33333&sign=d29332e75214b63c173b5485c7f07d45
复制代码


这样服务端是可以通过验签操作的,至于能不能成功返回查询就要看服务端怎么解析请求参数了


实现代码如下


package test
import ( "encoding/hex" "fmt" "testing"
"github.com/coredumptoday/hashpump/md5")
const KEY = "43f55d8396d0982fd62666883a9b3730"
func TestMD5Pump(t *testing.T) { data := "appid=111orderIds=11111,999" injectData := ",22222,33333"
originMd5Str := md5.Sum([]byte(KEY + data)) fmt.Println("origin:", hex.EncodeToString(originMd5Str[:]), KEY+data)
newSign, newStr, err := md5.HashPump(hex.EncodeToString(originMd5Str[:]), data, injectData, len(KEY)) fmt.Println(hex.EncodeToString(newSign)) fmt.Println(string(newStr)) fmt.Println(err)
m := md5.Sum([]byte(KEY + string(newStr))) fmt.Println(hex.EncodeToString(m[:]))
if hex.EncodeToString(m[:]) == hex.EncodeToString(newSign) { fmt.Println("It Worked!!!") }}
复制代码


发布于: 2 小时前阅读数: 4
用户头像

吐核hú

关注

这也不会,那也不会~ 2019.04.17 加入

高级菜哔工程师

评论

发布
暂无评论
crypto/md5