3. 离线口令哈希识别与格式判定
哈希样本识别任务说明
本案例对一批离线口令哈希样本进行算法类型与存储格式判定。判定依据来自哈希字符串的前缀标识、字段分隔符、编码字符集、固定长度四个可观察特征。识别结果分为三类:可审计的常见哈希族、需进一步确认的疑似格式、无法识别的异常样本。
识别规则与判定依据
| 哈希族 | 典型格式特征 | 字段结构 | 可审计性 |
|---|---|---|---|
| MD5 (raw) | 32 位十六进制,无前缀 | 单一字段 | 可审计(弱) |
| SHA-1 (raw) | 40 位十六进制,无前缀 | 单一字段 | 可审计(弱) |
| SHA-256 (raw) | 64 位十六进制,无前缀 | 单一字段 | 可审计 |
| bcrypt | $2a$/$2b$/$2y$ 前缀 | $2b$cost$salt+hash | 可审计 |
| SHA-512 crypt | $6$ 前缀 | $6$salt$hash | 可审计 |
| SHA-256 crypt | $5$ 前缀 | $5$salt$hash | 可审计 |
| MD5 crypt | $1$ 前缀 | $1$salt$hash | 可审计(弱) |
| NTLM | 32 位十六进制,无前缀 | 单一字段 | 需上下文确认 |
| MySQL 4.1+ | 41 位,* + 40 位十六进制 | *HASH | 可审计 |
MD5 与 NTLM 均为 32 位十六进制,仅凭字符串无法区分,需结合来源系统(如 Windows SAM 转储 vs Linux shadow)判定。本案例将其标记为"需上下文确认"。
识别流程与判定逻辑
识别脚本实现
以下 Python 脚本对样本列表逐条执行上述判定逻辑,输出结构化审计清单。脚本不依赖第三方库,可直接运行。
import re
# 前缀 → 算法映射表
PREFIX_MAP = {
"$2a$": "bcrypt", "$2b$": "bcrypt", "$2y$": "bcrypt",
"$6$": "SHA-512 crypt", "$5$": "SHA-256 crypt",
"$1$": "MD5 crypt",
}
def identify_hash(h):
"""识别单条哈希样本,返回 (算法, 格式, 可审计性)"""
h = h.strip()
if not h:
return ("空样本", "无效", "异常")
# 1. 前缀匹配
for prefix, algo in PREFIX_MAP.items():
if h.startswith(prefix):
parts = h.split("$")
if len(parts) >= 4:
return (algo, f"{prefix}cost$salt$hash", "可审计")
return (algo, "前缀匹配但字段不完整", "需确认")
# 2. MySQL 格式:* + 40位hex
if re.fullmatch(r"\*[0-9A-Fa-f]{40}", h):
return ("MySQL 4.1+", "*HASH(40hex)", "可审计")
# 3. 纯十六进制长度判定
if re.fullmatch(r"[0-9A-Fa-f]+", h):
n = len(h)
if n == 32:
return ("MD5 或 NTLM", "32位hex", "需上下文确认")
if n == 40:
return ("SHA-1 (raw)", "40位hex", "可审计(弱)")
if n == 64:
return ("SHA-256 (raw)", "64位hex", "可审计")
return (f"未知hex({n}位)", f"{n}位hex", "异常")
# 4. 无法识别
return ("无法识别", "格式不符", "异常")
# 教学示例样本
samples = [
"5f4dcc3b5aa765d61d8327deb882cf99", # 32位hex
"aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d", # 40位hex
"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", # 64位hex
"$2b$12$LJ3m4ys3Lk0TSwHnbfOMiOXQ8Y5vK3z1Q2w4e5r6t7y8u9i0o", # bcrypt
"$6$rounds=5000$saltvalue$hashvaluehere", # SHA-512 crypt
"*6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9", # MySQL
"not-a-valid-hash-string!!!", # 异常
"abc123", # 异常
]
print(f"{'样本(截断)':<38} {'算法':<18} {'格式':<22} {'可审计性'}")
print("-" * 100)
for s in samples:
algo, fmt, audit = identify_hash(s)
display = s[:34] + ".." if len(s) > 36 else s
print(f"{display:<38} {algo:<18} {fmt:<22} {audit}")
预期输出(教学示例,非实测结果):
样本(截断) 算法 格式 可审计性
----------------------------------------------------------------------------------------------------
5f4dcc3b5aa765d61d8327deb882cf99 MD5 或 NTLM 32位hex 需上下文确认
aaf4c61ddcc5e8a2dabede0f3b482cd9aea.. SHA-1 (raw) 40位hex 可审计(弱)
9f86d081884c7d659a2feaa0c55ad015a3.. SHA-256 (raw) 64位hex 可审计
$2b$12$LJ3m4ys3Lk0TSwHnbfOMiOXQ8Y5.. bcrypt $2b$cost$salt$hash 可审计
$6$rounds=5000$saltvalue$hashvalue.. SHA-512 crypt $6$cost$salt$hash 可审计
*6BB4837EB74329105EE4568DDA7DC67ED.. MySQL 4.1+ *HASH(40hex) 可审计
not-a-valid-hash-string!!! 无法识别 格式不符 异常
abc123 未知hex(6位) 6位hex 异常
异常样本处理与审计清单
识别为"异常"的样本需单独记录,不进入后续字典攻击或掩码攻击流程。审计清单应包含以下字段,便于后续案例直接引用:
| 字段 | 说明 | 示例值 |
|---|---|---|
| sample_id | 样本序号 | 007 |
| raw_hash | 原始哈希字符串(截断显示) | not-a-valid-hash... |
| algorithm | 识别出的算法名 | 无法识别 |
| format | 存储格式描述 | 格式不符 |
| auditable | 是否可审计 | 异常 |
| action | 建议操作 | 排除,不参与后续攻击 |
若样本来源为 Windows 系统(如 SAM 转储),32 位十六进制应优先判定为 NTLM 而非 MD5;若来源为 Linux shadow 且无
$ 前缀,则更可能是 MD5(raw)。来源信息缺失时,必须在审计清单中标注"需上下文确认",不得强行归类。验证与通过条件
- 脚本对 8 条教学样本的输出与预期表一致,无未捕获异常。
- 所有
$前缀样本均被正确映射到对应算法族。 - 32 位十六进制样本被标记为"需上下文确认",而非直接判定为 MD5。
- 异常样本(长度不符、含非法字符)被正确标记为"异常"并排除。
- 审计清单包含 sample_id、raw_hash、algorithm、format、auditable、action 六个字段。