golang/go · critical
tls: invalid server finished hash
Error message
tls: invalid server finished hash
What it means
readServerFinished compares the server's Finished message verify_data against the HMAC the client computes over the transcript with the handshake traffic secret. A mismatch means the server does not know the handshake secret (failed key exchange, tampering) or the transcript diverged. Go sends `decrypt_error`. The Finished MAC is the final integrity check of the handshake.
Source
Thrown at src/crypto/tls/handshake_client_tls13.go:704
// finishedMsg is included in the transcript, but not until after we
// check the client version, since the state before this message was
// sent is used during verification.
msg, err := c.readHandshake(nil)
if err != nil {
return err
}
finished, ok := msg.(*finishedMsg)
if !ok {
c.sendAlert(alertUnexpectedMessage)
return unexpectedMessageError(finished, msg)
}
expectedMAC := hs.suite.finishedHash(c.in.trafficSecret, hs.transcript)
if !hmac.Equal(expectedMAC, finished.verifyData) {
c.sendAlert(alertDecryptError)
return errors.New("tls: invalid server finished hash")
}
if err := transcriptMsg(finished, hs.transcript); err != nil {
return err
}
// Derive secrets that take context through the server Finished.
hs.trafficSecret = hs.masterSecret.ClientApplicationTrafficSecret(hs.transcript)
serverSecret := hs.masterSecret.ServerApplicationTrafficSecret(hs.transcript)
if err := c.setReadTrafficSecret(hs.suite, QUICEncryptionLevelApplication, serverSecret, false); err != nil {
return err
}
err = c.config.writeKeyLog(keyLogLabelClientTraffic, hs.hello.random, hs.trafficSecret)
if err != nil {
c.sendAlert(alertInternalError)
return errView on GitHub (pinned to b6b368adc5)
Solutions
- Treat as a security-relevant failure; do not retry automatically against the same peer without investigation.
- Capture the full handshake and verify no messages were altered between ClientHello and Finished.
- Rule out TLS-intercepting proxies/firewalls that rewrite handshake bytes.
- Report to the server operator if the failure reproduces against the server directly with no proxy in path.
Defensive patterns
Strategy: try-catch
Try / catch
// Finished MAC failure is a handshake-integrity break; treat as security-relevant.
if err := conn.Handshake(); err != nil {
if strings.Contains(err.Error(), "invalid server finished hash") {
securityLog.Printf("handshake integrity failure with %s: %v", addr, err)
}
return err
} Prevention
- Do not auto-retry on Finished MAC failures; investigate the peer and network path.
- Audit middleboxes that rewrite handshake bytes.
- Capture the full handshake when these errors cluster for forensic review.
- Prefer end-to-end TLS without TLS-terminating intermediaries for sensitive traffic.
When it happens
Trigger: hmac.Equal(expectedMAC, finished.verifyData) is false. Reached for every TLS 1.3 handshake after establishing handshake keys.
Common situations: Active MITM that cannot compute the secret, transcript tampering by a middlebox that altered earlier messages, a buggy server that signs the wrong transcript, or packet corruption surviving the record-layer MAC.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- tls: invalid signature by the server certificate: {err}
- tls: invalid server key share
- tls: server sent an unnecessary HelloRetryRequest key_share
- tls: server sent two HelloRetryRequest messages
- tls: server sent a cookie in a normal ServerHello
AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12).
Data as JSON: /api/errors/3296b513250f10be.
Report an issue: GitHub.