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

  1. Check for any TLS-intercepting proxy, WAF, or load balancer that modifies handshake messages.
  2. Test with a reference TLS 1.3 client to rule out client-side bugs.
  3. If using a custom crypto/tls fork, verify transcript hash and key schedule implementations.
  4. Run with go test -race to detect data races on the transcript or traffic secret.
  5. 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

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

Related errors


AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12). Data as JSON: /api/errors/2c2977af6e8c2b59. Report an issue: GitHub.