grpc/grpc-go · error

protocol

Error message

protocol %q: %v

What it means

Returned by conn.NewConnWithMaxFrameSize when the ALTSRecordFunc for the negotiated protocol was found and invoked, but returned an error during construction of the crypto instance. The wrapped error (%v) is the underlying crypto error. For the built-in ALTSRP_GCM_AES128_REKEY this means conn.NewAES128GCMRekey(side, key) failed.

Solutions

  1. Inspect the wrapped error message — for AES128GCMRekey it usually indicates a key-length problem; compare against the expected 44 bytes (keyLength map).
  2. Verify the GCP handshaker service and grpc-go versions are compatible.
  3. File a grpc-go issue with the wrapped error if the handshaker service is the standard GCP one and key data appears malformed.
  4. If using a custom ALTSRecordFunc, debug its constructor's validation logic.
Defensive patterns

Strategy: try-catch

Try / catch

// Crypto construction failures during ALTS setup are unrecoverable for the connection.
// Log the wrapped error and fail the RPC; gRPC will reconnect.
if err != nil {
    log.Printf("ALTS crypto setup failed: %v", err)
    return err
}

Prevention

When it happens

Trigger: The ALTS handshake delivers a key of incorrect length or invalid content for AES-128-GCM-Rekey, so NewAES128GCMRekey returns an error when deriving counters or setting up the AEAD. Reached during doHandshake -> NewConnWithMaxFrameSize on GCP.

Common situations: The handshaker service returned truncated or malformed key data; a custom ALTS record implementation with a bug in its constructor; memory corruption on the handshaker RPC; an interoperability issue with a non-Go handshaker that produces keys of a different size.

Related errors


AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11). Data as JSON: /api/errors/4052dafda2418cf4. Report an issue: GitHub.

Appendix: source

Thrown at credentials/alts/internal/conn/record.go:151

	constPool constBufferPool // stored as a field to avoid heap allocations.
}

// NewConn creates a new secure channel instance given the other party role and
// handshaking result.
func NewConn(c net.Conn, side core.Side, recordProtocol string, key []byte, protected []byte) (net.Conn, error) {
	return NewConnWithMaxFrameSize(c, side, recordProtocol, key, protected, 0)
}

// NewConnWithMaxFrameSize creates a new secure channel instance given the
// other party role, handshaking result, and negotiated maximum frame size.
func NewConnWithMaxFrameSize(c net.Conn, side core.Side, recordProtocol string, key []byte, protected []byte, negotiatedMaxFrameSize int) (net.Conn, error) {
	newCrypto := protocols[recordProtocol]
	if newCrypto == nil {
		return nil, fmt.Errorf("negotiated unknown next_protocol %q", recordProtocol)
	}
	crypto, err := newCrypto(side, key)
	if err != nil {
		return nil, fmt.Errorf("protocol %q: %v", recordProtocol, err)
	}
	overhead := MsgLenFieldSize + msgTypeFieldSize + crypto.EncryptionOverhead()

	// Clamp maxRecordLen to be at least altsRecordDefaultLength.
	maxRecordLen := max(altsRecordDefaultLength, negotiatedMaxFrameSize)
	payloadLengthLimit := maxRecordLen - overhead
	// We pre-allocate protected to be of size 32KB during initialization.
	// We increase the size of the buffer by the required amount if it can't
	// hold a complete encrypted record.
	protectedHandle := readBufPool.Get(max(altsReadBufferInitialSize, len(protected)))
	protectedBuf := *protectedHandle
	// Copy additional data from hanshaker service.
	copy(protectedBuf, protected)
	protectedBuf = protectedBuf[:len(protected)]

	altsConn := &conn{
		Conn:               c,
		reader:             readyreader.New(c),

View on GitHub (pinned to 0c51461d27)