安全hash函数在信息系统中有相当广泛的应用,特别是用于消息签名来保护消息的完整性和不可抵赖性等任务,可以说安全hash函数是现代应用密码学最重要的基石之一。如果安全hash函数出现安全问题,那么整个应用密码体系乃至整个互联网的安全都受到严重影响,包括软件发行、网络支付、设备升级等等。安全hash函数面临的最大技术安全威胁是同谋碰撞攻击,但我们也发现国内不少厂家的自制协议中存在着大量不正确的安全hash应用,导致可以利用简单的方法攻破其防护。近些年随着计算机性能的提高,针对
MD5、SHA1 函数的碰撞攻击研究也进展迅速。而国内由于现实情况,替换MD5/SHA1的代价高昂,很难在短期内能解决。MD5/SHA1
碰撞究竟会对现有的信息系统产生哪些威胁?本文对 MD5/SHA1
的碰撞攻击及其应用场景进行讨论,给出了具体的攻击实例,同时给出了缓解措施。我们指出,在进行严格的约束消除同谋碰撞的条件下,要攻破MD5/SHA1依然是一个非常艰巨的任务。尽管如此,这样的机制只是给了厂商难得的喘息时间,我们依然督促各厂商尽快升级到更安全的HMAC-SHA256。
密码学中的安全 hash 函数与普通的 hash 函数有着相当大的区别。在典型数据结构hash_map、hash_set中所用到的hash函数,只需要将key映射为一个索引即可,即使产生了碰撞,也可以采用开散列或者闭散列的方式进行处理。密码学中的安全hash函数[1]要求则严格的多,至少需要满足三个性质:(1)抗碰撞攻击(2)抗原根攻击以及(3)抗第二原根攻击。抗碰撞攻击是指,寻找两个不同的串x和y,使得hash(x)=hash(y)是困难的。抗原根攻击是指,在hash(x)=s中,已知s,求x是困难的。抗第二原根攻击是指,已知hash(x)=s,求一个异于x的串y,使得hash(y)=s是困难的。只有满足了以上三点的hash函数才有资格成为安全hash函数。
MD5/SHA1是用途非常广泛的安全hash函数。他们的输入是任意串,输出是128/160bit长的散列值。MD5和SHA1在1992年和1993年被发明出来,在相当长一段时间内,他们被认为是安全的。2004年和2005年,研究者发现了对他们的攻击方法,减少了特定情况下发现碰撞的计算代价。在2007年首次提出了利用MD5碰撞伪造CA证书的理论方法,2008年这种攻击方法得到了验证,攻击者成功伪造了合法的CA证书。而针对SHA1的攻击方法则一直停留在理论中,直到2017年初,对SHA1的成功碰撞攻击才首次公开出来。
这里简要描述MD5的计算过程,以便读者理解。SHA1的计算过程十分相似:
首先,加入补位,使之bit长度模512的余数为448(512-64)。接下来将长度表示为小端序,附在后面(大于64bit只取低64bit)。此时bit长度是512的倍数。接下来依次处理每个512bit的块。在处理过程中不断更新MD5的内部状态(128bit)。在处理完所有的块后,将其内部状态输出,作为最后的散列值。
在某些情况下,攻击者需要构造一个串y使得hash(y)为一指定的值s。此时攻击者只能控制y,那么此时若要寻求一个满足hash(y)=s的实例,则属于原根攻击。对MD5/SHA1的原根攻击目前尚无好的计算方法,只能依赖彩虹表、字典或蛮力搜索,攻击复杂度相当高(MD5的状态空间是2^128,蛮力搜索要计算2^128次MD5)。
在另外一些情况下,攻击者可以同时控制两个输入串x和y,使得hash(x)=hash(y),从而欺骗基于hash的验证系统。这种情况我们称为“同谋碰撞”。同谋碰撞是可以使用“生日攻击”大幅度减少运算量的。以MD5为例,借用生日攻击,我们不需要枚举所有128bit的串计算MD5值以求碰撞,只需要计算2^64次MD5值即可有50%的几率找到这样的x和y。
MD5/SHA1碰撞具有添加后缀依然碰撞的性质:
若hash(A)=hash(B),则hash(A+S)=hash(B+S)
但是并不满足增加前缀的性质,即
hash(A)=hash(B)推不出hash(S+A)=hash(S+B)
由碰撞的后缀延伸特性导致的一种简单易行的攻击是长度扩展攻击(Length Extension Attack) [4]。例如,某些基于MD5的协议使用了如下形式的签名值
sign =hash(secret + message)
其中的secret是密钥。在计算出签名值sign后,客户端将sign和message一起提交给服务端。在某些情况下,攻击者可以同时获得签名值sign和明文message,并且拥有在明文尾部附加任意串的能力。此时,攻击者可以直接利用MD5的性质,在明文尾部附加恶意部分s,并直接从原MD5值计算
sign' =hash(sign, s) = hash(secret + message + s)
这样构造出来的新请求message+s拥有合法的签名sign',因此是能够通过MD5签名校验的,但是包括了攻击者注入的恶意部分s。这种攻击在某些CTF题目中也有涉及 [5]。在某些情况下长度扩展攻击可以用于绕过基于MD5的身份认证 [6]。由于SHA1或者其他的安全Hash大部分具有这样的性质,所以将MD5调换为SHA1或其他安全Hash也大都是无效的。
所有可能被长度扩展攻击的数字签名协议应该立即升级成第四节中所述的HMAC-SHA256算法,因为有很大可能黑产已经在享用你的服务了。
对于MD5/SHA1更复杂的攻击目前主要有共同前缀碰撞攻击(Identical PrefixCollision)和选择前缀碰撞攻击(Chosen Prefix Collision)。共同前缀攻击是构造两个不同的消息s1和s2,他们由相同的前缀,和不同的尾部“碰撞块”组成。前几天震惊世界的SHAttered攻击是对SHA1进行的共同前缀攻击。选择前缀攻击则是构造两个不同的消息s1和s2,他们不必有共同的前缀,但是经过在尾部附加不同的“碰撞块”后,两个消息的散列值相同。选择前缀攻击是包括共同前缀攻击的,因此共同前缀的攻击代价小于选择前缀攻击。在Marc Stevens(SHAttered作者)2009年的文章中对此类攻击的计算代价总结如表1。
表1:MD5和SHA1的两种不同攻击的计算代价
这里的单位是需要计算的hash的次数。例如2004年之前,对MD5的平凡攻击(生日攻击)需要蛮力计算2的64次方次md5 hash。在2009年时,对于MD5的共同前缀攻击计算代价是2的16次方次MD5 hash,对SHA1的则是2的52次方。不难看出,无论是共同前缀攻击,还是选择前缀攻击,都可以被应用在同谋碰撞攻击中。由于共同前缀攻击和选择前缀攻击的研究进展迅速,同谋碰撞攻击逐渐成为数字签名体系最大的威胁。
计算复杂度和最后构造出的碰撞结构有很大关系。对于共同前缀碰撞攻击,由王小云教授提出的攻击方法需要构造两个碰撞块,第一个碰撞块由生日攻击得到,目的是使得计算完第一碰撞块后的hash内部状态的差分满足特定形式。第二个碰撞块则由对差分路径的搜索得到。因此构造共同前缀碰撞攻击只需要两个碰撞块。对于选择前缀碰撞攻击,需要构造多个(>=2个)碰撞块。第一个碰撞块的作用相同,后续的碰撞块则对第一个碰撞块计算完成之后内部状态的区别做逐一修正。在唯一一份公开的选择前缀碰撞代码 [2,3] 中,这个后续碰撞块的长度指定为9,意为需要构造9次修正块,以完成一次碰撞攻击。长度越短,对于每个碰撞块的要求就越高,计算所需的时间也指数级提高。在长度为9时,计算可以在普通计算机上运算几小时即可完成。
对于MD5碰撞的研究主要关注与快速发现MD5碰撞。王小云教授的著名工作[7]等,以及谢涛的后续工作[8]等,使得在短时间内构造简短的MD5碰撞成为可能。对SHA1最著名的攻击是最近的SHA1 Shattered攻击,是一种共同前缀碰撞攻击 [9]。另一方面,利用选择前缀攻击,可以伪造X509格式的CA证书 [10],以及构造多个消息的碰撞“Herding Attack”,可以用于“预测”任何一次的美国总统大选结果。 此外,对于攻击签名机制,还有一些由于签名机制实现不当而产生的漏洞和对应的攻击方法。下面我们简要介绍一下选择前缀碰撞攻击的能力,然后再讨论如何利用择前缀碰撞攻击,最后回顾一下对于基于hash的签名机制实现不当而产生的攻击。
对于两个给定的不同前缀p1和p2,CPC攻击可以构造出两个后缀m1和m2,使得
hash(p1+m1) = hash(p2+m2)
而且p1+m1的长度和p2+m2的长度相等。
举个例子,令p1=“希拉里会当选”,p2=“川普会当选”。在大选之前先利用CPC攻击,构造出两个后缀m1和m2,然后计算他们的MD5值v=MD5(p1+m1)=MD5(p2+m2)。然后告诉世人本次大选结果的md5值是v。在真正结果揭晓后,若希拉里当选,则展示p1+m1,否则展示p2+m2,即可完成预测。当然实际场景下没有那么简单,任何人看到“XXX当选”后面那串毫无意义的后缀字串都会产生质疑。在实际攻击场景中往往需要一个可以在末尾附加额外数据的数据结构上进行CPC攻击,例如PE可执行文件、ZIP压缩文件、JPG图像文件等末尾都可以加入后缀且不影响正常使用。
利用CPC攻击,还可以构造多个消息的碰撞。以三个消息为例(图片来自[9]):
IHV0是MD5默认的初始状态。m1/m2/m3是三个不同的前缀,经过MD5运算后得到了三个不同的MD5状态(值)IHV1、IHV2、IHV3。此时先用CPC攻击m1和m2,生成padding c1和c2,他们有共同的MD5值IHV4。此时对m3做平凡的补长p3,生成IHV5。再从IHV4和IHV5开始使用CPC攻击,即可得到c4和c5。此时
MD5(m1+c1+c4)=MD5(m2+c2+c4)=MD5(m3+p3+c5)
而且len(m1+c1+c4)=len(m2+c2+c4)=len(m3+p3+c5)。
一份公开的CPC攻击源代码是由Mark Stevens编写的HashClash [2],支持使用CUDA来加速CPC攻击中的生日攻击部分,然而这份代码目前不能很好工作。一份能工作的代码在 [11]中可以找到。由于CUDA接口的升级,[2,11]中的CUDA部分均不能正常编译。在做了一些修正后我们把现在可以工作的版本公布在 [12]中,供网友实验。
使用 [12],我们构造了两组hash碰撞,大小分别为704字节和1048576字节(1Mb)。在我们双路E5-2650V3的服务器上,我们使用了30个核心和3块K1200加速卡,运算6小时可得到一组碰撞。其中使用加速卡进行生日攻击的时间只有不到十分钟。
A. 伪造CA证书
在MarcStevens 2009年的工作中,展示对CA证书的攻击使用的是选择前缀攻击。图1展示了伪造CA证书的原理 [10]。
图1 CA证书的伪造过程
图1左侧是合法的网站证书结构,右侧是需要伪造的CA证书结构。进行攻击时,攻击者可以同时控制左侧的合法网站证书结构,和右侧的伪造CA证书的结构。这里攻击者需要对头部几个域(serial number/validity period)等进行"猜测",但是由于很强的可预测性,做这个猜测并不难。攻击者通过对发行证书的机构进行反复观测(反复申请证书),发现1. 序列号是线性增加的并且2. 有效期是可预测的。因此攻击者可以计划一个攻击的时间,并猜测那个时间段可能出现的序列号范围,并构造其对应有效期,构造出一批可能的合法证书头部,作为选择前缀攻击的消息前缀。而对于右侧伪造的CA证书,则构造了一个合法的CA证书头部。接下来攻击者就进行大量运算,计算出一批碰撞。接下来再对做出的碰撞进行尾部补全,得到一批“合法网站证书”和“伪造CA证书”,并且从“合法网站证书”中取出RSA公钥。最后,在攻击者精心计划的攻击时间,去申请网站证书。如果申请下来的网站证书的序列号/有效期恰好匹配到攻击者预测范围内的某一个证书,那么攻击就完成了,生成了一个完全“有效”的伪造的CA证书。
2007年,Alexander Sotirov和Marc Stevens的团队使用了200台PS3主机进行了基于MD5 CPC攻击的CA证书伪造攻击。每一次计算耗时1-2天,在失败了3次后,第四次终于成功。
B.SHA1 SHAttered攻击
今年2月,SHA1的碰撞攻击SHAttered首次公开在互联网上。SHAttered攻击属于共同前缀攻击,其展示形式非常直观--两个大小相等、SHA1值相同,但是显式效果迥异的PDF文档。
图2 SHAttered攻击的实例
该攻击的难点有二,一是如何完成SHA1共同前缀攻击,二是如何使用共同的前缀和后缀,以及不同的碰撞块,构造两个合法的PDF,并且他们的显示效果不同。
第一个问题,Marc Stevens已经为此研究数年。这次他联合了Google的工程师终于把算法变成现实。在这次攻击中,他们使用了CPU来计算碰撞块的第一个block,使用GPU来计算碰撞块的第二个block。碰撞块的第一个block的第一次碰撞消耗了3583核-年的计算量,第二次碰撞消耗了2987核-年的计算量。在第二阶段,除了CPU外,还是用了GPU集群进行运算。GPU集群的计算量相当于114块K20计算一年,或是95块K40计算一年,或是71块K80一年的计算量。如果使用商业的云GPU服务,租用71块K80计算一年,就将耗费56万美元。
对于第二个问题,该攻击做了非常巧妙的构造。我们借用热心网友的回答 [17] 来做分析。首先,SHAttered攻击是一个共同前缀攻击,所以构成的pdf一定是有一个共同的pdf头部,参见图3。
图3 SHAttered攻击的PDF结构
两个PDF的头部共同部分包括:PDF文件头和image对象的一部分。在image对象中,包括了属性表引用、content结构,和属性。该攻击构造了一个巧妙的content结构,在这个结构中包含了一个JPEG图片。碰撞块Magic block 1和Magic block 2就位于这个JPEG图片内部metadata的部分,见图4。
图4 JPEG metadata部分的结构
注意其中的FFFE comment处。FF FE 这两个字节代表了comment段的开始,其后的两个字节代表该comment段的长度。在图4中出现了两个comment段,第一个段的长度是0x24,用该comment段的起始地址0x99+0x24可以得到第二个comment段的地址0xBD。可以看到0xBD位置处依然是一个FF FE标签,代表另一个comment段的开始。这个comment段的长度在两份pdf中就不相同了,在第一份pdf中,这个长度是0x173(指向0xBF+0x173=0x232),而在另一份pdf中,这个长度是0x17F(指向0xBF+0x17F=0x23E)。0x232开始的位置是一个很长的区段,而0x23E开始的部分是一个非常短的区段。其后还有一些精心布置的区段,它们的字节表示都一样,但是由碰撞块的第一字节引起差别后,所能表示的结构不同,从而使得显示出来的图像不同。
C. 对网盘的“秒传”机制进行攻击
总所周知,网盘的“秒传”机制可以大大减少文件上传的时间代价。“秒传”机制往往使用hash函数生成文件的摘要值来作为文件特征值,判断是否可以使用“秒传”功能。另一个影响“秒传”机制的参量是文件大小。往往大文件会触发“秒传”机制,而小文件不一定触发。
这里我们使用网易网盘、115网盘、城通网盘、腾讯网盘、中国移动彩云网盘、Box.com、Dropbox进行测试。除了网易网盘外,其他网盘均提供了对txt后缀文件的预览功能。我们使用了一对704字节的碰撞和一对1MB字节的碰撞作为实验文件,将他们上传到网盘中,使用下载、预览功能分别下载、预览这两个文件,测试各个网盘能否区分这两个大小相等而且MD5值相同的文件。结果如下表。如果下载下来的两个文件是相同的,则证明网盘无法区分这两个文件,我们将这个测试结果标记为“冲突”,否则标记为“不冲突”。
可以看出,网易网盘、城通网盘和彩云网盘完全无法区分MD5碰撞的文件。
著名的virustotal网站曾经使用MD5作为文件判重的标准以减少运算量。但是在CPC攻击出现之后,攻击者就可以通过先提交善意文件再传播恶意文件的方式躲避来自virusltotal的报警。因此virustotal迅速进行了修改,使用SHA256的散列值作为文件特征。
目前很多安全厂商依然在使用md5用于恶意代码识别,这里需要有额外的判重机制。由于MD5碰撞基本上都是属于上文所述的同谋攻击,因此如果相同MD5值下有一个样本是恶意的,其他的样本一般也是作为攻击的中间环节而存在,将所有样本都报警也是一种可行的办法。
D. 攻击基于“尾部附加key”形式的MD5签名进行攻击
我们先回顾上一篇文章 [13]中所述的在线支付协议中基于MD5的签名机制。以请求“c=C&b=B&a=A”为例。为了签名这个请求,先将请求解析为key-value对:
{"c":"C","b":"B","a":"A"}
然后按key的字典序进行排序并连接
a=A&b=B&c=C
附加上key
a=A&b=B&c=C&key=this_is_a_secret
计算MD5值
MD5(a=A&b=B&c=C&key=this_is_a_secret)=0a1d218f9b2b029c84c84458bb6dbc00
形成最终的请求
c=C&b=B&a=A&sign=0a1d218f9b2b029c84c84458bb6dbc00
这样的请求存在于从支付平台返回给商户服务的支付结果异步通知中。商户的验签过程通常是这样的:
1、从参数中去掉sign域
2、将剩余参数decode,将每个接口的参数按照key值升序排列;
3、将排好序的参数按照 key=value&的形式链接为字符串;
4、将生成的字符串尾部附加上密钥“key=token”,然后做MD5;
5、检查MD5值是否等于sign域。若相等,则证明消息未被窜改。
这里我们注意到:验签时的参数列表默认是对“所有”参数验签。如果攻击者可以在消息中插入一个域,那么就可以使得验签时也包括这个参数。由于此类系统的复杂性,这个请求往往很长,包括很多参数以及嵌套了许多层的商品信息。攻击者一旦找到一个参数或者嵌套的参数可以注入,那么就可以控制异步通知的内容。为了叙述方便,我们假定攻击者下了两个订单,订单Id分别为1704176438和1704176439。而且攻击者可以控制参数“zzz”的尾部内容,例如将请求设置为src的形式:
customerId=4&orderId=1704176438&service=querypay&version=1.0&zzz=
这里zzz=后面可以是任意给定内容,这里留空也不影响。攻击者构造另一个请求dst:
customerId=4&orderId=1704176439&service=querypay&version=1.0&zzz=
这里只简单修改了orderId以演示效果。然后通过CPC攻击计算得出一组碰撞src.coll和dst.coll。由于攻击者能控制zzz域,于是将src.coll进行真实支付,获得支付平台的签名sign,然后将其异步通知中的订单号做简单修改,从1704176438改为1704176439,作为伪造的异步通知提交给商户服务器。由于src.coll和dst.coll的MD5值是相等的,所以他们在尾部附加上商户的密钥之后做MD5值依然是相等的(MD5碰撞的尾部附加性质)。因此伪造的异步通知也是有效的。如果商户服务器不去查账而是相信支付平台的异步通知,那么攻击者成功的用一个订单的金额完成了两个订单。
此外,之前也出现过针对微软msi签名机制不完善/双签名机制的攻击,构造具有“合法签名”的恶意代码。也发现了真实的利用CPC攻击传播的恶意代码的实例。感兴趣的读者可以参考 [14]。
首先为了抵抗CPC攻击,不能简单的在待签字符串后面附加密钥,而是需要在待签字符串前后均附加密钥,即计算:
sign = hash(secret1 + string + secret2)
作为签名。如果只在头部附加,那么可能受到长度扩展攻击影响。如果只在尾部附加,那么可能受到CPC攻击的影响。但是如果两端均附加,那么就不受到这两类攻击的影响了。此时,为了计算一个合法的碰撞,攻击者不得不使用基于生日攻击的蛮力搜索来进行攻击。而且如果secret安全地存储在云端,攻击者必须要产生大量的网络请求来完成碰撞,这个是难以实施且易于探测的。
如果存在同谋碰撞攻击的可能,强烈建议前后同时都加签名密钥保护,而这也恰恰是HMAC [15,16] 的设计实现。因此在实际应用中建议使用HMAC扩展算法,而不是直接使用MD5/SHA等算法,即使对于SHA256也是如此。
若不能直接升级到HMAC-SHA256算法,则要尽可能的消除同谋碰撞攻击的机会,将攻击退化成针对一个固定hash值的单向碰撞攻击。在同谋碰撞攻击(共同前缀攻击和选择前缀攻击)中,由于碰撞块的存在,需要对输入有充分的自由选择能力。若能严格的限制参与签名的域,并且对参与签名的每一个域进行严格检查,则可以大幅度消除同谋攻击的潜在机会。
真实世界中不乏易受同谋攻击的例子。这里用AnySDK为例。AnySDK是一个第三方SDK接入工具,使用AnySDK可以接入许多不同在线支付平台。AnySDK提供了一份典型的有风险的验签代码 [17],容易受到同谋攻击威胁:
function checkSign($data, $privateKey) {
if (empty($data) || !isset($data['sign']) || empty($privateKey)) {
return false;
}
$sign =$data['sign'];
//sign 不参与签名
unset($data['sign']);
$_sign =getSign($data,$privateKey);
if ($_sign!= $sign){
return false;
}
return true;
}
其中的getSign函数只是负责进行拼接待签字符串和计算hash。可以看出,这份代码对输入的域并没有任何限制,于是攻击者的任何输入均可进入验签过程中。这是相当危险的,使用普通服务器运算几小时即可生成完成MD5 CPC的运算,生成一组可用于发动攻击的MD5碰撞,而成本只需几元电费。若使用云计算平台则可进一步提高速度。因此,这样的签名结构,搭配有缺陷的验签代码,现在已经非常危险。
以下是一份进行了良好的输入参数验证的示例代码,可以最大程度的消除同谋碰撞的机会:
functioncheckSign($data, $privateKey) {
if (empty($data) || !isset($data['sign']) || empty($privateKey)) {
return false;
}
$this->params['arg1'] = $data['arg1'];
$this->params['arg2'] = $data['arg2'];
if (checkArgs($this->params) == false) {
return false;
}
$sign =$data['sign'];
$_sign =getSign($this->params,$privateKey);
if ($_sign!= $sign){
return false;
}
return true;
}
其中通过checkArgs函数对params数组的每个域进行合法性检查,通过后再进行签名计算。
在实施了这些防护措施之后,同谋碰撞攻击将会近似退回到原根攻击,目前估计针对MD5的攻击复杂度将超过112bit,而针对SHA1的攻击复杂度将超过144bit,在可见的近期代价依然非常高昂,不是普通机构能够承担。
种种关于MD5/SHA1是否过时的讨论已经持续多年,但是由于国内的实际情况,MD5/SHA1依然存在于我们日常生活的每个方面,要替换的过程依然漫长。我们在对实际系统中的MD5/SHA1进行攻击分析后发现,在完善了验签、判重机制并尽可能消除同谋碰撞后,其安全性短期之内还是可以得到保证的。尽管如此,这样的机制只是给了我们难得的喘息时间,我们依然督促各厂商尽快升级到更安全的HMAC-SHA256。最后再次强调,所有可能被长度扩展攻击的数字签名协议(节2.1)应该立即升级成第四节中所述的HMAC-SHA256算法,因为很有可能黑产已经在享用你的服务了。
参考文献
1. Cryptographic Hash Function,https://en.wikipedia.org/wiki/Cryptographic_hash_function
2. The HashClash Project, http://www.win.tue.nl/hashclash/
3. Create your own MD5 collisions,https://natmchugh.blogspot.com/2015/02/create-your-own-md5-collisions.html
4. MD5长度扩展攻击,https://zh.wikipedia.org/wiki/长度扩展攻击
5. 哈希长度扩展攻击解析,ricterzheng,https://ricterz.me/posts/哈希长度扩展攻击解析
6. MD5长度扩展攻击原理,http://blog.nsfocus.net/tech/技术分享/2016/06/27/MD5长度扩展攻击原理.html
7. How to break MD5 and other hash functions,http://dl.acm.org/citation.cfm?id=2154601
8. How To Find Weak Input Differences For MD5 Collision Attacks,http://eprint.iacr.org/2009/223.pdf
9. MD5 Chosen Prefix Collision,https://www.win.tue.nl/hashclash/ChosenPrefixCollisions/
10. Creating a rogue CA certificate,http://www.win.tue.nl/hashclash/rogue-ca/
11. Create your own MD5 collisions,https://natmchugh.blogspot.com/2015/02/create-your-own-md5-collisions.html
12. MD5碰撞示例代码,https://github.com/dingelish/md5-cpc
13. 支付安全不能说的那些事,百度安全实验室公众号
14. MD5碰撞的演化之路,http://bobao.360.cn/learning/detail/2577.html
15. Keying hash functions for message authentication, Mihir Bellare ,Ran Canetti , Hugo Krawczyk
16. How did the Shattered.iogroup manage to create a SHA1 collision for a PDF that is similar looking tothe original?,https://security.stackexchange.com/questions/152341/how-did-the-shattered-io-group-manage-to-create-a-sha1-collision-for-a-pdf-that
17. AnySDK签名算法及示例, http://docs.anysdk.com/integration/server/payment-notice/