← 中文资源

简体中文版本

恒时比较

恒时比较对两个输入的每个字节执行完全相同的操作并累积差异,消除“提前退出”这一泄漏前缀匹配的时序信号。

阅读英文原文

概念定义

朴素的相等判断在第一个不同字节处停下,耗时暴露了前多少字节匹配。恒时比较读取两个输入的全部字节、把差异异或进累加器,再报告累加器是否为零——工作量相同、时长相同、没有信号。

工作机制

规范形式是遍历全长计算按位差并 OR 进累加器,最后以无分支方式把累加器转成布尔。库版本还要求输入等长或显式处理长度,因为提前返回泄漏长度本身就是漏洞。

使用场景

任一操作数是机密或由机密派生的一切比较:会话令牌查找结果、HMAC 校验、密码重置令牌、webhook 签名、加密 Cookie 完整性标签。

常见误区

“先哈希再 ==”并不等价——摘要的最终比较仍需恒时,哈希洗不掉泄漏。恒时的成本只是一遍固定长度输入,相对网络延迟可忽略。调用点常把比较包进按结果分叉的日志或异常路径,把信号又引回来——要审计整个函数而不是一行。最敏感的端点应在 CI 里用负载下的时序分布验证。

代码示例

package main

import (
	"crypto/sha256"
	"crypto/subtle"
	"fmt"
)

func ConstantTimeEqual(a, b string) bool {
	left := sha256.Sum256([]byte(a))
	right := sha256.Sum256([]byte(b))
	return subtle.ConstantTimeCompare(left[:], right[:]) == 1
}

func main() {
	secret := "s3cr3t-token"
	fmt.Println(ConstantTimeEqual(secret, "s3cr3t-token"))
	fmt.Println(ConstantTimeEqual(secret, "s3cr3t-tokeX"))
	fmt.Println(ConstantTimeEqual(secret, "short"))
}
use std::hint::black_box;

fn constant_time_eq(left: &[u8], right: &[u8]) -> bool {
    if left.len() != right.len() {
        return false;
    }
    let mut difference = 0u8;
    for (a, b) in left.iter().zip(right.iter()) {
        difference |= a ^ b;
    }
    black_box(difference) == 0
}

fn compare_tokens(expected: &str, provided: &str) -> bool {
    constant_time_eq(expected.as_bytes(), provided.as_bytes())
}

fn main() {
    println!("equal: {}", compare_tokens("token-abc", "token-abc"));
    println!("different: {}", compare_tokens("token-abc", "token-abd"));
    println!("length mismatch: {}", compare_tokens("token-abc", "token"));
    println!("empty: {}", compare_tokens("", ""));
}