Kubesphere 依赖链中的 sha1cd:一个具备碰撞检测能力的 SHA-1 实现
2026/9/14 6:02:16 网站建设 项目流程

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 // indirect

modules.txt 中登记了三个包路径,也就是 vendor 目录实际保留的全部包:github.com/pjbgf/sha1cd(根包)、github.com/pjbgf/sha1cd/internalgithub.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 = 20Chunk = 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里:

  1. 记录每步的消息字m1 [80]uint32保存该块 80 步各自使用的消息字;
  2. 记录三个"预步压缩状态"
// 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-L47cs[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):

  1. 计算 DV 掩码ubc.CalculateDvMask(m1)根据该块 80 个消息字算出一个位掩码,标识出"该块在哪些 Disturbance Vector(DV,扰动向量)意义下逼近碰撞条件";
  2. 查表匹配:遍历ubc.SHA1_dvs()返回的 DV 表(定义在 ubc/const.go),对每条 DV 检查其MaskB对应的掩码位是否命中。表中每条DvInfo携带DvTypeDvK(目标字编号,如 43~56)、DvB(字内 bit)、TestT(测试步,本表中为 58 或 65,0 表示块首)、以及一个 80 字的扰动消息增量Dm
  3. 重放验证:命中的 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/cscheckCollision命中后把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.godigest状态机、New/NewGeneric/Sum/CollisionResistantSum、状态序列化
detection.goCollisionResistantHash接口定义
sha1cdblock_generic.go纯 Go 块压缩 +checkCollision/hasCollided检测核心
sha1cdblock_amd64.go、sha1cdblock_amd64.samd64 汇编块压缩(构建标签!noasm && gc && amd64
sha1cdblock_noasm.go禁用汇编时退回纯 Go 实现
ubc/const.go、ubc/*.go/.sDV 位掩码计算与sha1_dvs扰动向量表
internal/const.goSHA-1 公共常数:K0..K3Init0..4SizeRoundsChunkPreStepState、序列化魔数

几点从源码结构可以直接确认的细节:

  • 碰撞状态是逐 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 源码中找到对应实现:

  1. 作为crypto/sha1的 drop-in replacement:Size/BlockSize与标准库一致(20/64),API 形状相同;
  2. 检测到碰撞攻击输入时,通过 3 次重压(80→240 步)使输出偏离标准 SHA-1,并可通过Sum的第二个返回值或CollisionResistantSum显式读到col标志;
  3. 合法输入的哈希结果与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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询