golang/go · critical
tls: invalid client finished hash
Error message
tls: invalid client finished hash
What it means
The server received the client's Finished message but the HMAC verifyData didn't match the server's independently computed hs.clientFinished value. The Finished message (RFC 8446 §4.4.4) is an HMAC over the entire handshake transcript using the finished_key derived from the handshake traffic secret. A mismatch means the client and server disagree on the transcript or derived keys.
Source
Thrown at src/crypto/tls/handshake_server_tls13.go:1139
func (hs *serverHandshakeStateTLS13) readClientFinished() error {
c := hs.c
// finishedMsg is not included in the transcript.
msg, err := c.readHandshake(nil)
if err != nil {
return err
}
finished, ok := msg.(*finishedMsg)
if !ok {
c.sendAlert(alertUnexpectedMessage)
return unexpectedMessageError(finished, msg)
}
if !hmac.Equal(hs.clientFinished, finished.verifyData) {
c.sendAlert(alertDecryptError)
return errors.New("tls: invalid client finished hash")
}
if err := c.setReadTrafficSecret(hs.suite, QUICEncryptionLevelApplication, hs.trafficSecret, false); err != nil {
return err
}
return nil
}
View on GitHub (pinned to b6b368adc5)
Solutions
- Check for any TLS-intercepting proxy, WAF, or load balancer that modifies handshake messages.
- Test with a reference TLS 1.3 client to rule out client-side bugs.
- If using a custom crypto/tls fork, verify transcript hash and key schedule implementations.
- Run with go test -race to detect data races on the transcript or traffic secret.
- Capture a full packet trace and verify handshake message integrity end-to-end.
Defensive patterns
Strategy: try-catch
Validate before calling
// No caller-side validation can prevent this — it's a transcript/key divergence.
// Ensure no proxy modifies handshake messages.
// Verify the TLS configuration is standard:
func validateTLSConfig(cfg *tls.Config) error {
// Ensure no custom/incompatible settings that could cause transcript divergence
if cfg.InsecureSkipVerify {
// Not directly related but indicates non-standard config
}
return nil
} Try / catch
if err := conn.Handshake(); err != nil {
if strings.Contains(err.Error(), "invalid client finished hash") {
log.Printf("handshake transcript divergence detected: %v", err)
// This often indicates a MITM or a serious bug — investigate
}
} Prevention
- Ensure no TLS-intercepting proxy or WAF modifies handshake messages.
- Use standard TLS libraries on both sides.
- Run go test -race to catch data races on transcript state.
- Monitor this error — it can indicate active MITM attacks.
When it happens
Trigger: Server calls hmac.Equal(hs.clientFinished, finished.verifyData) and they differ. This means the client computed a different Finished MAC than expected, indicating the two sides have divergent handshake transcripts or traffic secrets.
Common situations: A buggy MITM or TLS-terminating proxy that alters handshake messages; a client TLS library bug in transcript hashing or key derivation; memory corruption or a data race affecting the transcript hash; a network device that silently modifies handshake records; an extremely rare implementation bug in the client's HKDF or HMAC computation.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- tls: invalid server finished hash
- tls: client didn't send one key share in second ClientHello
- tls: client sent unexpected key share in second ClientHello
- tls: client illegally modified second ClientHello
- tls: failed to sign handshake: %s
AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12).
Data as JSON: /api/errors/2c2977af6e8c2b59.
Report an issue: GitHub.