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 err

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Treat as a security-relevant failure; do not retry automatically against the same peer without investigation.
  2. Capture the full handshake and verify no messages were altered between ClientHello and Finished.
  3. Rule out TLS-intercepting proxies/firewalls that rewrite handshake bytes.
  4. 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

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

Related errors


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