{"record":{"id":"3296b513250f10be","repo":"golang/go","slug":"tls-invalid-server-finished-hash","errorCode":null,"errorMessage":"tls: invalid server finished hash","messagePattern":"tls: invalid server finished hash","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"src/crypto/tls/handshake_client_tls13.go","lineNumber":704,"sourceCode":"\n\t// finishedMsg is included in the transcript, but not until after we\n\t// check the client version, since the state before this message was\n\t// sent is used during verification.\n\tmsg, err := c.readHandshake(nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfinished, ok := msg.(*finishedMsg)\n\tif !ok {\n\t\tc.sendAlert(alertUnexpectedMessage)\n\t\treturn unexpectedMessageError(finished, msg)\n\t}\n\n\texpectedMAC := hs.suite.finishedHash(c.in.trafficSecret, hs.transcript)\n\tif !hmac.Equal(expectedMAC, finished.verifyData) {\n\t\tc.sendAlert(alertDecryptError)\n\t\treturn errors.New(\"tls: invalid server finished hash\")\n\t}\n\n\tif err := transcriptMsg(finished, hs.transcript); err != nil {\n\t\treturn err\n\t}\n\n\t// Derive secrets that take context through the server Finished.\n\n\ths.trafficSecret = hs.masterSecret.ClientApplicationTrafficSecret(hs.transcript)\n\tserverSecret := hs.masterSecret.ServerApplicationTrafficSecret(hs.transcript)\n\tif err := c.setReadTrafficSecret(hs.suite, QUICEncryptionLevelApplication, serverSecret, false); err != nil {\n\t\treturn err\n\t}\n\n\terr = c.config.writeKeyLog(keyLogLabelClientTraffic, hs.hello.random, hs.trafficSecret)\n\tif err != nil {\n\t\tc.sendAlert(alertInternalError)\n\t\treturn err","sourceCodeStart":686,"sourceCodeEnd":722,"githubUrl":"https://github.com/golang/go/blob/b6b368adc57c96c3151d224d172029f233ead2c3/src/crypto/tls/handshake_client_tls13.go#L686-L722","documentation":"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.","triggerScenarios":"hmac.Equal(expectedMAC, finished.verifyData) is false. Reached for every TLS 1.3 handshake after establishing handshake keys.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Finished MAC failure is a handshake-integrity break; treat as security-relevant.\nif err := conn.Handshake(); err != nil {\n    if strings.Contains(err.Error(), \"invalid server finished hash\") {\n        securityLog.Printf(\"handshake integrity failure with %s: %v\", addr, err)\n    }\n    return err\n}","preventionTips":["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."],"tags":["tls","tls13","handshake","finished","mac","integrity","security","mitm","network","go"],"analyzedSha":"b6b368adc57c96c3151d224d172029f233ead2c3","analyzedAt":"2026-08-12T00:22:02.250Z","schemaVersion":2},"datasetVersion":"2026-08-12T12:17:08.281Z"}