Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

crypto/subtle - 恒定时间密码学操作

概述

crypto/subtle 包实现了底层的恒定时间(constant-time)密码学操作。

重要警告

  • ⚠️ 仅用于底层密码学实现:普通应用不应直接使用
  • ⚠️ 需要专业知识:错误使用可能导致安全漏洞
  • ⚠️ 不是通用工具包:仅用于实现密码学原语

主要用途

  • 🔐 防止时序攻击:恒定时间比较、选择
  • 🔒 密码学原语实现:加密算法、哈希函数
  • 🛡️ 安全敏感操作:密钥比较、MAC 验证
  • ⚙️ 底层密码学库:实现 AES、ChaCha20 等

时序攻击简介

什么是时序攻击?

时序攻击(Timing Attack)是一种侧信道攻击,通过分析算法执行时间的差异来推断秘密信息。

攻击原理

// ❌ 不安全:早期退出
func insecureCompare(a, b []byte) bool {
    if len(a) != len(b) {
        return false // 立即返回,泄露长度信息
    }
    for i := 0; i < len(a); i++ {
        if a[i] != b[i] {
            return false // 第一个不匹配字节位置泄露
        }
    }
    return true
}

// 攻击者可以:
// 1. 测量比较时间
// 2. 推断第一个不匹配字节的位置
// 3. 逐字节猜测正确的值

恒定时间实现

// ✅ 安全:恒定时间
func secureCompare(a, b []byte) bool {
    return subtle.ConstantTimeCompare(a, b) == 1
}

// 无论哪里不匹配,执行时间都相同

核心函数

1. ConstantTimeCompare - 恒定时间比较

func ConstantTimeCompare(x, y []byte) int

功能:恒定时间比较两个字节切片。

参数

  • x:第一个字节切片
  • y:第二个字节切片

返回值

  • 1:如果 x == y
  • 0:如果 x != y

特点

  • ✅ 执行时间与内容无关
  • ✅ 比较长度(长度不同返回 0)
  • ✅ 防止时序攻击

示例

package main

import (
    "crypto/subtle"
    "fmt"
)

func main() {
    // 正确的密钥
    correctKey := []byte("secret-key-12345")
    
    // 用户提供的密钥
    userKey1 := []byte("secret-key-12345")
    userKey2 := []byte("wrong-key-123456")
    
    // 恒定时间比较
    if subtle.ConstantTimeCompare(correctKey, userKey1) == 1 {
        fmt.Println("✓ 密钥匹配")
    } else {
        fmt.Println("✗ 密钥不匹配")
    }
    
    if subtle.ConstantTimeCompare(correctKey, userKey2) == 1 {
        fmt.Println("✓ 密钥匹配")
    } else {
        fmt.Println("✗ 密钥不匹配")
    }
}

使用场景

  • HMAC 签名验证
  • 密码比较
  • API 密钥验证
  • MAC 校验

2. ConstantTimeByteEq - 恒定时间字节比较

func ConstantTimeByteEq(x, y byte) int

功能:恒定时间比较两个字节。

参数

  • x:第一个字节
  • y:第二个字节

返回值

  • 1:如果 x == y
  • 0:如果 x != y

示例

package main

import (
    "crypto/subtle"
    "fmt"
)

func main() {
    a := byte(0x42)
    b := byte(0x42)
    c := byte(0x43)
    
    fmt.Println(subtle.ConstantTimeByteEq(a, b)) // 1
    fmt.Println(subtle.ConstantTimeByteEq(a, c)) // 0
}

3. ConstantTimeIntEq - 恒定时间整数比较

func ConstantTimeIntEq(x, y int) int

功能:恒定时间比较两个整数。

参数

  • x:第一个整数
  • y:第二个整数

返回值

  • 1:如果 x == y
  • 0:如果 x != y

示例

package main

import (
    "crypto/subtle"
    "fmt"
)

func main() {
    fmt.Println(subtle.ConstantTimeIntEq(10, 10)) // 1
    fmt.Println(subtle.ConstantTimeIntEq(10, 20)) // 0
}

4. ConstantTimeSelect - 恒定时间选择

func ConstantTimeSelect(mask int, x, y int) int

功能:恒定时间选择两个值之一。

参数

  • mask:选择掩码(0 或 1)
  • x:如果 mask == 1,返回 x
  • y:如果 mask == 0,返回 y

返回值

  • x:如果 mask == 1
  • y:如果 mask == 0

特点

  • ✅ 执行时间与 mask 值无关
  • ✅ 防止通过时序推断选择条件

示例

package main

import (
    "crypto/subtle"
    "fmt"
)

func main() {
    // mask = 1,选择 x
    result1 := subtle.ConstantTimeSelect(1, 100, 200)
    fmt.Println(result1) // 100
    
    // mask = 0,选择 y
    result2 := subtle.ConstantTimeSelect(0, 100, 200)
    fmt.Println(result2) // 200
}

5. ConstantTimeByteSelect - 恒定时间字节选择

func ConstantTimeByteSelect(mask int, x, y byte) byte

功能:恒定时间选择两个字节之一。

参数

  • mask:选择掩码(0 或 1)
  • x:如果 mask == 1,返回 x
  • y:如果 mask == 0,返回 y

返回值

  • x:如果 mask == 1
  • y:如果 mask == 0

示例

package main

import (
    "crypto/subtle"
    "fmt"
)

func main() {
    result1 := subtle.ConstantTimeByteSelect(1, 'A', 'B')
    fmt.Printf("%c\n", result1) // A
    
    result2 := subtle.ConstantTimeSelect(0, 'A', 'B')
    fmt.Printf("%c\n", result2) // B
}

6. ConstantTimeCopy - 恒定时间复制

func ConstantTimeCopy(mask int, x, y []byte)

功能:恒定时间复制字节切片。

参数

  • mask:复制掩码(0 或 1)
  • x:目标切片
  • y:源切片

行为

  • 如果 mask == 1x = y
  • 如果 mask == 0x 不变

特点

  • ✅ 执行时间与 mask 值无关
  • ✅ 即使不复制也会访问内存(防止缓存时序攻击)

示例

package main

import (
    "crypto/subtle"
    "fmt"
)

func main() {
    x := make([]byte, 5)
    y := []byte("hello")
    
    // 复制 y 到 x
    subtle.ConstantTimeCopy(1, x, y)
    fmt.Printf("x: %s\n", x) // hello
    
    // 不复制
    subtle.ConstantTimeCopy(0, x, y)
    fmt.Printf("x: %s\n", x) // hello (不变)
}

7. ConstantTimeCondCopy - 恒定时间条件复制

func ConstantTimeCondCopy(mask int, x, y []byte)

功能:根据条件恒定时间复制。

参数

  • mask:条件掩码
  • x:目标切片
  • y:源切片

行为

  • 如果 mask == 1:复制 yx
  • 如果 mask == 0x 不变

示例

package main

import (
    "crypto/subtle"
    "fmt"
)

func main() {
    x := make([]byte, 10)
    y := []byte("secret")
    
    // 条件复制
    subtle.ConstantTimeCondCopy(1, x, y)
    fmt.Printf("x: %s\n", x[:len(y)]) // secret
}

8. ConstantTimeLessOrEq - 恒定时间小于等于比较

func ConstantTimeLessOrEq(x, y int) int

功能:恒定时间判断 x <= y

参数

  • x:第一个整数
  • y:第二个整数

返回值

  • 1:如果 x <= y
  • 0:如果 x > y

示例

package main

import (
    "crypto/subtle"
    "fmt"
)

func main() {
    fmt.Println(subtle.ConstantTimeLessOrEq(5, 10))  // 1
    fmt.Println(subtle.ConstantTimeLessOrEq(10, 10)) // 1
    fmt.Println(subtle.ConstantTimeLessOrEq(15, 10)) // 0
}

9. ConstantTimeLess - 恒定时间小于比较

func ConstantTimeLess(x, y int) int

功能:恒定时间判断 x < y

参数

  • x:第一个整数
  • y:第二个整数

返回值

  • 1:如果 x < y
  • 0:如果 x >= y

示例

package main

import (
    "crypto/subtle"
    "fmt"
)

func main() {
    fmt.Println(subtle.ConstantTimeLess(5, 10))  // 1
    fmt.Println(subtle.ConstantTimeLess(10, 10)) // 0
    fmt.Println(subtle.ConstantTimeLess(15, 10)) // 0
}

10. ConstantTimeEq - 恒定时间相等比较(泛型)

func ConstantTimeEq[T comparable](x, y T) int

功能:恒定时间比较两个可比较类型的值。

参数

  • x:第一个值
  • y:第二个值

返回值

  • 1:如果 x == y
  • 0:如果 x != y

特点

  • Go 1.24+ 支持
  • 泛型版本
  • 适用于任何可比较类型

示例

package main

import (
    "crypto/subtle"
    "fmt"
)

func main() {
    // 整数比较
    fmt.Println(subtle.ConstantTimeEq(10, 10)) // 1
    fmt.Println(subtle.ConstantTimeEq(10, 20)) // 0
    
    // 字符串比较
    fmt.Println(subtle.ConstantTimeEq("hello", "hello")) // 1
    fmt.Println(subtle.ConstantTimeEq("hello", "world")) // 0
}

完整示例代码

示例 1:安全的 HMAC 验证

package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "crypto/subtle"
    "encoding/hex"
    "fmt"
    "log"
)

// HMACVerifier HMAC 验证器
type HMACVerifier struct {
    key []byte
}

// NewHMACVerifier 创建验证器
func NewHMACVerifier(key string) *HMACVerifier {
    return &HMACVerifier{
        key: []byte(key),
    }
}

// ComputeHMAC 计算 HMAC
func (v *HMACVerifier) ComputeHMAC(message string) []byte {
    h := hmac.New(sha256.New, v.key)
    h.Write([]byte(message))
    return h.Sum(nil)
}

// Verify 验证 HMAC(安全版本)
func (v *HMACVerifier) Verify(message, signature string) bool {
    // 计算期望的 HMAC
    expected := v.ComputeHMAC(message)
    
    // 解码提供的签名
    provided, err := hex.DecodeString(signature)
    if err != nil {
        return false
    }
    
    // 恒定时间比较
    return subtle.ConstantTimeCompare(expected, provided) == 1
}

func main() {
    verifier := NewHMACVerifier("my-secret-key")
    message := "Hello, World!"
    
    // 计算签名
    signature := hex.EncodeToString(verifier.ComputeHMAC(message))
    fmt.Printf("签名:%s\n", signature)
    
    // 验证正确签名
    if verifier.Verify(message, signature) {
        fmt.Println("✓ 签名验证成功")
    } else {
        log.Fatal("✗ 签名验证失败")
    }
    
    // 验证错误签名
    wrongSignature := "0000000000000000000000000000000000000000000000000000000000000000"
    if !verifier.Verify(message, wrongSignature) {
        fmt.Println("✓ 错误签名被正确拒绝")
    }
}

示例 2:安全的 API 密钥验证

package main

import (
    "crypto/rand"
    "crypto/sha256"
    "crypto/subtle"
    "encoding/hex"
    "fmt"
    "io"
    "log"
    "sync"
)

// APIKeyManager API 密钥管理器
type APIKeyManager struct {
    keys   map[string][]byte
    mu     sync.RWMutex
}

// NewAPIKeyManager 创建密钥管理器
func NewAPIKeyManager() *APIKeyManager {
    return &APIKeyManager{
        keys: make(map[string][]byte),
    }
}

// GenerateKey 生成新密钥
func (m *APIKeyManager) GenerateKey(userID string) (string, error) {
    // 生成随机密钥
    key := make([]byte, 32)
    if _, err := io.ReadFull(rand.Reader, key); err != nil {
        return "", err
    }
    
    // 存储密钥哈希(而不是明文)
    hash := sha256.Sum256(key)
    
    m.mu.Lock()
    m.keys[userID] = hash[:]
    m.mu.Unlock()
    
    // 返回明文密钥(仅显示一次)
    return hex.EncodeToString(key), nil
}

// VerifyKey 验证密钥(安全版本)
func (m *APIKeyManager) VerifyKey(userID, apiKey string) bool {
    // 解码提供的密钥
    keyBytes, err := hex.DecodeString(apiKey)
    if err != nil {
        // 即使解码失败也执行哈希(防止时序攻击)
        keyBytes = make([]byte, 32)
    }
    
    // 计算哈希
    hash := sha256.Sum256(keyBytes)
    
    m.mu.RLock()
    storedHash, exists := m.keys[userID]
    m.mu.RUnlock()
    
    if !exists {
        // 使用虚拟哈希进行比较(防止时序攻击)
        dummyHash := make([]byte, 32)
        return subtle.ConstantTimeCompare(hash[:], dummyHash) == 1 && false
    }
    
    // 恒定时间比较
    return subtle.ConstantTimeCompare(hash[:], storedHash) == 1
}

func main() {
    manager := NewAPIKeyManager()
    
    // 生成密钥
    userID := "user-123"
    apiKey, err := manager.GenerateKey(userID)
    if err != nil {
        log.Fatal(err)
    }
    
    fmt.Printf("API 密钥:%s\n", apiKey)
    
    // 验证正确密钥
    if manager.VerifyKey(userID, apiKey) {
        fmt.Println("✓ 密钥验证成功")
    } else {
        log.Fatal("✗ 密钥验证失败")
    }
    
    // 验证错误密钥
    wrongKey := "0000000000000000000000000000000000000000000000000000000000000000"
    if !manager.VerifyKey(userID, wrongKey) {
        fmt.Println("✓ 错误密钥被正确拒绝")
    }
}

示例 3:安全的密码比较

package main

import (
    "crypto/rand"
    "crypto/sha256"
    "crypto/subtle"
    "encoding/base64"
    "encoding/hex"
    "fmt"
    "io"
    "log"
)

// PasswordHasher 密码哈希器
type PasswordHasher struct{}

// Hash 哈希密码
func (h *PasswordHasher) Hash(password string) (string, error) {
    // 生成随机盐
    salt := make([]byte, 16)
    if _, err := io.ReadFull(rand.Reader, salt); err != nil {
        return "", err
    }
    
    // 哈希:SHA256(salt + password)
    hasher := sha256.New()
    hasher.Write(salt)
    hasher.Write([]byte(password))
    hash := hasher.Sum(nil)
    
    // 返回:salt:hash
    result := append(salt, hash...)
    return base64.StdEncoding.EncodeToString(result), nil
}

// Compare 比较密码(安全版本)
func (h *PasswordHasher) Compare(password, storedHash string) bool {
    // 解码存储的哈希
    data, err := base64.StdEncoding.DecodeString(storedHash)
    if err != nil {
        // 即使解码失败也继续(防止时序攻击)
        data = make([]byte, 48) // salt(16) + hash(32)
    }
    
    // 提取盐和哈希
    if len(data) < 48 {
        // 填充到正确长度
        padded := make([]byte, 48)
        copy(padded, data)
        data = padded
    }
    
    salt := data[:16]
    expectedHash := data[16:]
    
    // 计算提供的密码哈希
    hasher := sha256.New()
    hasher.Write(salt)
    hasher.Write([]byte(password))
    providedHash := hasher.Sum(nil)
    
    // 恒定时间比较
    return subtle.ConstantTimeCompare(providedHash, expectedHash) == 1
}

func main() {
    hasher := &PasswordHasher{}
    password := "my-secure-password"
    
    // 哈希密码
    hash, err := hasher.Hash(password)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("哈希:%s\n", hash)
    
    // 验证正确密码
    if hasher.Compare(password, hash) {
        fmt.Println("✓ 密码验证成功")
    } else {
        log.Fatal("✗ 密码验证失败")
    }
    
    // 验证错误密码
    if !hasher.Compare("wrong-password", hash) {
        fmt.Println("✓ 错误密码被正确拒绝")
    }
}

示例 4:恒定时间 AES S-Box 查找

package main

import (
    "crypto/subtle"
    "fmt"
)

// AES S-Box(简化版本,仅用于演示)
var sbox = [256]byte{
    0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5,
    // ... 实际 S-Box 有 256 个条目
}

// ConstantTimeSBoxLookup 恒定时间 S-Box 查找
func ConstantTimeSBoxLookup(index byte) byte {
    result := byte(0)
    
    // 恒定时间查找:遍历所有条目
    for i := 0; i < 256; i++ {
        // 如果 i == index,选择 sbox[i],否则选择 0
        mask := subtle.ConstantTimeByteEq(byte(i), index)
        selected := subtle.ConstantTimeByteSelect(mask, sbox[i], 0)
        result |= selected
    }
    
    return result
}

func main() {
    // 测试 S-Box 查找
    index := byte(0x00)
    result := ConstantTimeSBoxLookup(index)
    fmt.Printf("S-Box[%02x] = %02x\n", index, result)
    
    // 所有查找操作时间相同
    for i := 0; i < 5; i++ {
        result := ConstantTimeSBoxLookup(byte(i))
        fmt.Printf("S-Box[%02x] = %02x\n", i, result)
    }
}

示例 5:安全的密钥派生验证

package main

import (
    "crypto/rand"
    "crypto/sha256"
    "crypto/subtle"
    "encoding/hex"
    "fmt"
    "io"
    "log"
)

// KeyDerivation 密钥派生验证
type KeyDerivation struct {
    masterKey []byte
}

// NewKeyDerivation 创建密钥派生
func NewKeyDerivation() (*KeyDerivation, error) {
    masterKey := make([]byte, 32)
    if _, err := io.ReadFull(rand.Reader, masterKey); err != nil {
        return nil, err
    }
    return &KeyDerivation{masterKey: masterKey}, nil
}

// DeriveKey 派生子密钥
func (k *KeyDerivation) DeriveKey(context []byte) []byte {
    hasher := sha256.New()
    hasher.Write(k.masterKey)
    hasher.Write(context)
    return hasher.Sum(nil)
}

// VerifyKey 验证派生密钥(安全版本)
func (k *KeyDerivation) VerifyKey(context, providedKey []byte) bool {
    expectedKey := k.DeriveKey(context)
    
    // 即使长度不同也要恒定时间比较
    if len(providedKey) != len(expectedKey) {
        // 使用虚拟值进行比较
        dummyKey := make([]byte, len(expectedKey))
        return subtle.ConstantTimeCompare(expectedKey, dummyKey) == 1 && false
    }
    
    return subtle.ConstantTimeCompare(expectedKey, providedKey) == 1
}

func main() {
    kd, err := NewKeyDerivation()
    if err != nil {
        log.Fatal(err)
    }
    
    // 派生密钥
    context := []byte("encryption-key")
    key := kd.DeriveKey(context)
    
    fmt.Printf("派生密钥:%s\n", hex.EncodeToString(key))
    
    // 验证正确密钥
    if kd.VerifyKey(context, key) {
        fmt.Println("✓ 密钥验证成功")
    }
    
    // 验证错误密钥
    wrongKey := make([]byte, 32)
    if !kd.VerifyKey(context, wrongKey) {
        fmt.Println("✓ 错误密钥被正确拒绝")
    }
}

时序攻击案例分析

案例 1:不安全的字符串比较

// ❌ 不安全:早期退出
func insecureCompare(a, b string) bool {
    if len(a) != len(b) {
        return false // 立即返回
    }
    for i := 0; i < len(a); i++ {
        if a[i] != b[i] {
            return false // 第一个不匹配位置泄露
        }
    }
    return true
}

// 攻击过程:
// 1. 发送 "a",测量时间 t1
// 2. 发送 "b",测量时间 t2
// 3. 如果 t2 > t1,说明 "b" 的第一个字节正确
// 4. 重复,逐字节推断整个密钥

案例 2:不安全的 MAC 验证

// ❌ 不安全:使用 == 比较
func insecureVerifyMAC(expected, provided []byte) bool {
    return string(expected) == string(provided)
}

// ✅ 安全:使用恒定时间比较
func secureVerifyMAC(expected, provided []byte) bool {
    return subtle.ConstantTimeCompare(expected, provided) == 1
}

最佳实践

✅ 推荐做法

  1. 始终使用恒定时间比较敏感数据

    // ✅ 推荐
    if subtle.ConstantTimeCompare(a, b) == 1 {
        // ...
    }
    
    // ❌ 避免
    if bytes.Equal(a, b) {
        // ...
    }
    
  2. 即使失败也要执行完整操作

    // ✅ 推荐:始终计算哈希
    hash := computeHash(data)
    if subtle.ConstantTimeCompare(hash, expected) == 1 {
        return true
    }
    return false
    
  3. 处理长度不同的情况

    // ✅ 推荐:处理长度差异
    if len(provided) != len(expected) {
        dummy := make([]byte, len(expected))
        return subtle.ConstantTimeCompare(expected, dummy) == 1 && false
    }
    return subtle.ConstantTimeCompare(expected, provided) == 1
    

❌ 不安全做法

  1. 不要使用普通比较

    // ❌ 绝对不要
    if a == b { }
    if bytes.Equal(a, b) { }
    if string(a) == string(b) { }
    
  2. 不要早期退出

    // ❌ 绝对不要
    for i := 0; i < len(a); i++ {
        if a[i] != b[i] {
            return false // 泄露位置信息
        }
    }
    
  3. 不要根据秘密值改变执行路径

    // ❌ 绝对不要
    if secretByte == 0 {
        // 快速路径
    } else {
        // 慢速路径
    }
    

总结

核心 API

// 比较操作
ConstantTimeCompare(x, y []byte) int      // 字节切片比较
ConstantTimeByteEq(x, y byte) int         // 字节比较
ConstantTimeIntEq(x, y int) int           // 整数比较
ConstantTimeEq[T](x, y T) int             // 泛型比较(Go 1.24+)

// 选择操作
ConstantTimeSelect(mask, x, y int) int    // 整数选择
ConstantTimeByteSelect(mask, x, y byte) byte // 字节选择

// 复制操作
ConstantTimeCopy(mask int, x, y []byte)   // 字节切片复制

// 比较操作(不等式)
ConstantTimeLessOrEq(x, y int) int        // <= 比较
ConstantTimeLess(x, y int) int            // < 比较

使用场景

场景推荐函数说明
HMAC 验证ConstantTimeCompare防止时序攻击
密码比较ConstantTimeCompare安全验证
API 密钥验证ConstantTimeCompare防止密钥泄露
条件选择ConstantTimeSelect恒定时间分支
条件复制ConstantTimeCopy恒定时间内存操作
S-Box 查找ConstantTimeByteSelect防止缓存时序攻击

关键要点

  1. 时序攻击是真实的威胁:攻击者可以通过测量执行时间推断秘密信息
  2. 恒定时间操作至关重要:执行时间不应依赖于秘密数据
  3. 仅用于底层密码学:普通应用应使用高级库(如 crypto/hmac
  4. 需要专业知识:错误使用可能导致安全漏洞
  5. 测试和验证:使用工具(如 dudect)验证恒定时间特性

相关包

  • crypto/hmac:内部使用 subtle.ConstantTimeCompare
  • crypto/cipher:恒定时间加密操作
  • encoding/hex:安全的十六进制解码

参考资料


最后更新:2026-04-03
Go 版本:Go 1.23+
安全状态:⚠️ 仅限专业密码学实现使用