Skip to content
返回博客
安全

MD5 能转成 SHA-256 吗?为什么不可能,以及正确做法

MD5 哈希无法转成 SHA-256,因为哈希是单向的。看清哈希转换器页面实际在做什么,以及四类真实需求各自的正解。免费在线工具,数据不离开浏览器。

11 分钟

MD5 能转成 SHA-256 吗?为什么不可能,以及正确做法

网上有大量页面承诺能做这件事,其中一些看上去还挺像那么回事。但它们宣传的这个操作并不存在。搞清楚为什么不存在,你才知道接下来该做什么。

简短答案:MD5 转不成 SHA-256,SHA-256 也转不回 MD5

MD5 哈希无法转换成 SHA-256。MD5 摘要是 128 位的输出,它并不包含产生自己的那段输入,而 SHA-256 必须拿到那段输入才能算出任何东西。原始数据一旦不在手上,中间那一步就永久缺失,没有任何工具或库能把它补回来。

同一段输入在三种算法下的结果:

算法hello 的摘要十六进制字符数位数
MD55d41402abc4b2a76b9719d911017c59232128
SHA-1aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d40160
SHA-2562cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b982464256

把这三个摘要当成字符串看,它们之间没有共同前缀,也没有共同子串,更谈不上什么算术关系。唯一把它们联系起来的是 hello 这个词,而三者谁都没把它带在身上。想亲眼看着它们随输入各自独立变化,可以用 MD5 哈希生成器SHA-256 哈希生成器 自己算一遍。

反方向也一样。从 SHA-256 转 MD5 之所以失败,原因完全相同:两个函数都是故意销毁信息,所以任何一个摘要都没法倒推回另一个函数所需要的那段输入。

剩下的才是有用的问题。没人是出于研究哈希理论的好奇心去搜这个词的,那你原本想做的到底是什么?实践中无非四件事,每一件都有真正的答案:

  1. 校验下载文件或比对校验和(checksum)
  2. 把密码库从 MD5 迁走
  3. 还原哈希背后的原始输入
  4. 用另一种算法对同一份数据重新哈希

先看那些承诺「转换」的页面到底在做什么。认出这个套路,你就不会再去相信它们的输出。

「MD5 转 SHA-256 转换器」页面实际在做什么

只有三种可能,没有一种是转换。

它向你索要原文。你粘贴一段字符串,页面同时给出两个摘要,然后宣传文案把这称作转换。这只是一个挂着误导性标签的哈希生成器:什么都没被转换,输入只是被哈希了两次。

它在查表。页面维护着一张「输入—摘要」对照表,数据来自各种字典和泄露的凭据库。粘贴一个原文恰好在表里的 MD5,页面就能找出原文,再用 SHA-256 算一遍结果给你看。这一招对 password123456 以及一长串常见短字符串有效,对其他任何东西则会静默失败,这也是这类页面经常返回一片空白又不给任何解释的原因。

它什么都没做。相当多的页面只是把你的输入原样回显,或者对你粘贴的那串十六进制算个 SHA-256,却暗示这就是你原始数据的 SHA-256。这两个值并不相同,后文讲双重哈希的那一节会精确展示差在哪里。

检验方法很快:只给页面一个 32 字符的十六进制串,别的什么都不给,然后问一句:它凭什么可能知道原文?如果它照样吐出一个 64 字符的结果并称之为转换,那它要么在查表,要么在骗你。一个从不索要原始数据的哈希转换器,除了碰巧蒙对之外没有任何办法给出正确答案。

这个说法之所以能流传下来,是因为它听上去很像另一件确实可行的事。

哈希、编码与加密:三者中只有两个可逆

开发者往往在同一周里碰上这三样,然后把它们塞进脑子里同一个抽屉。可它们的行为完全不同:

操作例子是否可逆需要什么
编码(encoding)Base64、十六进制、URL 编码永远可逆什么都不需要
加密(encryption)AES、RSA可逆密钥
哈希(hashing)MD5、SHA-1、SHA-256永远不可逆给什么都没用

培养出错误直觉的是 Base64。任何人都能把 Base64 字符串解码还原成字节,不需要密钥,也不需要授权;把 Base64 转成十六进制更是天天都在做的正当操作。两者不过是同一串字节的两种写法。见过这种操作之后,「MD5 转 SHA-256」听上去就像是同一类换写法。

但它不是。编码保留输入的每一个比特,加密把输入藏在密钥后面同样完整保留,哈希则把其中绝大部分直接扔掉。不管你喂进去的是一个密码还是一个 40 GB 的磁盘镜像,MD5 摘要都是 16 字节。那 40 GB 并没有被巧妙地藏到哪里去,它就是没了。想深入了解哈希与加密的区别,可以看 MD5 哈希生成器的 FAQ,不过这里一句话就够:没有密钥,所以根本没有什么可以解密的。

「MD5 已被攻破」不等于「MD5 可以反解」

这一点连不少态度诚实的文章都会写错。人们会从「MD5 好多年前就被攻破了」直接推到「那现在总该有人能把它还原了吧」。

这里牵涉到两种不同的攻击:

  • 碰撞攻击(collision attack):找出两段不同的输入,让它们产生相同的摘要。攻击者两段输入都自己挑,也不在乎它们具体是什么。
  • 原像攻击(preimage attack):从一个摘要出发,还原出能产生它的某段输入。能让你把 MD5 转成 SHA-256 的正是这一种,因为它会把可以喂给 SHA-256 的东西还给你。

MD5 的抗碰撞性在 2004 年被攻破,今天在普通硬件上几秒钟就能构造出一次碰撞。这就是 MD5 不适合用于签名、证书以及任何攻击者能施加影响的场景的原因。

MD5 的抗原像性并没有被攻破。已知最好的原像攻击至今仍停留在理论层面,复杂度为 2^123.4Sasaki 与 Aoki,2009)。这比暴力破解只快了一丝一毫,在任何有实际意义的尺度上都遥不可及。无论 2004 年还是现在,都没有人能反解任意的 MD5 摘要。

所以这两句话可以同时成立,彼此并不矛盾:MD5 已被攻破,MD5 仍然不可反解。被攻破这件事动摇不了上面的结论。如果你要做的其实是给新系统在两种算法之间选型,而不是在它们之间转换,那么 MD5 与 SHA-256 对比 专门讲了这个决策。

你真正想做的:四类任务与各自的正解

你实际在做的事真正的答案
校验下载文件或比对校验和拿文件用你需要的算法重算一遍
迁移 MD5 密码哈希用 bcrypt 包一层,登录时逐个升级
还原原始输入那叫破解不叫转换,成败取决于熵
用另一种算法重新哈希同一份数据回到原始字节

任务一:比对校验和或验证下载文件

重算,不要转换。文件就躺在你的磁盘上,这意味着摘要丢掉的那样东西你手里有。直接对它跑你需要的那个算法:

# GNU coreutils
md5sum    ubuntu-24.04-desktop-amd64.iso
sha256sum ubuntu-24.04-desktop-amd64.iso

# macOS
md5           ubuntu-24.04-desktop-amd64.iso
shasum -a 256 ubuntu-24.04-desktop-amd64.iso

人们真正卡住的地方是:发布方只给了 MD5,而你所在团队的安全策略要求 SHA-256。就算转换真的可行,转换出来的东西也证明不了任何事:从另一个摘要推导出来的摘要,并不构成发布方对这个文件的任何声明。你需要的是发布方签署过的 SHA-256。如果对方不提供,用 SHA-256 哈希生成器 自己算一个仍然有价值:它是一个稳定的指纹,你可以把它固定下来,在各个镜像之间比对,日后也能靠它发现文件被改过。它只是替代不了发布方的背书。

任务二:把密码库从 MD5 迁走

这是四件事里风险最高的一件,好在解法已经很成熟:先把旧哈希包一层,再在登录时对每个账号做透明升级。用一个批处理任务给所有存量 MD5 哈希套上 bcrypt,此后登录流程先对提交的密码算 MD5,再拿这个值去和 bcrypt 记录比对。

不少旧建议在这里恰好说反了。把 MD5 包进 SHA-256 并不能让密码存储变安全。 SHA-256 是快哈希,和 MD5 属于同一类问题,GPU 扫过这两种哈希的速度一样快。那些让你用数据库内置 SHA2() 函数来解决问题的建议,解的是错误的问题。目标应该是一个刻意设计得很慢的哈希:bcrypt、scrypt 或 Argon2。要存 bcrypt(md5(password)),绝不要存 sha256(md5(password))。三者之间怎么选,bcrypt vs Argon2 vs scrypt 对比 有更细的比较;Bcrypt 生成器与验证器 可以让你在定下 cost 参数之前先试几组。

「密码的 MD5」这句话本身有歧义,两种理解会得出不同的结果,迁移真正翻车的地方就在这里:

const { createHash } = require('node:crypto');

const md5Hex = createHash('md5').update('hello').digest('hex');
const md5Raw = createHash('md5').update('hello').digest();   // the same value as 16 bytes

createHash('sha256').update(md5Hex).digest('hex');
// 4914e23374bb211e3dca0df7636fefffc7fedd94f1340ae81c7d6c07b7113e9b

createHash('sha256').update(md5Raw).digest('hex');
// 88e20f0abb88153e3f0a9683668ccb5b84ed771817dc448a2b73254ed02c8d8c

createHash('sha256').update('hello').digest('hex');
// 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824

三个结果,全都正确,全都不同。对那串 32 字符的十六进制做哈希得到一个值;对它所代表的 16 个原始字节做哈希得到另一个值;对原始密码做哈希又是第三个值。挑定一种约定,把它写下来,然后在迁移脚本和登录流程里用同一种。这里出错会一次性把所有用户锁在门外,而表现出来的症状看着像是 bcrypt 有 bug,不像是一个编码约定的问题。

对 bcrypt 包装来说,十六进制是更安全的约定。原始摘要字节里可能出现零字节,而好几个 bcrypt 实现把输入当作 C 字符串处理,读到零字节就停下。十六进制彻底避开了这个问题,而且 32 个字符远低于 bcrypt 的输入长度上限。大小写也要统一,因为 5D41402A...5d41402a... 在 bcrypt 眼里是两个不同的字符串,尽管它们是同一个摘要。

const bcrypt = require('bcrypt');
const { createHash } = require('node:crypto');

const legacyDigest = (password) =>
  createHash('md5').update(password, 'utf8').digest('hex');   // lowercase hex

// Batch job, run once: wrap each stored MD5, then drop the old column.
async function wrapRow(row) {
  return bcrypt.hash(row.md5_hash.toLowerCase(), 12);
}

// Login path
async function verify(password, row) {
  if (row.scheme === 'bcrypt') {
    return bcrypt.compare(password, row.hash);
  }
  const ok = await bcrypt.compare(legacyDigest(password), row.hash);
  if (ok) {
    const upgraded = await bcrypt.hash(password, 12);
    await saveCredential(row.id, { scheme: 'bcrypt', hash: upgraded });  // your DB write
  }
  return ok;
}

账号会随着各自的主人登录而逐步迁到纯 bcrypt。给掉队的账号定一个截止时间,过期就强制重置。

任务三:你想还原出原始输入

这件事最好挑明说,至少对自己挑明,因为它决定了该用什么工具、该抱什么预期。从摘要还原输入叫破解,不叫转换;能不能成功和摘要出自哪种算法毫无关系,只取决于输入本身。

短的、人挑出来的常见字符串很快就会失守。字典词、后面缀个数字的人名,以及任何在泄露库里出现过的内容,一次查表或一轮短暂的暴力尝试就能拿下。长的随机字符串则根本破不了,而且这两种情况之间的差距,不是多买几台机器能填平的。

彩虹表(rainbow table)常被笼统地提起。准确的说法是:彩虹表能还原某个输入,当且仅当这个输入没有加盐、并且已经在表里。两个条件缺一不可。加上按用户随机生成的盐,预计算好的表就一文不值了,因为攻击者得为每个盐单独建一张表。而如果被哈希的是一个 128 位的随机令牌,世上没有任何一张表会包含它。密码熵详解 用具体数字标出了这条界线在哪里。

如果那个输入是你自己的密码而你已经想不起来,就把这件事当成重置来处理,而不是找回。如果它属于别人,上面那段话就是能给出的全部诚实回答。

任务四:你需要用另一种算法哈希同一份数据(「md5 转 sha1 转换器」)

这和任务一是同一个问题,只是更一般。请求里是哪一对算法并不重要,因为答案永远是回到原始字节再哈希一次:

sha1sum   payload.bin
sha256sum payload.bin

你能找到的每一个「md5 转 sha1 转换器」,要么是在重新哈希,要么是在查表,原因和上面讲的完全一样。如果拿不到原始数据,这件事就是无解的,而这并不是现有工具能力不足。目标算法怎么选,可以参考 SHA-1 vs SHA-256 vs SHA-512 选择指南SHA-1 哈希生成器 是给那些至今仍要求 SHA-1 的遗留系统准备的,新系统不该再用它。

查表真正管用的那一种情况

有些类别的数据有人专门集中收集,并按各种常见摘要建好索引。恶意软件样本就是标准例子。VirusTotal 这类服务保存着样本本身,所以它们是从同一串字节算出了 MD5、SHA-1 和 SHA-256,并把三者归到同一条记录下。在那里用样本的 MD5 去搜,就能拿到它的 SHA-256。

这是一次数据库连表查询,不是转换。它之所以成立,是因为有人手里有原始文件,并用每种算法都算了一遍,也就是上文说的那个重算步骤,只不过由第三方提前做完了。条件很苛刻:对象必须已经在索引里,而且你得信任建索引的那一方。威胁情报(threat intelligence)的工作流每天都依赖这一点。但它对任意的 MD5 摘要什么都说明不了,对你自己那份数据的哈希也永远帮不上忙。

那些可疑的转换器页面用的也是这套机制。区别在于,恶意软件库会明确告诉你这是查表,并且把记录本身摊开给你看。

自己动手验一遍

要把这件事真正想通,最快的办法是看着同一段输入的两个摘要一起变化,却始终互不相干:

$ printf 'hello' | md5sum
5d41402abc4b2a76b9719d911017c592  -
$ printf 'hello' | sha256sum
2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824  -

改动其中一个字符,两个输出都会彻底改变,而且改变的方式彼此毫无关联。把同一段文本分别粘进 MD5 哈希生成器SHA-256 哈希生成器 并排看,效果最直观;如果你真正需要的是给 API 请求签名的带密钥摘要,那就用 HMAC 生成器。这三个工具都完全在你的浏览器里运行。

常见问题

MD5 能转成 SHA-256 吗?

不能。MD5 摘要里不含原始输入,而 SHA-256 必须拿到那段输入才能算出任何东西。任何声称能转换的页面,要么是在对你提供的文本重新哈希,要么是在一个预计算好的对照库里查这个值。

为什么那么多网站都提供「MD5 转 SHA-256 转换器」?

因为它们能在这个人们真的会搜的词上拿到排名。你点进去之后,页面要么向你索要原文(那就成了重新哈希而不是转换),要么返回一堆没用的东西。这个说法能一直流传,是因为它背后的误解太普遍了。

SHA-256 能转回 MD5 吗?

不能,而且方向不重要。两者都是单向函数,所以在没有原始输入的情况下,任何一个摘要都变不成另一个。提供反向转换的工具,用的是同样的查表或重新哈希的把戏。

MD5 都被攻破了,那不就意味着它可以被反解吗?

不。MD5 的抗碰撞性在 2004 年被攻破,意思是可以强行构造出两段共用同一摘要的输入。而反解一个摘要属于原像攻击,已知最好的方案至今仍要 2^123.4 次运算。用于签名它已经废了,但依然无法求逆。

怎么把一整个 MD5 密码哈希库迁移到更安全的方案?

在 MySQL 里,UPDATE users SET password = SHA2(password, 256) 这种一行搞定的写法是错的,它哈希的是已经存进去的 MD5 摘要而不是密码;正确做法仍是存 bcrypt(md5(password)),并通过同一层包装来验证,在每个账号下次登录成功时逐条升级。把 MD5 包进 SHA-256 没有用,因为两者都是快哈希,GPU 破解这两种哈希的速度一样快。

为什么双重哈希的结果和我预期的不一样?

对 MD5 的十六进制字符串做哈希,和对它的 16 个原始字节做哈希,得到的 SHA-256 输出不同。两个都是有效的,它们只是不同的输入而已。挑定一种约定,在迁移脚本和登录流程里一模一样地执行。

彩虹表能还原 MD5 哈希背后的输入吗?

只有在输入未加盐、且已经在表里的时候才行,实践中这意味着短字符串或常见字符串。长的随机输入没法用这种方式还原,而给每个用户加上独立的盐,可以彻底废掉预计算表。

有没有 MD5 转 SHA-1 转换器?

没有。同一条不可逆性对 MD5 转 SHA-1 和 MD5 转 SHA-256 一样成立,所以自称的转换器要么拿你贴进去的原文重新哈希,要么在预计算的对照表里查。原始输入还在,用 SHA-1 生成器 重算一遍即可;输入没了,谁也算不出来。

标签: md5 sha-256 hashing security checksum cryptography