golang/go · error

tls: failed to decrypt second client hello encrypted client

Error message

tls: failed to decrypt second client hello encrypted client hello extension payload

What it means

After the HPKE context is established from ClientHello1, decrypting the ECH payload in ClientHello2 fails (decryptECHPayload error). The ciphertext is wrong, the HPKE context diverged, or the bytes were tampered with. The server sends decrypt_error.

Source

Thrown at src/crypto/tls/handshake_server_tls13.go:619

			c.sendAlert(alertDecodeError)
			return nil, errors.New("tls: client sent invalid encrypted client hello extension")
		}

		if echType == outerECHExt && hs.echContext.inner || echType == innerECHExt && !hs.echContext.inner {
			c.sendAlert(alertDecodeError)
			return nil, errors.New("tls: unexpected switch in encrypted client hello extension type")
		}

		if echType == outerECHExt {
			if echCiphersuite != hs.echContext.ciphersuite || configID != hs.echContext.configID || len(encap) != 0 {
				c.sendAlert(alertIllegalParameter)
				return nil, errors.New("tls: second client hello encrypted client hello extension does not match")
			}

			encodedInner, err := decryptECHPayload(hs.echContext.hpkeContext, clientHello.original, payload)
			if err != nil {
				c.sendAlert(alertDecryptError)
				return nil, errors.New("tls: failed to decrypt second client hello encrypted client hello extension payload")
			}

			echInner, err := decodeInnerClientHello(clientHello, encodedInner)
			if err != nil {
				c.sendAlert(alertIllegalParameter)
				return nil, errors.New("tls: client sent invalid encrypted client hello extension")
			}

			clientHello = echInner
		}
	}

	if len(clientHello.keyShares) != 1 {
		c.sendAlert(alertIllegalParameter)
		return nil, errors.New("tls: client didn't send one key share in second ClientHello")
	}
	ks := &clientHello.keyShares[0]

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Ensure the ECH HPKE context is consistent between ClientHello1 and ClientHello2
  2. Verify client and server use the same HPKE KEM/KDF/AEAD algorithms
  3. Update the ECH library on both sides to the same draft revision
Defensive patterns

Strategy: try-catch

Try / catch

if err := tlsConn.Handshake(); err != nil {
    if strings.Contains(err.Error(), "failed to decrypt second client hello encrypted client hello") {
        log.Printf("ECH payload decrypt failed on retry from %v", remote)
    }
    c.Close()
    return
}

Prevention

When it happens

Trigger: ClientHello2's ECH payload cannot be decrypted with the HPKE context from ClientHello1. Tampering, a client bug, or an HPKE algorithm/context mismatch.

Common situations: ECH client bug, network corruption, attacker tampering, HPKE KEM/KDF/AEAD mismatch between client and server.

Understand the failure class

Related errors


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