SHA1校验和必须为严格40位小写十六进制字符(0–9、a–f),长度不符、含大写字母或非hex字符均非法;推荐用fmt.Sscanf配合len(s)==40校验,一次完成格式验证与二进制转换。

SHA1 字符串长度和字符集必须严格匹配
合法的 SHA1 校验和是 40 个十六进制字符(0–9, a–f),全部小写,不带前缀(如 sha1: 或 0x)。任何长度偏差、大写字母、非 hex 字符(如 g、-、空格)都应视为非法。
常见错误现象:"a1b2c3d4e5f67890123456789012345678901234"(40 位但含大写)→ 非法;"sha1:a1b2..." → 非法;"a1b2...cdef"(39 或 41 位)→ 非法。
- 用
len(s) != 40快速排除长度不符的字符串 - 用
strings.ToLower(s)统一小写后再校验,避免因大小写混用误判(但注意:标准 SHA1 输出约定为小写,强制转小写只是容错手段,生产环境建议先拒绝大写输入) - 逐字符检查是否属于
"0123456789abcdef"比正则更轻量,尤其在高频校验场景
用 fmt.Sscanf 做类型安全解析
fmt.Sscanf 可以一次性完成长度 + 字符集双重验证,且不分配额外切片,比正则或循环更高效。它要求输入恰好匹配格式,多一个字符、少一个字符、非 hex 字符都会失败。
var sha1Bytes [20]byte
n, err := fmt.Sscanf(s, "%x", &sha1Bytes)
if err != nil || n != 1 || len(s) != 40 {
return false
}
注意:%x 会接受任意长度 hex 字符串(只要能填满 [20]byte),所以必须显式检查 len(s) == 40;n != 1 表示未成功扫描到任何值(比如空字符串或全非 hex 字符)。
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 该方法隐含要求输入不含空格或换行——若输入可能带空白,需先
strings.TrimSpace - 不支持 Unicode 空格(如
\u2000),如有此风险,应先用bytes.Trim处理 - 返回的
sha1Bytes可直接用于后续 crypto/sha1 计算比对,避免重复解析
别直接用 regexp.MustCompile 验证 SHA1
正则 ^[a-f0-9]{40}$ 看似直观,但实际有隐患:Go 的 regexp 包在短字符串上性能不如纯逻辑判断,且容易忽略边界情况。
典型坑:regexp.MustCompile(`^[a-f0-9]{40}$`).MatchString("abc") 返回 false(正确),但 MatchString("a1b2...cdef\n") 若未加 (?m) 可能意外通过——因为 $ 默认只匹配行尾,而换行符后还有内容。
- 若坚持用正则,必须用
^(?i)[a-f0-9]{40}$并确保输入已strings.TrimSpace - 更稳妥的做法是:先
len(s) != 40快速返回 false,再用strings.IndexFunc扫描首个非法字符,比正则快 3–5 倍 - 所有正则方案都无法像
fmt.Sscanf那样顺便产出二进制值,需额外调用hex.DecodeString,增加一次内存分配
注意 hex 解码失败时的 panic 风险
如果后续要用该字符串做实际校验(比如比对文件 hash),必然要调用 hex.DecodeString。但它在输入非法时会 panic,不是返回 error —— 这和多数 Go 函数行为不一致,极易被忽略。
错误写法:decoded, _ := hex.DecodeString(s) → 若 s 长度非偶数或含非法字符,直接 panic。
- 务必用
decoded, err := hex.DecodeString(s)并检查err != nil - 但此时你已经做了两次校验(先格式校验,再 decode),冗余。推荐统一用
fmt.Sscanf方案,一次到位 - 若必须用
hex.DecodeString,请确保输入已通过前述长度 + 字符集预检,这样err实际不会发生,可省略检查——但前提是预检逻辑 100% 正确
真正容易被忽略的是:SHA1 字符串本身合法,不代表它对应真实数据。格式验证只是第一道门,后面还需结合业务逻辑确认该 hash 是否存在于预期上下文(如数据库记录、签名清单)中。

















