golang/go · error

tls: server selected an invalid PSK and cipher suite pair

Error message

tls: server selected an invalid PSK and cipher suite pair

What it means

Server selected a PSK whose original cipher suite hash does not match the hash of the cipher suite negotiated for this handshake. RFC 8446 §4.2.11 mandates the binder hash be consistent. Go sends `illegal_parameter`. Indicates inconsistent session resumption bookkeeping.

Source

Thrown at src/crypto/tls/handshake_client_tls13.go:459

	if !hs.serverHello.selectedIdentityPresent {
		return nil
	}

	if int(hs.serverHello.selectedIdentity) >= len(hs.hello.pskIdentities) {
		c.sendAlert(alertIllegalParameter)
		return errors.New("tls: server selected an invalid PSK")
	}

	if len(hs.hello.pskIdentities) != 1 || hs.session == nil {
		return c.sendAlert(alertInternalError)
	}
	pskSuite := cipherSuiteTLS13ByID(hs.session.cipherSuite)
	if pskSuite == nil {
		return c.sendAlert(alertInternalError)
	}
	if pskSuite.hash != hs.suite.hash {
		c.sendAlert(alertIllegalParameter)
		return errors.New("tls: server selected an invalid PSK and cipher suite pair")
	}

	hs.usingPSK = true
	c.didResume = true
	c.peerCertificates = hs.session.peerCertificates
	c.verifiedChains = hs.session.verifiedChains
	c.ocspResponse = hs.session.ocspResponse
	c.scts = hs.session.scts
	return nil
}

func (hs *clientHandshakeStateTLS13) establishHandshakeKeys() error {
	c := hs.c

	ke, err := keyExchangeForCurveID(hs.serverHello.serverShare.group)
	if err != nil {
		c.sendAlert(alertInternalError)
		return err

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Clear the client session cache (discard stale tickets) and retry the handshake fresh.
  2. Align server-side cipher suite preferences so resumed and initial handshakes use the same suite family.
  3. Disable 0-RTT/resumption temporarily to confirm the cause.
  4. Report to the server operator if it persists across fresh sessions.

Example fix

// before: stale tickets from a cipher-suite change cause mismatch
cfg := &tls.Config{ClientSessionCache: tls.NewLRUClientSessionCache(100)}

// after: flush cache so a fresh ticket is minted under the current suite
cfg := &tls.Config{ClientSessionCache: tls.NewLRUClientSessionCache(0)}
Defensive patterns

Strategy: validation

Validate before calling

// Pre-flight: pin cipher suites so resumed and initial handshakes use the same family.
cfg.CipherSuites = []uint16{
    tls.TLS_AES_128_GCM_SHA256,
    tls.TLS_AES_256_GCM_SHA384,
    tls.TLS_CHACHA20_POLY1305_SHA256,
}

Try / catch

if err := conn.Handshake(); err != nil {
    if strings.Contains(err.Error(), "invalid PSK and cipher suite pair") {
        cfg.ClientSessionCache = nil // stale ticket; retry fresh
        return retryHandshake(addr, cfg)
    }
}

Prevention

When it happens

Trigger: selectedIdentity is in range and the resumed session's cipher suite (looked up via cipherSuiteTLS13ByID) has a hash that differs from the negotiated suite's hash.

Common situations: Session ticket cached under one cipher suite then resumed under another (e.g., server changed its suite preferences), a server cluster with divergent configs, or a buggy server that ignores the resumption-cipher-suite binding.

Understand the failure class

Related errors


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