Kubesphere 依赖链中的 sha1cd:一个具备碰撞检测能力的 SHA-1 实现
【免费下载链接】kubesphereThe container platform tailored for Kubernetes multi-cloud, datacenter, and edge management ⎈ 🖥 ☁️项目地址: https://gitcode.com/GitHub_Trending/ku/kubesphere
本文以 Kubesphere 仓库中 vendor 进来的vendor/github.com/pjbgf/sha1cd/README.md为主线,完整讲解 sha1cd 这个"带反密码分析(counter-cryptanalysis)碰撞检测能力的 SHA-1"Go 库:如何在代码中把它当作crypto/sha1的替代品来调用、CollisionResistantSum返回的碰撞标志位意味着什么,并结合仓库内 v0.3.2 的实际源码,剖析其 80 步压缩、3 个预步压缩状态、Disturbance Vector 校验表,以及检测到碰撞后"扩展为 240 步"的完整实现机制。
1. sha1cd 是什么,以及它在 Kubesphere 仓库中的位置
sha1cd 的 README 对库本身的定位只有一句话:一个 Go 实现的 SHA-1,具备碰撞攻击检测能力。原文同时交代了两条实现脉络:
- README 中提到的
cgo/lib部分是对原始 C 实现的"carbon copy"(原样复制),其算法基础是 Marc Stevens 获奖的 counter-cryptanalysis 白皮书; - 纯 Go 部分则"largely based off Go's generic sha1",即基于 Go 标准库
crypto/sha1的纯 Go 通用实现改写,并且 README 明确说明当前没有实现 SIMD 优化。
在 Kubesphere 仓库中,这个库并不是被业务代码直接调用的。从 go.mod 可以看到它是以间接依赖的形式声明的:
github.com/pjbgf/sha1cd v0.3.2 // indirectmodules.txt 中登记了三个包路径,也就是 vendor 目录实际保留的全部包:github.com/pjbgf/sha1cd(根包)、github.com/pjbgf/sha1cd/internal和github.com/pjbgf/sha1cd/ubc。从源码结构看,结合 go.mod 中同时声明了github.com/go-git/go-git/v5 v5.16.0,可以推断 sha1cd 是经由 go-git 这条依赖链进入 Kubesphere 的依赖图的——go-git 系列库将 sha1cd 用作其内容寻址哈希的后端,以在对象哈希边界上防御 SHA-1 碰撞攻击。对 Kubesphere 本身而言,这份 vendor 代码的价值在于:它把"哈希后端如何抵抗已被攻破的 SHA-1 碰撞攻击"这一机制完整地固化在了仓库内,可以直接阅读。
为什么 git 工具链要关心 SHA-1 碰撞?因为 SHA-1 的密码学碰撞在 2017 年的 Shattered 演示后已是"工程上可构造"的。git 生态用 SHA-1 作为 blob/commit 的内容指纹,若攻击者能构造出两个哈希相同但内容不同的对象,仓库完整性与供应链安全就会受到威胁。sha1cd 的思路不是替换算法,而是在计算哈希的同时在线检测输入是否正在遭受碰撞攻击,并在检测到后改变输出行为。
2. API 用法:README 给出的两种调用方式
2.1 基础用法:Sum
README 给出的最简示例是把 sha1cd 当作crypto/sha1的 drop-in replacement:
import "github.com/pjbgf/sha1cd" func test(){ data := []byte("data to be sha1 hashed") h := sha1cd.Sum(data) fmt.Printf("hash: %q\n", hex.EncodeToString(h)) }需要注意的一个版本差异:README 的这段示例对应较早版本;在当前仓库 vendor 的 v0.3.2 源码中,Sum的签名已经返回了两个值——哈希本身加一个碰撞标志位,见 sha1cd.go:
// Sum returns the SHA-1 checksum of the data. func Sum(data []byte) ([Size]byte, bool) { d := New().(*digest) d.Write(data) return d.checkSum(), d.col }因此用 v0.3.2 调用时应写作h, col := sha1cd.Sum(data)。
2.2 显式询问碰撞:CollisionResistantSum
README 的第二个示例演示了如何获取"是否检测到碰撞"的信息:
import "github.com/pjbgf/sha1cd" func test(){ data := []byte("data to be sha1 hashed") h, col := sha1cd.CollisionResistantSum(data) if col { fmt.Println("collision found!") } fmt.Printf("hash: %q", hex.EncodeToString(h)) }CollisionResistantSum是接口层面的约定。detection.go 定义了CollisionResistantHash接口:
type CollisionResistantHash interface { // CollisionResistantSum extends on Sum by returning an additional boolean // which indicates whether a collision was found during the hashing process. CollisionResistantSum(b []byte) ([]byte, bool) hash.Hash }它的实现位于 sha1cd.go:先复制一份 digest(d0 := *d,保证调用者可以继续 Write),执行checkSum()完成 padding 与定长处理,最后返回追加了摘要的字节切片和d0.col标志。
2.3 流式用法:New 与 NewGeneric
除了一次性Sum,v0.3.2 还提供两个构造函数(sha1cd.go):
// New returns a new hash.Hash computing the SHA1 checksum. The Hash also // implements encoding.BinaryMarshaler and encoding.BinaryUnmarshaler to // marshal and unmarshal the internal state of the hash. func New() hash.Hash // NewGeneric is equivalent to New but uses the Go generic implementation, // avoiding any processor-specific optimizations. func NewGeneric() hash.Hash两者返回的hash.Hash都是*digest,区别仅在于内部blockFunc指向哪个块处理函数:New走默认路径(在 amd64 + gc 且未禁用汇编时为汇编实现),NewGeneric强制走纯 Go 的blockGeneric。与标准库crypto/sha1一致,Size()返回 20,BlockSize()返回 64,这两个常量在 internal/const.go 中定义为Size = 20、Chunk = 64。
另一个值得注意的能力:digest实现了encoding.BinaryMarshaler/encoding.BinaryUnmarshaler(sha1cd.go),可以把哈希中间状态序列化到字节流再恢复。序列化格式以魔数shacd\x01开头,总长度MarshaledSize为 6 + 5×4 + 64 + 8 = 98 字节(internal/const.go),反序列化时会校验魔数与长度,不匹配则报错。
3. 碰撞是怎么被"检测"出来的:三个预步压缩状态
这一节回答 README 中最关键的一句行为描述背后的原理。先回顾 SHA-1 的压缩结构:输入按 64 字节(Chunk)分块,每块经过 80 步(Rounds = 80,每 20 步一轮,分别使用K0~K3与不同的 f 函数)压缩到 5 个 32 位字h0..h4上。
sha1cd 在压缩循环中额外做了两件事,全部实现在 sha1cdblock_generic.go 的blockGeneric里:
- 记录每步的消息字:
m1 [80]uint32保存该块 80 步各自使用的消息字; - 记录三个"预步压缩状态":
// cs stores the pre-step compression state for only the steps required for the // collision detection, which are 0, 58 and 65. cs := [shared.PreStepState][shared.WordBuffers]uint32{}对应源码中的三处采样点:
- 步骤 0(块开始):L46-L47
cs[0] = {a,b,c,d,e}; - 步骤 58:L84-L87;
- 步骤 65:L100-L103。
这正是 internal/const.go 中PreStepState = 3的由来:"Currently there are 3 pre-step compression states required: 0, 58, 65."。
3.1 检测逻辑:DV 表、掩码与反向重放
80 步走完后(L122-L134),hi == 1(即该块的第一次处理)时调用checkCollision(m1, cs, state),state是块压缩完成后的h0..h4。其判定分三步(L142-L179):
- 计算 DV 掩码:
ubc.CalculateDvMask(m1)根据该块 80 个消息字算出一个位掩码,标识出"该块在哪些 Disturbance Vector(DV,扰动向量)意义下逼近碰撞条件"; - 查表匹配:遍历
ubc.SHA1_dvs()返回的 DV 表(定义在 ubc/const.go),对每条 DV 检查其MaskB对应的掩码位是否命中。表中每条DvInfo携带DvType、DvK(目标字编号,如 43~56)、DvB(字内 bit)、TestT(测试步,本表中为 58 或 65,0 表示块首)、以及一个 80 字的扰动消息增量Dm; - 重放验证:命中的 DV 交由
hasCollided(L181-L268)做真正的数学验证——从TestT处保存的中间状态出发,先反向执行 SHA-1 的逆步(用b = RotateLeft32(b, -30)撤销左旋、按轮次的 f 函数与 K 常数做减法)还原出"中间哈希值"(IHV),再以m2 = m1 XOR Dm(即攻击者构造的"第二条消息")正向重新压缩剩余步数,最后把重建的 IHV 与块压缩的真实输出h逐字异或比较,全零即判定碰撞成立(L262-L265)。
用白皮书的术语概括:碰撞攻击要求消息在某个中间步同时满足一组"不可避免位条件"(unavoidable bit conditions)。sha1cd 把这一条件转化为一次确定性的重放检验——如果攻击者的第二条消息 m2 在TestT步之后压缩结果必须与第一条消息 m1 完全一致才能成功碰撞,那么真实h与 m2 重放结果相等就是攻击正在发生的数学指纹。ubc包文件头注释也点明了包职责:ubc.go "provides ways for SHA1 blocks to be checked for Unavoidable Bit Conditions that arise from crypto analysis attacks"。
4. 检测到碰撞后:从 80 步扩展到 240 步
README 用一小段话描述了检测结果对输出的影响,原文要点是:算法在检测到碰撞尝试时会自动"避免"碰撞——把 SHA-1 从 80 步扩展到 240 步;因此包含不可避免位条件的输入,其sha1cd哈希将与crypto/sha1的结果不同,而合法(非攻击)输入的两者输出完全一致。
源码中这一"自动避免"的落地方式非常直白:命中碰撞后把dig.col = true记下,然后把同一个块再完整压缩两遍,合计三遍 80 步,即 240 步。纯 Go 路径用循环计数器与goto实现(sha1cdblock_generic.go 的注释解释了动机):
// Collision attacks are thwarted by hashing a detected near-collision block 3 times. // Think of it as extending SHA-1 from 80-steps to 240-steps for such blocks: // The best collision attacks against SHA-1 have complexity about 2^60, // thus for 240-steps an immediate lower-bound for the best cryptanalytic attacks would be 2^180. // An attacker would be better off using a generic birthday search of complexity 2^80. rehash:hi从 1 递增到 2 时goto rehash,第二次仍检测到碰撞则hi到 3 结束循环,从而保证同一块最多压缩三次。amd64 汇编路径语义相同(sha1cdblock_amd64.go):blockAMD64处理一个 64 字节块并填好m1/cs,checkCollision命中后把dig.col置真,并再调用两次blockAMD64。
注释中给出的安全论证值得完整引用:SHA-1 上最好的碰撞攻击复杂度约为 2^60;把被攻击的块强制重压到 240 步后,对"240 步 SHA-1"构造同类攻击的下界被抬高到 2^180,远超 2^80 的通用生日搜索成本——理性攻击者反而只能退回暴力搜索。换句话说,重压不是为了产出"更安全的哈希函数",而是让特定攻击向量在该条数据流上失效。
与之配套的是 internal/const.go 中的InitTmp0..InitTmp4五组常量,注释写明它们是"SHA recompression step"(重压缩步骤)中临时变量ihvtmp0..ihvtmp4的初始值,供汇编路径的快速重放使用。
5. 源码结构与构建细节
把 vendor 目录里的文件组织起来看,整个库的分工是:
| 文件 | 职责 |
|---|---|
| sha1cd.go | digest状态机、New/NewGeneric/Sum/CollisionResistantSum、状态序列化 |
| detection.go | CollisionResistantHash接口定义 |
| sha1cdblock_generic.go | 纯 Go 块压缩 +checkCollision/hasCollided检测核心 |
sha1cdblock_amd64.go、sha1cdblock_amd64.s | amd64 汇编块压缩(构建标签!noasm && gc && amd64) |
sha1cdblock_noasm.go | 禁用汇编时退回纯 Go 实现 |
ubc/const.go、ubc/*.go/.s | DV 位掩码计算与sha1_dvs扰动向量表 |
| internal/const.go | SHA-1 公共常数:K0..K3、Init0..4、Size、Rounds、Chunk、PreStepState、序列化魔数 |
几点从源码结构可以直接确认的细节:
- 碰撞状态是逐 digest 的:
digest结构体中专门的col bool字段(sha1cd.go)只在检测到碰撞时置位,Reset()会将其清零(L115-L125),因此同一digest复用前必须 Reset,否则碰撞标志会跨段残留。 - 对 Go 注册表的行为:
init()中执行了crypto.RegisterHash(crypto.SHA1, New)(sha1cd.go)。从源码结构看,只要某个二进制 import 了 sha1cd,Gocrypto包注册表中 "SHA1" 的构造器就会指向sha1cd.New,后续经crypto.New(crypto.SHA1)取哈希的调用方拿到的就是这个带检测的实现——这也是它能"无缝"替换标准库 SHA-1 的机制所在。 - 无 SIMD 的准确性:README 说"At present no SIMD optimisations have been implemented",与 v0.3.2 源码一致——amd64 汇编路径是标量汇编优化,
NewGeneric则给出与汇编无关的纯 Go 结果,两者可用于交叉验证同一输入的普通哈希值。 - 性能代价的边界:
checkCollision对每个 64 字节块都会做一次CalculateDvMask与查表;只有在掩码命中后才可能进入hasCollided重放。对不触位条件的普通数据,额外开销是每块的掩码计算,重压(240 步)仅对攻击块发生。这一结论可由blockGeneric/block的控制流直接读出。
6. 小结与延伸阅读
sha1cd 在 README 中承诺的行为可以浓缩为三条,且都能在本仓库 vendor 的 v0.3.2 源码中找到对应实现:
- 作为
crypto/sha1的 drop-in replacement:Size/BlockSize与标准库一致(20/64),API 形状相同; - 检测到碰撞攻击输入时,通过 3 次重压(80→240 步)使输出偏离标准 SHA-1,并可通过
Sum的第二个返回值或CollisionResistantSum显式读到col标志; - 合法输入的哈希结果与
crypto/sha1完全一致。
对 Kubesphere 使用者而言,理解这份库的实际意义在于:当你使用基于 go-git 的 Git 操作功能时,其对象哈希边界上已经内建了这套 SHA-1 碰撞攻击在线检测机制,其完整算法资料(counter-cryptanalysis 白皮书、原始 C 实现、NIST SHAVS 测试向量)可在 README 的 References 一节中按名称追溯,仓库内的入口文件为 vendor/github.com/pjbgf/sha1cd/README.md。
【免费下载链接】kubesphereThe container platform tailored for Kubernetes multi-cloud, datacenter, and edge management ⎈ 🖥 ☁️项目地址: https://gitcode.com/GitHub_Trending/ku/kubesphere
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考