golang/go · critical

tls: certificate private key does not implement crypto.Decry

Error message

tls: certificate private key does not implement crypto.Decrypter

What it means

During TLS 1.0–1.2 RSA key exchange, the server's certificate private key does not implement the crypto.Decrypter interface. The RSA key agreement requires decrypting the client's encrypted pre-master secret using priv.Decrypt(). If the private key type (even though it's an RSA key) doesn't implement crypto.Decrypter, decryption cannot proceed. Standard *rsa.PrivateKey implements crypto.Decrypter, so this error implies a custom key type.

Source

Thrown at src/crypto/tls/key_agreement.go:62

type rsaKeyAgreement struct{}

func (ka rsaKeyAgreement) generateServerKeyExchange(config *Config, cert *Certificate, clientHello *clientHelloMsg, hello *serverHelloMsg) (*serverKeyExchangeMsg, error) {
	return nil, nil
}

func (ka rsaKeyAgreement) processClientKeyExchange(config *Config, cert *Certificate, ckx *clientKeyExchangeMsg, version uint16) ([]byte, error) {
	if len(ckx.ciphertext) < 2 {
		return nil, errClientKeyExchange
	}
	ciphertextLen := int(ckx.ciphertext[0])<<8 | int(ckx.ciphertext[1])
	if ciphertextLen != len(ckx.ciphertext)-2 {
		return nil, errClientKeyExchange
	}
	ciphertext := ckx.ciphertext[2:]

	priv, ok := cert.PrivateKey.(crypto.Decrypter)
	if !ok {
		return nil, errors.New("tls: certificate private key does not implement crypto.Decrypter")
	}
	// Perform constant time RSA PKCS #1 v1.5 decryption
	preMasterSecret, err := priv.Decrypt(config.rand(), ciphertext, &rsa.PKCS1v15DecryptOptions{SessionKeyLen: 48})
	if err != nil {
		return nil, err
	}
	// We don't check the version number in the premaster secret. For one,
	// by checking it, we would leak information about the validity of the
	// encrypted pre-master secret. Secondly, it provides only a small
	// benefit against a downgrade attack and some implementations send the
	// wrong version anyway. See the discussion at the end of section
	// 7.4.7.1 of RFC 4346.
	return preMasterSecret, nil
}

func (ka rsaKeyAgreement) processServerKeyExchange(config *Config, clientHello *clientHelloMsg, serverHello *serverHelloMsg, cert *x509.Certificate, skx *serverKeyExchangeMsg) error {
	return errors.New("tls: unexpected ServerKeyExchange")
}

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Use a standard *rsa.PrivateKey loaded via tls.LoadX509KeyPair or x509.ParsePKCS1PrivateKey.
  2. If using a custom key type, implement crypto.Decrypter (Decrypt method) on it.
  3. For HSM keys: ensure the HSM/PKCS#11 provider supports RSA decryption and exposes it via crypto.Decrypter.
  4. Switch to an ECDHE cipher suite that only requires signing (crypto.Signer), not decryption.
  5. Provide the key via a tls.Certificate where PrivateKey fully implements crypto.Decrypter.

Example fix

// before: custom key type only implements crypto.Signer
cert := tls.Certificate{
    PrivateKey: myCustomSignerOnlyKey,
}
// after: implement crypto.Decrypter or use standard key
cert := tls.Certificate{
    PrivateKey: parsedRSAKey, // *rsa.PrivateKey implements crypto.Decrypter
}
// Or implement Decrypt on your custom type:
// func (k *CustomKey) Decrypt(rand io.Reader, msg []byte, opts crypto.DecrypterOpts) ([]byte, error) { ... }
Defensive patterns

Strategy: type-guard

Validate before calling

// Verify the private key implements crypto.Decrypter before starting the server
func verifyDecrypter(cert *tls.Certificate) error {
    _, ok := cert.PrivateKey.(crypto.Decrypter)
    if !ok {
        return errors.New("private key must implement crypto.Decrypter for RSA key exchange")
    }
    return nil
}

Type guard

func isDecrypter(key any) bool {
    _, ok := key.(crypto.Decrypter)
    return ok
}

Try / catch

// Check at server startup
if err := verifyDecrypter(&cert); err != nil {
    log.Fatal("certificate key not suitable for RSA key exchange: ", err)
}
// Or handle at handshake time:
if err := conn.Handshake(); err != nil {
    if strings.Contains(err.Error(), "crypto.Decrypter") {
        log.Printf("key type issue: %v", err)
    }
}

Prevention

When it happens

Trigger: Server calls rsaKeyAgreement.processClientKeyExchange and attempts cert.PrivateKey.(crypto.Decrypter). The type assertion fails because the private key is a custom type that wraps an RSA key but doesn't expose the Decrypt method, or the key is stored in a way (e.g. some HSM integrations) that only implements crypto.Signer but not crypto.Decrypter.

Common situations: Custom private key type (e.g. PKCS#11 wrapper, cloud KMS proxy) that implements crypto.Signer but not crypto.Decrypter; an HSM-backed key that only supports signing; a key loaded from an unusual source that doesn't fully implement the standard interfaces.

Understand the failure class

Related errors


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