“MD5能不能解密?”每次项目群里有人问这个问题,我都能预感到一场混战:一边是刚入行的开发小声说“应该不能吧”,一边是测试老哥甩来一个在线MD5解密网站的链接,输入一段密文,回车,屏幕上赫然出现“admin”。于是多数人得出结论:MD5加密了,而且可以解密。这个结论错得离谱,却又异常顽固。作为跟哈希函数打了多年交道的开发者,我觉得有必要把这件事彻底讲清楚:MD5不是加密,而是摘要;它不可解密,跟“密文”能不能被查出来是两回事。这篇内容适合后端开发、测试、运维、安全方向的初学者,也适合面试前临时抱佛脚的朋友。如果你曾经疑惑过“为什么网上明明能解开MD5”,那这篇文章就是为你准备的。
1. 先纠正一个最要命的概念:MD5不是加密,是摘要
1.1 “加密”应该是什么样子的:可逆才是加密的前提
很多人把MD5和AES、DES、RSA这些东西混在一张“加密算法”的清单里,这是第一个认知误区。要理解MD5为什么不可解密,先得弄清楚真正的加密算法长什么样。
拿AES来说,它的完整流程是:原文加密钥输入算法,输出密文;解密时再用密钥把密文恢复成原文。整个过程是双向的,也就是说,只要密钥没错,算法本身保证你能从密文还原出和原文一模一样的内容。这就像保险柜,锁上之后拿钥匙还能再打开,钥匙丢了不行,但至少设计目标里有“可逆”这条。
MD5的定位完全不一样。它没有密钥,也从不承诺“还原原文”。它做的事情是把任意长度的输入,通过一系列计算压成一个固定长度的输出,这个输出通常叫“摘要”或“消息摘要”(Message Digest),而不是“密文”。你对着一个MD5值问“原文是什么”,本质上就像一个碎纸机用户问“碎屑能不能拼回原文件”一样,方向就错了。
更直白一点说:加密关心的是“保密”,我藏起来,只有有钥匙的人能看;MD5关心的是“指纹”,我算出一个特征值,用来确认内容有没有变化。两者解决的问题不同,适用场景也不同,把MD5归入“加密算法”属于历史遗留的习惯性误称。
1.2 哈希函数的三件套:定长输出、雪崩效应、单向性
MD5属于哈希函数家族。哈希函数有三个核心特征,搞清楚这三个特征,“为什么不可解密”基本就通了。
第一,定长输出。不管输入是1个字节的字符,还是1GB的电影文件,MD5的输出永远是一个128位的二进制串,通常表示成32位十六进制字符。这个特性让系统可以用固定长度的值去标识任意大小的文件,但代价是你不能从16字节里还原出几个GB的原始信息。
第二,雪崩效应。输入哪怕只改一个比特,输出也会面目全非。比如“hello”和“Hello”的MD5值完全不一样。正是这个特性,让MD5可以用来检测“文件有没有被改动过”。
第三,单向性。从输入算输出,非常快,几微秒就能完成;从输出反推输入,没有捷径可走,穷举空间大到不现实。单向性正是“不可解密”的本质来源,但我在这里要特别强调一个容易误解的点:单向不是说“一定算不出来”,而是说“没有一个通用算法能让你从摘要反推出原文”。至于某些弱密码能被查出来,那是另一码事,后面专门讲。
有一个特别好用的类比:MD5相当于给一段内容提取指纹。人体指纹可以唯一标识一个人,但你永远无法从指纹倒推出这个人的完整生理结构。别人拿着指纹数据库能“认出”这是谁,不代表指纹系统本身是可逆的。
1.3 为什么很多人都把它叫“MD5加密”:历史包袱与习惯陷阱
既然MD5不是加密,为什么到处都写着“MD5加密”?原因很复杂,但可以归纳成三个主要因素。
第一个因素是历史惯性。早期互联网应用需要存储用户密码,但数据库一旦泄露,明文密码就是灾难。开发者很快想到把密码哈希后存储,MD5是当年最流行的哈希算法,于是“密码经过MD5处理”被简单粗暴地写成“密码加密”。久而久之,“MD5加密”这个不准确的说法就传播开了,连很多技术文档、教程、甚至代码注释里都这么写。
第二个因素是反向查询网站的出现。在线MD5解密网站让你输入一个MD5值,就能得到常见密码的原貌。这给了大众一个直观印象:MD5能解,所以它是加密。但实际上这些网站根本不叫“解密”,它们是在用提前算好的海量字典做匹配查询,只是界面上写着“MD5解密”,进一步强化了错误认知。
第三个因素是编程语言和框架的推波助澜。某些老版本的类库或工具里,函数名直接就叫encrypt,参数传进去,返回MD5字符串。用的人只看API不读源码,想当然地把它当作加密来用。直到今天,很多项目的字段名还叫encrypted_password,实际存的却是哈希值。
你要想在这个行业里长期折腾,第一步就得学会对这些约定俗成的说法保持警惕。别人怎么说不重要,重要的是你把概念是否理解透了。
2. 从算法内部看MD5为什么“不可解”
2.1 一句话版:128位输出装不下原始输入的全部信息
如果只用一个理由向别人解释“为什么MD5不可解密”,我会选信息论这个角度。
MD5的输出固定只有128比特,也就是16字节。设想一下,你输入一段1KB的文本,经过MD5计算,得到16字节的摘要。请问,从16字节里能不能“还原”出那1KB文本?理论上是不可能的,因为信息量下降了:1KB等于8192比特,远大于128比特,压缩过程中绝大多数信息已经丢失了。
这里要稍微严谨一点。MD5的输出空间是2的128次方,这个数字极其庞大,但它仍然是有限的空间。而输入的取值范围是无限的,可以有无穷多个不同的原文映射到同一个MD5值。既然一个摘要可能对应无数个输入,那么“确定性地还原出唯一的原文”从理论上就不成立。
所以哪怕有朝一日量子计算机发展成熟,也不可能“计算出”一个随意的MD5值对应的原始输入,因为对应的原始输入本来就不是唯一的。你只能通过旁路信息猜测,最有可能的原文是什么,而不是从算法本身找回原文。
2.2 四轮压缩循环与位运算:每一步都在丢信息
如果只懂信息论还不够,最好再往算法内部看一眼。MD5的计算过程并不复杂,但它的设计目的就是破坏性压缩。
大致的流程可以这样理解:先把原始消息填充到长度为512比特的整数倍,填充方式是在消息末尾补一个“1”,后面跟着若干个“0”,最后附上原始消息长度的64比特表示。然后把填充后的数据分成若干个512比特的分组,每个分组再切分成16个32比特的字,进入主循环。
主循环一共有四轮,每轮进行16次操作,总共64步。每步都会基于当前128比特的状态值,从消息字里取一个32比特的块,经过非线性函数、左移位、加常数等一系列运算,更新状态。这些非线性函数里包含各种位运算和取模加法,它们的共性都是“多对一”的操作:很多种不同的输入组合,可能计算出相同的结果。
举个不严谨但直观的例子:两个数相加,你只知道结果是10,那原始的两个数可能是3和7,也可能是4和6。加法把信息合并了,合并之后就无法唯一拆开。MD5的64步操作里处处都是类似的信息合并,每走一步,原始信息的精确结构就被打乱一次。当四轮循环结束后,输出的128比特状态,已经和原文之间隔了几层“信息搅拌”,不存在一条干净的逆运算路径。
这也是为什么散列函数设计者会强调“不可逆计算”,不是某一处阻碍了逆向,而是整个算法的每一步都在主动丢弃信息。想要从最终状态逐级反推,你在遇到那些“多对一”运算时就会卡住:不知道原来的输入到底是哪一种可能。
2.3 算力再强也不能靠暴力“撞”回原文
有人可能会说,既然算法本身不能逆推,那我用暴力穷举呢?把可能的原文全试一遍,每个都算MD5,总能看到哪个能对上吧?
能,但这里有几个前提。暴力穷举只有在“候选空间很小”时才行得通。比如你知道原文是一个6位数字验证码,那最多就100万种组合,现代计算机一下午就能跑完。但如果原文是一段任意长度的自然语言文本,候选空间就变成了无限大,你没有办法穷举。
而且就算你穷举出了一个能匹配的字符串,也无法证明它就是“原始”的那个。因为哈希碰撞的存在,可能有无数个不同的字符串产生了同一个MD5值,你找到的只是碰巧撞上的一个。做密码破解时,我们常常说“跑字典”,意思是拿一份常见密码列表去试,试中了就说明“用户很可能用的是这个密码”。这依然不叫“可逆”,而叫“概率命中”。
想一想MD5的用途就知道了。如果MD5真的能逆向,那文件完整性校验体系会瞬间崩塌,所有依赖MD5校验下载文件的做法都会失去意义。这么多年过去了,MD5虽然被证明在安全性上不再可靠,但“不可逆”这个基本属性依然成立。问题不在于MD5可以被逆推,而在于它已经变得太容易“撞”了。
3. “在线MD5解密”的真相:彩虹表、字典与碰撞
3.1 在线解密网站的原理:查询数据库,不是运算
只要在搜索引擎里搜“MD5解密”,你会得到一堆在线工具网站。我第一次见到这种网站也天真地以为算法被破解了,实际上它的机制简单得令人失望:网站上存了一张巨大的数据库,里面是几十亿条“原文→MD5”的对应关系。你输入一串MD5值,它只做一个数据库查询,查到了就把对应的原文显示出来,查不到就告诉你“解密失败”。
换句话说,你输入“21232f297a57a5a743894a0e4a801fc3”,它能告诉你“admin”,是因为它在建库的时候就已经把“admin”的MD5值算好存进去了。换个随机密码,比如“p@ssw0rd-9f8e7d6c”,它的数据库里如果恰好没有,那它就“解”不出来。
所以在线解密网站的本质是“字典查询”。这也解释了为什么简单密码很快就能被解出来,复杂密码却怎么也解不了。真正决定你密码是否暴露的,不是MD5算法的安全性,而是你的密码在多大程度上存在于别人的预计算数据库里。
这就好比你家的门锁,理论上撬锁技术再高超,遇到一个没有钥匙孔的怪异锁也得研究半天;但如果你的钥匙和邻居的钥匙完全一样,那邻居随手就能开门。MD5在线解密的逻辑就是:你用的密码越常见,越容易被“撞见”。
3.2 彩虹表:空间换时间的字典攻击
普通的字典查询需要为每一条原文存储完整的原文和哈希值,数据量一大,存储开销就很可观。彩虹表则是一种更聪明的预计算攻击方式,它用“哈希链”把一系列明文和哈希值串起来,在表里只存储链条的起点和终点。
攻击时,拿着要破解的哈希值,沿着链条向后推导,如果能命中某条链的终点,就可以从起点重新计算,还原出对应的明文。整个过程中不需要存储每一条“明文–哈希值”对,只需要存储链的首尾,空间大幅缩小,但查询时需要付出一定的计算量。
这种“空间换时间”的设计,让构建超大规模字典成为可能。攻击者可以提前算好所有常见密码、常用词汇组合、各种键盘序列的彩虹表,一次性投入成本后,后续破解就异常高效。
不过彩虹表再厉害,也有一个致命软肋:它针对的是无盐哈希。一旦原始系统在计算MD5前给每个密码加了一段随机的盐值,同一个密码在不同用户、不同时间生成的MD5都是不同的。攻击者如果想用彩虹表破解,就必须为每一个可能的盐值都建一张表,那存储成本会爆炸式增长,这项攻击就变得不划算了。
3.3 加盐与迭代:让“解密”变成赔本生意
既然彩虹表和字典攻击都依赖“相同原文产生相同哈希值”这一规律,那防御手段也很自然:破坏这种规律。
最常见的做法是加盐。盐值是一段随机生成的字符串,在计算哈希时把盐和密码拼接在一起,比如password_hash = md5(salt + password),然后把盐值和哈希值一起存入数据库。这样即使两个用户使用了完全相同的密码,因为盐不同,最终得到的MD5也完全不同,攻击者预建的字典瞬间失灵。
但说实话,单纯加盐对MD5来说还是不够。MD5计算速度太快了,普通GPU每秒可以算几十亿次MD5,即使是加盐后的弱密码,用高性能设备做在线暴力破解,也可能在很短时间内跑完常用口令空间。现代密码存储更推荐使用专门的慢哈希算法,比如bcrypt、scrypt、Argon2,这些算法故意设计得非常耗CPU和内存,让攻击者的暴力破解成本高到无法承受。
所以在讨论“MD5能不能解密”时,真正重要的不是有没有算法能从MD5还原原文,而是攻击者有没有足够廉价的方式猜出原文。加盐和迭代哈希,就是为了让“猜”这件事变得昂贵,昂贵到攻击者直接放弃。
4. 正确打开MD5的方式:校验、签名与防篡改
4.1 文件比对场景:CSV、固件、镜像的完整性检查
MD5最经典、也最没有争议的用途,是文件完整性校验。这个场景完美匹配哈希函数的特征:不关心保密,只关心内容是否与原版一致。
举个例子。你从网上下载一个STM32固件压缩包,官方页面上给出了MD5值,你下载完在终端里跑一句md5sum firmware.zip,得到的值和官方一致,就能确定文件在传输过程中没有被损坏,也没被恶意替换。如果对不上,那就老老实实重下,别装。
CSV文件也一样。在很多数据管道里,上游会导出一个几万行的CSV给下游导入。CSV是纯文本,在跨系统传输时很容易被转换换行符、被填充BOM、被Excel另存后偷偷改掉几个字段格式,肉眼很难看出来,但数据入库后往往引发各种诡异问题。解决办法很朴素:文件交付方附带一个MD5值,接收方在导入前做一次校验,对不上就先检查原始文件是否被改动过,避免脏数据进入业务库。
我自己就经历过一次:上游提供一个CSV文件,我这边怎么导入都报错,字段错位、乱码,排查半天。后来让上游发来他们的MD5,我本地跑了一下,发现两边的MD5不一致。再一查,原来是文件用FTP传输时启用了文本模式,把Unix换行符转换成了Windows换行符,字节流变了,MD5自然对不上。改成二进制模式重新传输后,一切正常。这就是MD5校验最典型的实战价值。
4.2 接口签名中的MD5:防篡改不防泄密
如果说文件校验是MD5的“主场”,那接口签名中的MD5则属于“能用但有争议”的领域。很多老一代开放平台的API,都喜欢用MD5做签名。
签名逻辑通常是这样的:把请求参数按字典序排序,拼接成一个字符串,再拼上双方约定的appSecret,然后取MD5作为sign。服务端收到请求后,用同样的规则重新计算一遍,比对sign是否一致。如果请求参数在传输过程中被篡改了一个字符,服务端计算出的MD5就会完全不同,于是拒绝请求。
但这里必须说清楚:MD5签名只能证明“参数没有被改过”,不能保证“参数内容没被看到”。你的请求体在HTTP协议中仍然是明文传输的,中间人完全可以看到所有参数,只是他改不了,因为改了sign就对不上。如果你担心参数被窃听,应该启用TLS,也就是用HTTPS协议,而不是指望MD5来做机密性保护。
很多Java开发者会问“MD5加密会被拦截解析吗?”这个问题本身就有偏差。在HTTPS下,整个请求内容都是加密传输的,不存在“被拦截解析”的问题;在HTTP下,你的参数和sign都暴露在明处,攻击者虽然算不出你的appSecret,但他完全可以拿你合法的sign去重放请求。所以MD5在接口层面解决的是“完整性”和“来源可信度”,而不是“机密性”。
4.3 实操:命令行、Python、JMeter里怎么算MD5
讲再多理论,不如动手算一次。以下是我在多个项目中反复使用的方法。
Linux/macOS命令行:
md5sum data.csvmacOS上也有md5命令,直接用。
Windows PowerShell:
Get-FileHash -Algorithm MD5 -Path data.csvPython:
import hashlib def file_md5(file_path): md5 = hashlib.md5() with open(file_path, "rb") as f: for chunk in iter(lambda: f.read(8192), b""): md5.update(chunk) return md5.hexdigest() print(file_md5("data.csv"))这里有一个很重要的细节:算文件MD5时要按二进制模式读取,也就是"rb"。如果以文本模式打开,Python会做换行符转换,同样的文件在不同操作系统上可能算出不同的MD5,导致校验失败。
JMeter并发测试里算MD5:
接口压测时经常需要动态生成sign。JMeter用户可以在函数助手里找到__digest函数,选择算法为MD5,填入原始字符串,点击生成表达式,然后放到请求参数里。像我常用的写法是:
${__digest(MD5,test123,appSecret,)}更灵活的做法是用JSR223采样器注入Groovy代码:
import java.security.MessageDigest; def md5 = MessageDigest.getInstance("MD5") def bytes = "参数拼接串".getBytes("UTF-8") def digest = md5.digest(bytes) def sign = digest.collect { String.format("%02x", it) }.join() vars.put("sign", sign)两种方式都能在JMeter里动态生成MD5签名,关键点在于参数拼接规则必须跟后端一致,否则算出来的sign没法通过校验。
4.4 一个真实的CSV文件MD5校验排错过程
这个案例我再展开一点。当时数据组同事找我说上游导出的CSV数据导入后总有一列显示乱码,文件大小看起来差不多,但数据内容总有几条对不上。我用md5sum raw.csv算了一下,和上游提供的MD5对比,结果完全不一致。
我先把两个文件分别用hexdump -C raw.csv | head看开头,发现上游文件开头有UTF-8 BOM(EF BB BF),而我这边下载下来的文件开头没有BOM。再进一步检查,发现FTP客户端在传输时自动转换了换行符。CSV这种文本文件在Windows和Linux之间来回倒腾,这种事情太常见了。
后来我的处理方案很简单:上游提供文件时不做任何文本转换,用ZIP压缩包包装CSV,压缩包本身走二进制传输。这样CSV内容没有经过任何中间层改动,双方校验压缩包的MD5即可。类似地,如果你自己写脚本下载文件并做MD5校验,记得走二进制模式,别让编码和换行符成为隐性炸弹。
5. MD5真的安全吗:碰撞、绕过与选型建议
5.1 碰撞攻击:MD5的“阿喀琉斯之踵”
MD5最大的安全性危机,不是“可逆”,而是“碰撞”。
碰撞指的是:存在两个不同的输入,但它们的MD5值完全相同。从信息论上说,碰撞是必然存在的,因为输出空间有限,输入空间无限。但理论存在和实际构造是两回事。如果只是在数学上证明“应该有碰撞”,攻击者未必能找出来;可2004年之后,密码学界公开了MD5的碰撞攻击方法,攻击者可以在合理时间内构造出两个不同的文件,它们的内容不一样,但MD5完全一样。
这给攻击者带来了什么可能性?想象一下:攻击者构造一份包含正常合同的PDF和一份包含恶意条款的PDF,两份文件MD5相同。受害者对手中的正常版本做了MD5校验,确认无误,但攻击者在某个环节悄悄替换成了恶意版本,由于MD5相同,校验依然通过。数字签名、软件发布、金融交易等领域如果依赖MD5做安全校验,就可能被这种手法欺骗。
在安全敏感场景中,MD5已经不再被信任。很多安全规范、等保测评、渗透测试报告都会明确指出“使用了不安全的哈希算法”。如果只是追求完整性校验而面对的威胁模型不包含恶意攻击,MD5仍可用;但只要涉及对抗攻击者,就应当改用SHA-256或更高强度的哈希算法。
5.2 “强比较绕过”是什么:PHP和CTF中的一桩悬案
网上有一个很热的词叫“MD5强比较绕过”,经常出现在CTF比赛题和PHP项目审计中。它并不是说攻击者破解了MD5算法,而是利用了编程语言的类型比较特性,制造出逻辑漏洞。
最经典的是PHP中的松散比较漏洞。PHP的md5()函数返回的是一个字符串,在早期版本中,如果把两个哈希值用==比较,0e开头的字符串会被当成科学计数法,即“0乘以10的n次方”,结果等于0。攻击者找到两个不同的字符串,它们的MD5值都是0e开头,那么在==比较时,两边都会被解析成浮点数0,进而判断为相等。
改成强比较===呢?0e开头的两个字符串内容不一样,强比较是能发现的。但还有一招,在PHP 5.x和7.x的部分版本中,如果把数组传给md5()函数,函数会返回null,两个数组的md5()结果都是null,用===比较时null === null成立,照样绕过。当然这在PHP 8里已经因为类型限制而失效了。
我提这一段是想说明:这类“绕过”攻击绕的不是MD5算法本身,而是代码实现和类型系统的缺陷。做安全评审时不能只看“用了MD5就是罪”,要具体到代码怎么比较、怎么处理异常、怎么应对边界情况。否则你换成SHA-256,如果还是用==松散比较,同样的漏洞照样存在。
5.3 安全场景选型:MD5、SHA-256、HMAC、bcrypt
根据使用目的不同,算法选型可以整理成一个简单的对照表,方便你以后做技术决策:
| 场景 | 推荐做法 | 为什么 |
|---|---|---|
| 文件完整性校验(非对抗场景) | MD5 / SHA-1 / SHA-256 | 校验速度很快,误报率低 |
| 安全敏感文件的完整性校验 | SHA-256 或更高 | 抗碰撞能力强,防恶意篡改 |
| 接口参数签名 | HMAC-SHA256 | 带密钥的哈希,防伪造更可靠 |
| 用户密码存储 | bcrypt / scrypt / Argon2 | 慢哈希,暴力破解成本高 |
| 传输机密性 | TLS / AES,与MD5无关 | MD5不提供保密性 |
这里面特别要提醒的是密码存储。如果你现在还在用md5(password)存用户密码,赶紧计划升级。即便加了盐,MD5的高性能也让它不适合口令保护,专业密码哈希算法慢几个数量级,对正常用户影响很小,对攻击者却是致命的成本提升。有时候一个典型的案例:攻击者拿到数据库后,用同一个GPU设备破解MD5加盐密码的速度,比破解bcrypt密码要快好几个数量级。这已经不只是理论风险,而是真实攻防中屡见不鲜的场景。
5.4 我已经存了MD5密码,怎么迁移
很多人看到这里会问:遗留系统已经用MD5存了几百万用户密码,总不能直接清库吧?直接清库当然不行,但迁移并没有想象中那么可怕,常见的做法是逐步升级。
第一种方案是“登录时再哈希”。用户登录时先取数据库中的旧MD5值做校验,如果匹配,说明用户这次输入的明文是对的,那么立刻用bcrypt对该明文重新哈希,并更新到数据库。这样每一次用户主动登录,都会悄无声息地完成一次哈希算法迁移。长时间不登录的残留账号,可以配合定期强制重置密码来覆盖。
第二种方案是“双字段过渡”。在用户表里同时保留旧哈希和新哈希字段,新用户直接存bcrypt,老用户仍用MD5校验。在某个版本周期后,把所有还没登录过的老用户标记为“需要重置密码”,完成彻底切换。
无论哪种方案,都不要尝试“解密MD5再重新加密”,这既做不到也没必要。正确思路是拿到用户输入的明文后再做新哈希,明文只在登录那一刻存在,这就是哈希存储的珍贵之处。
6. 关于MD5,我踩过的坑和一些实在的建议
6.1 把密码字段叫“encrypted_password”,结果被审计追着问
年轻时候写过一套后台系统,用户表里的字段叫encrypted_password,存的是MD5。后来安全团队做扫描,直接把这条记录列成“密码使用可逆加密算法存储”,要求限期整改。我有苦说不出:我的代码根本没有解密逻辑,MD5也不可逆,但字段名和命名习惯让人产生了误判。
那次之后我养成了一个习惯:涉及哈希值存储的字段,一律叫password_hash、token_hash一类的名字,绝不叫encrypted。这不是虚伪,而是降低沟通成本。安全审计人员、新来的同事、第三方评测机构,第一眼看到字段名就能猜到存储方式,不容易误判。也正因为这个命名,很多时候能避免一场无谓的争论。
6.2 用MD5做接口签名,却没做防重放
另一个典型的坑:给接口加MD5签名,以为万事大吉,结果上线第二天就被业务方反馈“重复请求产生重复订单”。原因是攻击者虽然没有appSecret,但他可以把完整的HTTP请求原封不动地重放一遍,服务端验证签名时发现参数没变、签名合法,于是又执行了一次下单逻辑。
MD5签名能防篡改,但防不了重放。要解决这个问题,必须在签名参数里加入时效因素。最常见的是加时间戳,服务端只接受一定时间窗口内的请求;再进一步可以加nonce随机数,服务端维护一个已使用nonce的过期集合,同一个nonce只能使用一次。这样即使请求被重放,也会因为时间戳过期或nonce重复而被拒绝。设计接口签名方案时,这句话应该刻在脑子里:签名只保证“没被改过”,不保证“是本人第一次发”。
6.3 工具习惯:算哈希之前先确定字节流
最后分享一个小习惯,也是我栽过跟头之后的肌肉记忆:凡是做文件校验,先明确两件事,一是文件是二进制还是文本,二是双方是否基于同一份原始字节流。
很多人在本地Windows记事本里保存一个txt文件,然后拿去跟Linux上的同一份文件比MD5,发现不一样。原因可能就是Windows记事本默认给UTF-8文件加了BOM头,而Linux下编辑的文件没有BOM。又或者是因为Windows换行符是\r\n,Linux是\n,文本模式传输时自动转换,字节流已经彻底变了。校验的是字节流,不是“肉眼看起来一样的东西”。
在写代码时,我总会刻意用二进制模式读取文件,比如Python的open(path, "rb"),这样程序不会替我做任何隐式转换。在写接口签名时,拼接字符串后也统一用UTF-8编码再取MD5,避免因为默认字符集不同导致两边签名对不上。这些细节看着小,真出了线上问题,排查起来一晚上就没了。
关于MD5,我的看法是:它是一个有历史地位的算法,但也是一个被误解最多的算法。它不是加密,不可解密,但可以被碰撞、被查库、被暴力猜解。理解它,才能知道什么时候可以放心用,什么时候必须果断换。希望这篇文章能帮你把这些事理清楚。