go / expert
Snippet
Timing-Safe Token Verification with crypto/subtle
A naive comparison like bytes.Equal or == on two strings returns as soon as it finds a difference. Network attackers can measure that early exit and brute-force a MAC one byte at a time — extending the matching prefix as response latency grows. subtle.ConstantTimeCompare performs the same comparison but always reads every byte and folds the result into a single accumulator, so execution time depends only on length, not on content. It returns 1 for equal and 0 otherwise. Use it for HMAC tags, password hashes, session tokens, and any other secret-bearing byte sequence.
snippet.go
go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package mainimport ("crypto/hmac""crypto/sha256""crypto/subtle""fmt")func sign(key, msg []byte) []byte {m := hmac.New(sha256.New, key)m.Write(msg)return m.Sum(nil)}func verify(provided, expected []byte) bool {// bytes.Equal short-circuits on the first mismatch and// would leak the matching prefix length via wall-clock// timing. ConstantTimeCompare always touches every byte.return subtle.ConstantTimeCompare(provided, expected) == 1}func main() {key := []byte("server-secret")want := sign(key, []byte("user=42"))got := make([]byte, len(want)) // attacker's guessfmt.Println("ok:", verify(got, want))}
Breakdown
1
m := hmac.New(sha256.New, key)
hmac.New binds the secret to a hash construction; the resulting tag is unforgeable without key, which is exactly what the verifier must check.
2
subtle.ConstantTimeCompare(provided, expected)
Iterates over every byte with XOR-and-OR accumulation. Different lengths return 0 immediately, but equal-length inputs always cost the same.
3
== 1
The function returns int (1 or 0) rather than bool — explicit comparison to 1 documents the constant-time contract at the call site.
4
got := make([]byte, len(want))
An all-zero candidate stands in for an attacker's guess; in production this would arrive from the wire as the X-Signature header.