
用 == 比较两个 API Token,居然会让黑客在网络另一端逐位猜出你的正确凭证?这可不是危言耸听,而是安全领域里非常经典的侧信道时序攻击(Timing Attack)。
在密码学和鉴权校验场景中,看似微不足道的时间差异,却能成为泄露绝对机密的通道。
在 Go 语言中,字符串比较操作符 == 采用的是按字节顺序依次对比。一旦在某一个位置发现了不匹配的字节,底层的比较操作就会立即中断并返回 false(早期退出)。
这导致如果输入的 Token 第一位就错了,比较用时会极短;如果前几个字符是对的,比较用时就会变长。
// 校验 API Token 的不安全写法
func ValidateToken(authHeader, expectedToken string) bool {
if authHeader == "" || expectedToken == "" {
return false
}
return authHeader == expectedToken // 存在早期退出,易受时序攻击
}
我们在本地对 1000 字节长度的字符串进行了 == 比对测试,测试数据表明:
两者存在约 6 倍 的时间差。虽然纳秒级差异微乎其微,但攻击者通过局域网或边缘发起数万次高精度探测并求均值,就可以逐步过滤掉网络抖动噪声,从而逐位猜测出正确的凭证。
要解决此问题,必须引入 Go 标准库的 crypto/subtle 包。其中的 ConstantTimeCompare 函数确保无论在哪个字节开始不匹配,都会完整遍历整个字节切片,使执行耗时仅与数据长度相关。
// 标准库 crypto/subtle.ConstantTimeCompare 的核心实现
funcConstantTimeCompare(x, y []byte)int{
iflen(x)!=len(y){
return0
}
var v byte
for i :=0; i <len(x); i++{
v |= x[i]^ y[i]
}
returnConstantTimeByteEq(v,0)
}
上述逻辑使用按位异或 x[i] ^ y[i]。若两字节相同则为 0,不同则为非 0。通过 v |= ... 累加,只要有一个字节不同,v 的最终值就会非零。这段逻辑避免了基于内容的提前退出,使比较耗时主要只与切片长度相关,而不取决于第几个字节开始不同。
从原理上,可以用如下不含数据相关分支的位运算理解单字节等值判断:
// 用于理解恒定时间单字节相等判断的等价写法
func ConstantTimeByteEq(x, y uint8) int {
return int((uint32(x^y) - 1) >> 31)
}
其背后的数学逻辑是:
x == y,则 x ^ y 是 0。0 - 1 产生下溢变成 0xFFFFFFFF,右移 31 位得到 1。x != y,则 x ^ y 是非零值 d(范围在 [1, 255])。d - 1 的第 31 位必定是 0,右移 31 位得到 0。仔细观察可以发现,若输入长度与真实 Token 长度不一致,ConstantTimeCompare 依然会立即返回 0(早期退出)。
这依然会泄露真实 Token 的长度。攻击者通过试探不同的输入长度,当耗时发生跃升时,即代表探测到了正确的长度。
对于变长敏感数据(如 API Token),如果直接用 subtle.ConstantTimeCompare 比较,依然会因为长度不同而发生早期退出,从而泄露长度信息。
因此,开发者需要自己在业务层先将比对对象哈希化(如使用 SHA-256 转化为固定的 32 字节摘要),再调用标准库比对。以下是推荐在项目中自行封装的函数:
// 开发者在业务中自行封装的安全比对函数
func SecureCompare(secret, input string) bool {
// 无论输入长度如何,SHA-256 均输出固定的 32 字节摘要
hSecret := sha256.Sum256([]byte(secret))
hInput := sha256.Sum256([]byte(input))
// 此时长度绝对都是 32 字节,规避了长度提前退出的风险
return subtle.ConstantTimeCompare(hSecret[:], hInput[:]) == 1
}
[!IMPORTANT] 注意:这里讨论的是服务端已安全持有明文 Secret 的比对场景,例如 API Token、Webhook Secret、签名校验。用户注册登录密码严禁采用此比对方式,而应使用
bcrypt、scrypt、Argon2id等具备慢速盐洗特征的专用密码哈希方案。
安全漏洞往往隐藏在最底层的指令级别。对于 API Token、签名校验等高危场景,普通的 == 会泄露大量时序侧信道信息。
利用 crypto/subtle 配合“先哈希,后比对”的防范手段,才是在秒级以下的对抗中筑起护城河的工程标准实践。