fatedier/frp · error

invalid crypto client random length %d, want %d

Error message

invalid crypto client random length %d, want %d

What it means

ValidateCryptoCapabilities rejected a ClientHello because its ClientRandom is not exactly CryptoRandomSize (32) bytes. The 32-byte random is a hard protocol requirement — it feeds the transcript hash used to derive session keys, so a wrong length breaks key derivation.

Source

Thrown at pkg/proto/wire/crypto.go:84

		return ServerHello{}, err
	}
	return ServerHello{
		Selected: ServerSelection{
			Message: MessageSelection{
				Codec:          MessageCodecJSON,
				UDPPacketCodec: selectUDPPacketCodec(clientHello.Capabilities.Message.UDPPacketCodecs),
			},
			Crypto: CryptoSelection{
				Algorithm:    algorithm,
				ServerRandom: serverRandom,
			},
		},
	}, nil
}

func ValidateCryptoCapabilities(c CryptoCapabilities) error {
	if len(c.ClientRandom) != CryptoRandomSize {
		return fmt.Errorf("invalid crypto client random length %d, want %d", len(c.ClientRandom), CryptoRandomSize)
	}
	if _, ok := SelectAEADAlgorithm(c.Algorithms); !ok {
		return fmt.Errorf("no supported crypto algorithm")
	}
	return nil
}

func ValidateServerHelloForClient(clientHello ClientHello, serverHello ServerHello) error {
	if serverHello.Selected.Message.Codec != MessageCodecJSON {
		return fmt.Errorf("unsupported selected message codec: %s", serverHello.Selected.Message.Codec)
	}
	udpPacketCodec := serverHello.Selected.Message.UDPPacketCodec
	if udpPacketCodec != "" {
		if udpPacketCodec != UDPPacketCodecBinary {
			return fmt.Errorf("unsupported selected UDP packet codec: %s", udpPacketCodec)
		}
		if !Supports(clientHello.Capabilities.Message.UDPPacketCodecs, udpPacketCodec) {
			return fmt.Errorf("selected UDP packet codec was not advertised by client: %s", udpPacketCodec)

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Always create client random via NewClientHello / newCryptoRandom, which guarantees 32 bytes from crypto/rand
  2. If building hello manually, copy exactly 32 bytes: make([]byte, wire.CryptoRandomSize) filled by crypto/rand
  3. Align peer versions so the wire format matches

Example fix

// before
hello := clientHelloWithCryptoRandom(bootstrap, []byte("short")) // len 5

// after
r := make([]byte, wire.CryptoRandomSize) // 32
rand.Read(r)
hello := clientHelloWithCryptoRandom(bootstrap, r)
Defensive patterns

Strategy: validation

Validate before calling

if len(hello.Capabilities.Crypto.ClientRandom) != wire.CryptoRandomSize {
    return fmt.Errorf("client random must be %d bytes", wire.CryptoRandomSize)
}

Type guard

func hasValidClientRandom(c wire.CryptoCapabilities) bool {
    return len(c.ClientRandom) == wire.CryptoRandomSize
}

Try / catch

if err := wire.ValidateClientHello(hello); err != nil {
    return fmt.Errorf("dropping malformed hello: %w", err)
}

Prevention

When it happens

Trigger: Constructing or deserializing a ClientHello (or calling ValidateClientHello/ValidateCryptoCapabilities) where Capabilities.Crypto.ClientRandom has len != 32 — hand-built hello, truncated field, or a peer using a different protocol revision.

Common situations: Custom test code filling ClientRandom with a short constant; version skew changing the field's encoding; deserialization bug dropping bytes.

Related errors


AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15). Data as JSON: /api/errors/e41df29bfc79418d. Report an issue: GitHub.