Hash Identifier
按长度和格式识别哈希类型 — MD5、SHA-1、SHA-256 等
哈希字符串
什么是哈希识别?
哈希识别是确定某个摘要由哪种密码学哈希算法生成的过程。MD5、SHA-1、SHA-256 等密码学哈希函数各自产生固定长度的输出,而输出长度是识别未知哈希的主要依据。当你在数据库转储、配置文件或 API 响应中遇到十六进制字符串时,哈希识别工具会告诉你哪种算法最有可能生成了它。
每种哈希算法都将任意输入数据映射为固定长度的输出,称为摘要。MD5 始终产生 128 位(32 个十六进制字符),SHA-1 始终产生 160 位(40 个十六进制字符),SHA-256 始终产生 256 位(64 个十六进制字符)。这种确定性的输出长度使得在无需原始输入或哈希代码的情况下进行算法识别成为可能。
仅凭长度进行识别并不总是确定的。多种算法可能共享相同的输出大小——例如,SHA-256 和 SHA3-256 都产生 64 个字符的十六进制摘要。在这种情况下,哈希识别工具会按出现频率列出候选算法列表。来源系统、编码格式(十六进制还是 Base64)以及算法前缀(如 bcrypt 的 '$2b$')等上下文信息可进一步缩小范围。
为什么使用哈希识别工具?
未知哈希在安全审计、数据库迁移和取证分析中经常出现。哈希识别工具消除猜测,在数秒内指向正确的算法。
哈希识别工具的使用场景
哈希算法长度参考
下表列出了常见哈希算法对应的输出位数、十六进制字符数和原始字节数。这是哈希识别工具使用的主要查询表。当多种算法共享相同的十六进制长度时,需要额外的上下文信息加以区分。
| 算法 | 位数 | 十六进制字符数 | 字节数 | 备注 |
|---|---|---|---|---|
| MD5 | 128 | 32 | 16 | Broken — collisions trivial since 2004 |
| SHA-1 | 160 | 40 | 20 | Deprecated — SHAttered attack (2017) |
| SHA-224 | 224 | 56 | 28 | Truncated SHA-256; rarely used standalone |
| SHA-256 | 256 | 64 | 32 | Current standard; TLS, Git, Bitcoin |
| SHA-384 | 384 | 96 | 48 | Truncated SHA-512; CNSA Suite approved |
| SHA-512 | 512 | 128 | 64 | Maximum SHA-2 output; large-data hashing |
| SHA3-256 | 256 | 64 | 32 | Keccak-based; NIST alternative to SHA-2 |
| SHA3-512 | 512 | 128 | 64 | Keccak-based; highest SHA-3 strength |
| RIPEMD-160 | 160 | 40 | 20 | Used in Bitcoin address derivation |
| BLAKE2s | 256 | 64 | 32 | Faster than SHA-256; 256-bit output |
区分相同长度的哈希
部分十六进制长度对应多种算法。最常见的两种歧义是 64 字符哈希(SHA-256 与 SHA3-256)和 40 字符哈希(SHA-1 与 RIPEMD-160)。以下是在仅凭长度无法区分时的判断方法。
代码示例
以下是四种语言中通过十六进制长度识别哈希的可运行实现。每个函数都会验证十六进制编码,查找字符数,并返回所有匹配的算法。
function identifyHash(hex) {
const len = hex.length
const isHex = /^[0-9a-fA-F]+$/.test(hex)
if (!isHex) return ['Not a hex-encoded hash']
const map = {
32: ['MD5'],
40: ['SHA-1', 'RIPEMD-160'],
56: ['SHA-224', 'SHA3-224'],
64: ['SHA-256', 'SHA3-256', 'BLAKE2s'],
96: ['SHA-384', 'SHA3-384'],
128: ['SHA-512', 'SHA3-512', 'BLAKE2b'],
}
return map[len] || [`Unknown (${len} hex chars)`]
}
identifyHash('d41d8cd98f00b204e9800998ecf8427e')
// → ["MD5"]
identifyHash('e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855')
// → ["SHA-256", "SHA3-256", "BLAKE2s"]import re
HASH_LENGTHS = {
32: ['MD5'],
40: ['SHA-1', 'RIPEMD-160'],
56: ['SHA-224', 'SHA3-224'],
64: ['SHA-256', 'SHA3-256', 'BLAKE2s'],
96: ['SHA-384', 'SHA3-384'],
128: ['SHA-512', 'SHA3-512', 'BLAKE2b'],
}
def identify_hash(hex_string: str) -> list[str]:
hex_string = hex_string.strip()
if not re.fullmatch(r'[0-9a-fA-F]+', hex_string):
return ['Not a hex-encoded hash']
return HASH_LENGTHS.get(len(hex_string), [f'Unknown ({len(hex_string)} hex chars)'])
identify_hash('da39a3ee5e6b4b0d3255bfef95601890afd80709')
# → ['SHA-1', 'RIPEMD-160']
identify_hash('a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a')
# → ['SHA-256', 'SHA3-256', 'BLAKE2s']package main
import (
"fmt"
"regexp"
)
var hexPattern = regexp.MustCompile("^[0-9a-fA-F]+$")
var hashLengths = map[int][]string{
32: {"MD5"},
40: {"SHA-1", "RIPEMD-160"},
56: {"SHA-224", "SHA3-224"},
64: {"SHA-256", "SHA3-256", "BLAKE2s"},
96: {"SHA-384", "SHA3-384"},
128: {"SHA-512", "SHA3-512", "BLAKE2b"},
}
func identifyHash(hex string) []string {
if !hexPattern.MatchString(hex) {
return []string{"Not a hex-encoded hash"}
}
if algos, ok := hashLengths[len(hex)]; ok {
return algos
}
return []string{fmt.Sprintf("Unknown (%d hex chars)", len(hex))}
}
func main() {
fmt.Println(identifyHash("d41d8cd98f00b204e9800998ecf8427e"))
// → [MD5]
}#!/bin/bash
# Identify a hash from the command line by character count
hash="$1"
if [[ ! "$hash" =~ ^[0-9a-fA-F]+$ ]]; then
echo "Not a hex-encoded hash"
exit 1
fi
len=${#hash}
case $len in
32) echo "MD5 (128-bit)" ;;
40) echo "SHA-1 or RIPEMD-160 (160-bit)" ;;
56) echo "SHA-224 or SHA3-224 (224-bit)" ;;
64) echo "SHA-256 or SHA3-256 (256-bit)" ;;
96) echo "SHA-384 or SHA3-384 (384-bit)" ;;
128) echo "SHA-512 or SHA3-512 (512-bit)" ;;
*) echo "Unknown hash length: $len chars" ;;
esac
# Usage: ./identify.sh d41d8cd98f00b204e9800998ecf8427e
# → MD5 (128-bit)