从哈希字符串识别哈希方式的实务步骤

· · 哈希, 安全, 密码, 现有资产利用, 技术调查

在日志或数据库中看到类似 5f4dcc3b5aa765d61d8327deb882cf99$2b$12$... 这样的字符串,想要判断”这到底是什么哈希”的场景相当多。在既有系统迁移、认证方式调查、日志分析、与其他公司系统对接等工作中,卡在这一步的情况并不少见。

但这里危险的是 仅凭长度就立即下判断。看到 64 位的十六进制字符串就断言”这是 SHA-256”未免过于草率。SHA3-256、SHA-512/256、BLAKE2s-256、BLAKE3 的默认 32 字节输出也可能是同样的长度。反过来,像 $2b$$argon2id$ 这样 连接头(prefix)和参数都包含在内的保存格式,仅凭字符串本身就能以相当高的精度进行判断。

本文中,”hash” 一词的用法比较宽泛,不仅包括 MD5 / SHA-2 / SHA-3 这类消息摘要,也包括 bcrypt / scrypt / Argon2 / PBKDF2 这类 用于密码保存的字符串表示。 内容基于 2026 年 4 月时点公开的 RFC、NIST、Linux crypt(5)、Apache、Django、Spring Security 等官方资料整理而成。

目录

  1. 先说结论
  2. 一览判定表
  3. 实务中的判定步骤
  4. 常见的误判
  5. 需要 100% 确定时的确认顺序
  6. 总结
  7. 与本主题相关的服务
  8. 参考资料

1. 先说结论

先简要总结一下结论。

  • 带有前缀或分隔符的保存格式,仅凭字符串本身就相当容易确定。 例如:$argon2id$...$2b$...$5$...$6$...{SHA}...pbkdf2_sha256$...

  • 仅有纯粹的十六进制字符串或 Base64 时,通常只能做到「缩小候选范围」。 例如:32 位十六进制 = 可能是 MD5,但也可能来自 MD4 / NTLM

  • 字符种类与长度同样重要。 如果含有 + / =,就像是 RFC 4648 的 Base64;如果含有 . 且以 $ 分隔,则更像是 crypt(3) 系列——这样的判断方法很有效。

  • 如果想 100% 确定,就需要结合上下文。 是来自 /etc/shadow,还是 .htpasswd,还是 Django 的 auth_user,或是 Spring Security,情况都不一样。

归根结底,「仅凭字符串就能确定的方式」和「仅凭字符串只能缩小到候选群的方式」是两件不同的事。 只要把这一点区分清楚,调查的进展方式就会大不一样。

2. 一览判定表

2.1 通过前缀或格式标记几乎可以确定的类型

表格中「判定强度」一栏含义如下。

  • :仅凭字符串几乎可以确定
  • :候选范围可以大幅缩小,但需要留意实现上的差异
  • :仅凭长度或外观无法断定
外观特征 首先怀疑的方式 判定强度 补充说明 示例
$argon2id$... Argon2id PHC string format。后面经常跟着 v=, m=, t=, p= $argon2id$v=19$m=65536,t=3,p=4$MDEyMzQ1Njc4OWFiY2RlZg$uKZLaN6muIyoyIYr5waqw3y+zaDbe9aLSPj6Ln/rbz4
$argon2i$... Argon2i 同上 $argon2i$v=19$m=65536,t=3,p=4$MDEyMzQ1Njc4OWFiY2RlZg$Kx1koF/7n8EytGJYTS5krh+ag+FlG5ksM4xOsjOSDvo
$argon2d$... Argon2d 同上 $argon2d$v=19$m=65536,t=3,p=4$MDEyMzQ1Njc4OWFiY2RlZg$HLIGA+T1bwK8akx3LGOco+Df+PvxX6cIXhycO7O7t6c
$2a$... / $2b$... / $2y$... bcrypt 两位 cost 值 + crypt 系字符集 $2b$12$9YQ2u/e5Y/ArOnG.gJKxK.0makLATcYLP1q.Nsabzrw7XErYCfoYO
$1$... md5crypt Unix 系 MD5 密码保存格式 $1$vA7mQ9xZ$Erz32JUFnZ9991KdU5.N3.
$5$... sha256crypt 不是普通的 SHA-256 $5$rounds=5000$N3v8Kx2Lq9Rt$uOTla5GAHaRH2aHlUSjkrZUBCuFiahQZ36O/seB39r3
$6$... sha512crypt 不是普通的 SHA-512 $6$rounds=5000$N3v8Kx2Lq9Rt$6LUcSUAELX3aC/.60pTB.TFLTQi1mOGRCwKqNCqtRSaXjorxj01HJ9oNni97Kci1uDt7a/Kn4t3OS20Dw/.vi1
$7$... scrypt (crypt 系) 常见于 Linux crypt(5) 系实现 $7$CU..../....k2XAnEHBqQ1Ct2aMXFKNa/$y3Q0e/UlCHacIGWQshgvvz6UIbP.BCja.5BfVWP2Ml8
$y$... yescrypt 常见于较新的 Linux 系统 $y$j9T$k2XAnEHBqQ1Ct2aMXFKNa/$OVYXzjlkiQpWT/F1CUE0JrvV4phLY8FB.ofDttnrSQ7
$apr1$... Apache APR1-MD5 常见于 .htpasswd $apr1$vA7mQ9xZ$ZE64.ohiyK11sPZmtnJZQ.
{SHA}... SHA-1 digest 的 Base64 表示 常见于 Apache / LDAP 系 {SHA}VBPuJHI7uixaa6LQGWx4s+5GKNE=
{SSHA}... salted SHA-1 LDAP 系 {SSHA}/OczD0GNNkOAUPbYhA3L9fjmcyBCbHVlTWVzYTQyIQ==
{MD5}... / {SMD5}... MD5 / salted MD5 LDAP 系 {MD5}X03MO1qnZdYdgyfeuILPmQ==
{SMD5}fOn1rOv4ZH0OrO/KT9H0fEJsdWVNZXNhNDIh
pbkdf2_sha256$... PBKDF2-HMAC-SHA256 中〜强 Django 等实现会在前面加上格式名称 pbkdf2_sha256$600000$N3v8Kx2Lq9Rt$CLxGB+zTiV1IdOt2y4m9JpaAONzHuRTOd96xKQwRQAs
{bcrypt}$2b$... bcrypt 带有 Spring Security 的 {id} 包装 {bcrypt}$2b$12$9YQ2u/e5Y/ArOnG.gJKxK.0makLATcYLP1q.Nsabzrw7XErYCfoYO
{pbkdf2}... / {scrypt}... 带实现标签的方式 中〜强 Spring Security 等,关键是识别包装格式而非算法本体 {pbkdf2}sha256$600000$Qmx1ZU1lc2E0MiE$4eNuai1qNkgs1kXz3+tBUMzAexVsSUz9SrQKEhbk0Cw
{scrypt}ln=14,r=8,p=1$Qmx1ZU1lc2E0MiE$xAgBRhXbMtHB1UHUR0br5bI+1XdXWKbwauiFv5VRQBY

这个表的要点是,开头几个字符具有含义的格式,判定力就强。 尤其是以 $...$ 分隔的字符串,很有可能属于 Unix crypt(3) / MCF / PHC 系列,与看长度相比,先看 prefix 会更快

2.2 通过纯十六进制 / Base64 的长度缩小候选范围的表格

这是用于查看 没有 prefix 的「纯 digest 字符串」 时使用的表格。 如果混有 :- 或空格,应先去掉这些分隔符再计算长度。

原始字节长度 十六进制字符数 Base64 字符数 (= 有/无) 主要候选 示例
4 8 8 / 6 CRC32 等校验和 cbf43926
16 32 24 / 22 MD5、MD4、NTLM 系 5f4dcc3b5aa765d61d8327deb882cf99
20 40 28 / 27 SHA-1、RIPEMD-160 da39a3ee5e6b4b0d3255bfef95601890afd80709
28 56 40 / 38 SHA-224、SHA-512/224、SHA3-224 d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f
32 64 44 / 43 SHA-256、SHA-512/256、SHA3-256、BLAKE2s-256、BLAKE3 的默认 32 字节输出 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
48 96 64 / 64 SHA-384、SHA3-384、BLAKE2b-384 38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b
64 128 88 / 86 SHA-512、SHA3-512、BLAKE2b-512、Whirlpool cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e

这里重要的是,即便长度一致,方式也不会因此唯一确定。 尤其是 32 / 64 / 128 位的十六进制候选较多,仅凭这一点断定很容易出错。

2.3 容易混淆的典型示例

字符串的外观 常见的草率判断 实际的正确看法 示例
5f4dcc3b5aa765d61d8327deb882cf99 断定为 MD5 看起来像 MD5,但也可能来自 MD4 / NTLM 系,或是应用自身特有的 MD5 用法 8846f7eaee8fb117ad06bdd830b7586c
2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 这样的 64 hex 断定为 SHA-256 SHA-256 确实是候选之一,但也可能是 SHA3-256 / SHA-512/256 / BLAKE2s-256 / BLAKE3 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
$6$rounds=5000$salt$hash SHA-512 的十六进制表示 并非如此,而是名为 sha512crypt 的密码哈希字符串 $6$rounds=5000$N3v8Kx2Lq9Rt$6LUcSUAELX3aC/.60pTB.TFLTQi1mOGRCwKqNCqtRSaXjorxj01HJ9oNni97Kci1uDt7a/Kn4t3OS20Dw/.vi1
{SHA}VBPuJHI7uixaa6LQGWx4s+5GKNE= 某种「SHA」 在 Apache / LDAP 系中,多数指的是 经过 Base64 编码的 SHA-1 digest {SHA}VBPuJHI7uixaa6LQGWx4s+5GKNE=
{bcrypt}$2b$12$... 叫做 {bcrypt} 的独立方式 带有 Spring Security 包装的 bcrypt {bcrypt}$2b$12$9YQ2u/e5Y/ArOnG.gJKxK.0makLATcYLP1q.Nsabzrw7XErYCfoYO

3. 实务中的判定步骤

接下来按顺序整理实际的判断方法。 推荐的顺序是 prefix → 分隔符 → 字符种类 → 长度 → 上下文

3.1 首先看开头的符号

仅凭开头的 1 到 10 个字符左右,就能大幅缩小范围。

  • $argon2id$ / $argon2i$ / $argon2d$ 高度怀疑是 Argon2 的 PHC string format。查看 2.1 中「示例」一列,可以更容易追踪其组成部分。

  • $2a$ / $2b$ / $2y$ 高度怀疑是 bcrypt。

  • $1$ / $5$ / $6$ / $7$ / $y$ 怀疑是 Unix crypt(3) 系列的密码哈希。

  • {SHA} / {SSHA} / {MD5} / {SMD5} 怀疑是 LDAP / Apache 系的表示方式。

  • {bcrypt} / {pbkdf2} / {scrypt} 怀疑是类似 Spring Security 的 带实现标签的保存格式

这里的技巧是,不仅要看「算法本体」,还要看「保存格式」。 例如 $6$ 并不是「SHA-512 的 digest」,而是「使用 SHA-512 的密码哈希字符串」。如果在这里搞错,后续的调查就会出现偏差。

3.2 查看分隔符的数量

接下来,查看 $, :, {}, ,, = 这类分隔符。

  • 存在多个 $ 怀疑是同时携带参数、salt、hash 的格式。典型代表有 Argon2、bcrypt、sha256crypt、sha512crypt 等。

  • {name} 开头 怀疑是 LDAP / Spring Security 等 明确标注方式名称的包装格式

  • 类似 algo:salt:hashalgo$iterations$salt$hash 的形式 怀疑是框架或应用特有的格式。Django 的 pbkdf2_sha256$iterations$salt$hash 就是典型代表。

分隔符越多的字符串,越容易确定方式。 反过来,如果只是一整块纯粹的十六进制或 Base64,判断就会相当模糊。

3.3 查看字符种类

字符种类与长度同样重要。

十六进制表示

如果只由 [0-9a-fA-F] 组成,首先怀疑是十六进制表示。 此时 字符数 ÷ 2 = 原始字节长度

  • 32 hex → 16 bytes
  • 40 hex → 20 bytes
  • 64 hex → 32 bytes
  • 128 hex → 64 bytes

RFC 4648 的 Base64 / Base64url

如果含有 + / =,首先怀疑是普通的 Base64。 如果含有 - _,则怀疑是 Base64url。 由于 padding 的 = 有时会被省略,因此会出现类似 43 / 4486 / 88 这样「两种长度皆有可能」的情况。

crypt 系的 radix64

如果出现 ./,并且以 $...$ 分隔,那么与怀疑是普通的 Base64 相比,怀疑是 crypt 系字符集 更为自然。 bcrypt、sha256crypt、sha512crypt、md5crypt、yescrypt、scrypt 等都使用这一系统的字符集。

这一点看似不起眼,但相当管用。 如果因为 含有 . 就认为是「损坏的 Base64」,很容易漏掉 bcrypt 或 crypt(3) 系列。

3.4 计算长度

看完字符种类之后,接下来是长度。 思路很简单:

  • 如果是十六进制,则 原始字节长度 = 字符数 / 2
  • 如果是 Base64,则 字符数 ≒ 4 × ceil(原始字节长度 / 3) 不过如果省略了 padding 的 =,会缩短 0~2 个字符

在这一阶段缩小候选范围。 不过,不要像看到 64 位十六进制就断定是 SHA-256 这样跳跃式地下结论,这样更安全。

3.5 通过上下文来确定

最后起作用的是上下文。这一步能让判断接近 100% 确定。

  • 位于 /etc/shadow 中 怀疑是 $y$, $6$, $5$, $1$ 等 Linux 密码哈希格式

  • 位于 .htpasswd 中 怀疑是 $apr1$, {SHA}, bcrypt 等 Apache 系格式

  • 位于 Django 的配置或 auth_user.password 中 怀疑是 pbkdf2_sha256$...argon2$... 这类 Django 格式

  • 位于 Spring Security 的认证表中 怀疑是 {bcrypt}...{pbkdf2}... 这类带 {id} 的格式

  • 出现在 SMB / AD 集成相关场景中的 32 hex 强烈考虑是 NTLM / MD4 系

在实务中,与只看字符串本身相比,查看保存源的产品、框架、配置文件名往往更快,这样的场景并不少见。

4. 常见的误判

4.1 断定「64 hex = SHA-256」

这是相当常见的情况。 SHA-256 当然是有力候选,但拥有同样 32-byte 输出的方式并不止一种。SHA3-256、SHA-512/256、BLAKE2s-256、BLAKE3 的默认输出也是同样的长度。

长度只是用来构建候选群的材料,并不是确定性的证据。

4.2 把 $6$ 误解为普通的 SHA-512

$6$... 是 sha512crypt 的前缀。 它不是「SHA-512 的 hex digest」,而是 包含 salt 与 rounds 的密码哈希字符串

同样地,

  • $5$ 是 sha256crypt
  • $1$ 是 md5crypt

只要带有 prefix,就已经不再是「单纯的 digest」了。

4.3 把 {SHA} 当作「SHA-256 或 SHA-512 中的某一种」

在 Apache 或 LDAP 的语境中,{SHA} 并不是笼统地指「SHA 系列」。 多数情况下,它指的是 经过 Base64 编码的 SHA-1 digest{SSHA} 则是加盐的 SHA-1。

如果只凭 {SHA} 的外观就笼统地当作「某种 SHA」来处理,验证代码或迁移处理就会出错。

4.4 把密码哈希与内容哈希混为一谈

即便同为「哈希字符串」,用途也各不相同。

  • 用于文件完整性校验的 digest
  • 用于 API 签名的 digest
  • 用于密码保存的 hash / KDF 字符串

这三者外观相似,但处理方式各不相同。 尤其是密码哈希,字符串中往往包含 salt、rounds、内存开销(memory cost)、并行度(parallelism)等信息,用「直接比较原始 digest」的思路是看不出来的

4.5 忽略 XOF 与可变长度 digest

SHAKE128 / SHAKE256 属于 XOF(可扩展输出函数),可以自由选择输出长度。 BLAKE2 也可以更改 digest 长度,BLAKE3 同样具备可扩展输出(extendable output)。

也就是说,「因为是这个长度所以是这种方式」 这类推断如果过度依赖固定长度的经典 digest 前提,就会判断失误。

5. 需要 100% 确定时的确认顺序

在迁移或认证对接中,最终需要做到 确定。此时按以下顺序查看,可以减少出错。

5.1 确定保存来源

首先确定该字符串来自何处。

  • 是否来自 Linux 的 shadow
  • 是否来自 Apache / Nginx 的 basic auth
  • 是否来自 LDAP
  • 是否来自 Django / Spring Security
  • 是否来自自研应用的数据库

与单纯看字符串相比,保存来源的规范 往往更具说服力。

5.2 通过官方文档查询「保存格式」

接下来要查的不是算法名称,而是 保存格式

  • Django password format
  • Spring Security password storage format
  • crypt(5) sha512crypt format
  • Apache htpasswd password formats

类似上面这样,把 format / storage / encoding 当作关键词,会更容易查到。

5.3 如果有已知明文,就用候选方式实际进行核对

如果有测试账号或已知的明文,最快的方法是用候选方式实际计算并进行比较。 此时对于密码哈希,需要 从字符串中提取出 salt 与 rounds 后重新计算

5.4 确认实现代码或配置

如果调查对象是自家系统,最终查看代码或配置是最可靠的方法。

  • 使用的库
  • 框架的配置
  • 生成时的选项
  • 输出编码方式(hex / Base64 / Base64url / crypt 字符集)

只要查看这些内容,通常都能得出结论。

5.5 为将来考虑,采用带方式标签的保存方式

如果是从头开始设计的一方,选择 把方式信息嵌入字符串中的格式,将来的迁移会轻松很多。

  • Argon2 的 PHC string format
  • Spring Security 的 {id}encodedPassword
  • Django 的 algo$iterations$salt$hash
  • Unix crypt(3) 系的带 prefix 格式

这样处理之后,后来查看的人就不容易感到困惑。 反过来,只把「单纯的 64 hex」存入数据库的设计,对未来的自己并不友好。

6. 总结

从哈希值的字符串表示中识别方式时,按以下顺序查看会更容易整理清楚。

  1. 是否存在 prefix
  2. 分隔符是什么
  3. 字符集是什么
  4. 长度相当于多少字节
  5. 保存来源的上下文是什么

最重要的是以下两点:

  • 带前缀的保存格式相当容易确定
  • 纯粹的十六进制 / Base64 大多只能缩小到候选群

因此,在实务中的判断可以这样概括:

  • 如果是 $argon2id$..., $2b$..., $6$..., {SHA}..., pbkdf2_sha256$...,仅凭字符串就能大幅推进判断
  • 如果只有 32 / 40 / 64 / 128 位的十六进制,应视为「缩小候选范围」,而非直接断定
  • 如果确实需要确定结果,就要查看保存来源的产品、配置乃至实现

按这个顺序查看,调查速度会大幅提升。 反过来,如果仅凭长度就草率下结论,就会在不知不觉间走上一段弯路。

7. 与本主题相关的服务

技术咨询与设计评审

在识别现有数据库中残留密码哈希的方式、认证基础设施的迁移、Windows / Web 混合系统的日志调查等工作中,不能只看字符串的外观,还需要梳理清楚保存来源的实现与迁移方针。从方式识别到迁移设计一并考虑,更容易减少事故。

故障排查与原因分析

「因为不知道这个字符串是什么,导致验证无法推进」的调查并不少见。厘清方式究竟是由日志、配置文件、数据库结构,还是应用实现中的哪个环节决定的,可以大大加快原因定位的速度。

8. 参考资料

  1. RFC 1321 - The MD5 Message-Digest Algorithm
  2. NIST FIPS 180-4 - Secure Hash Standard (SHA-1, SHA-2, SHA-512/224, SHA-512/256)
  3. NIST FIPS 202 - SHA-3 Standard: Permutation-Based Hash and Extendable-Output Functions
  4. PHC string format specification
  5. Argon2 reference implementation
  6. RFC 7693 - The BLAKE2 Cryptographic Hash and Message Authentication Code (MAC)
  7. BLAKE3 C README - default output length and extendable output
  8. crypt(5) - prefixes and hashed passphrase formats
  9. Apache HTTP Server 2.4 - Password Formats
  10. slappasswd(8) - RFC 2307 schemes such as {SHA} and {SSHA}
  11. Django documentation - example of pbkdf2_sha256$...
  12. Spring Security - DelegatingPasswordEncoder storage format {id}encodedPassword

共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。

常见问题

汇总了咨询这一主题时常见的问题。

从哈希字符串识别方式时,应该按什么顺序查看?
按 prefix → 分隔符 → 字符种类 → 长度 → 上下文的顺序查看,会更容易整理清楚。开头几个字符具有含义的格式比较容易确定:$argon2id$ 可强烈怀疑是 Argon2,$2b$ 是 bcrypt,$5$ 是 sha256crypt,$6$ 是 sha512crypt。反过来,没有 prefix 的纯十六进制或 Base64 往往只能缩小候选范围,最终需要确认保存来源的产品、框架与配置才能确定。
64 位的十六进制字符串可以断定为 SHA-256 吗?
断定是危险的。SHA-256 确实是有力候选,但拥有同样 32 字节输出的方式还有 SHA3-256、SHA-512/256、BLAKE2s-256、BLAKE3 的默认输出等,仅凭长度无法区分。长度只是用来构建候选群的材料,并不是确定性的证据。如果需要确定,就要进一步确认保存来源的规范、官方文档中的保存格式、与已知明文的核对,乃至实现代码或配置。
以 $6$ 开头的字符串是 SHA-512 的哈希吗?
不是。$6$ 是 sha512crypt 这一 Unix 系密码保存格式的 prefix,并不是 SHA-512 本身的十六进制 digest,而是包含 salt 与 rounds 的密码哈希字符串。同样地,$5$ 是 sha256crypt,$1$ 是 md5crypt。只要带有 prefix,就已经不是「单纯的 digest」了,如果在这里搞错,迁移或验证代码的实现就会出现偏差。
从字符种类可以判断出什么?
字符种类是与长度同样重要的线索。如果字符只落在 0-9a-fA-F 的范围内,就是十六进制表示,字符数的一半即为原始字节长度。如果含有 + / =,则怀疑是 RFC 4648 的 Base64;含有 - 或 _,则怀疑是 Base64url。如果出现了点号和斜杠,并以 $ 分隔,那么与怀疑是普通的 Base64 相比,怀疑是 bcrypt、sha512crypt 等 crypt 系字符集会更自然。如果把带点号的字符串误认为「损坏的 Base64」,就会漏掉 crypt 系格式。

作者简介

本文作者的个人简介页面。

Go Komura

小村软件有限公司 代表

以 Windows 软件开发、技术咨询与故障排查为中心,擅长难以复现的故障调查,以及既有资产仍在运行的项目。

返回博客列表