gchq/CyberChef · error · OperationError

Incorrect handshake length.

Error message

Incorrect handshake length.

What it means

After skipping version (2 bytes), JA3SFingerprint reads the 2-byte record length and requires total stream length === length + 5. A mismatch means trailing bytes (concatenated records) or a truncated record; readInt returning undefined on short input also satisfies the !== check.

Source

Thrown at src/core/operations/JA3SFingerprint.mjs:72

     * @returns {string}
     */
    run(input, args) {
        const [inputFormat, outputFormat] = args;

        input = Utils.convertToByteArray(input, inputFormat);
        const s = new Stream(new Uint8Array(input));

        const handshake = s.readInt(1);
        if (handshake !== 0x16)
            throw new OperationError("Not handshake data.");

        // Version
        s.moveForwardsBy(2);

        // Length
        const length = s.readInt(2);
        if (s.length !== length + 5)
            throw new OperationError("Incorrect handshake length.");

        // Handshake type
        const handshakeType = s.readInt(1);
        if (handshakeType !== 2)
            throw new OperationError("Not a Server Hello.");

        // Handshake length
        const handshakeLength = s.readInt(3);
        if (s.length !== handshakeLength + 9)
            throw new OperationError("Not enough data in Server Hello.");

        // Hello version
        const helloVersion = s.readInt(2);

        // Random
        s.moveForwardsBy(32);

        // Session ID

View on GitHub (pinned to 4290ea7539)

Solutions

  1. Feed exactly one record: trim so total === length+5.
  2. Confirm the record is not truncated.
  3. Match inputFormat so the length field parses correctly.
  4. Split multi-record captures into individual records.

Example fix

// before
ja3s.run(fullServerStream, ['Hex','Base64']); // length+5 < total -> Incorrect handshake length.
// after
const recLen = (bytes[3]<<8)|bytes[4];
const oneRecord = bytes.slice(0, recLen + 5);
ja3s.run(oneRecord, ['Hex','Base64']);
Defensive patterns

Strategy: validation

Validate before calling

import Utils from "src/core/Utils.mjs";
function assertSingleRecord(input, inputFormat) {
  const b = Utils.convertToByteArray(input, inputFormat);
  if (b.length < 5) throw new Error('record too short');
  const recLen = (b[3] << 8) | b[4];
  if (b.length !== recLen + 5) {
    throw new Error(`Stream length ${b.length} != record length ${recLen}+5. Feed exactly one TLS record.`);
  }
  return b;
}

Type guard

function isSingleTlsRecord(bytes) {
  if (bytes.length < 5) return false;
  const recLen = (bytes[3] << 8) | bytes[4];
  return bytes.length === recLen + 5;
}

Prevention

When it happens

Trigger: Multiple TLS records concatenated (length+5 < total), a truncated ServerHello record (length+5 > total), or inputFormat mismatch corrupting the length field.

Common situations: Pasting the whole server-side handshake (ServerHello + Certificate + ServerKeyExchange) instead of one record; truncated paste; inputFormat mismatch.

Understand the failure class

Related errors


AI-assisted analysis of gchq/CyberChef@4290ea7539 (2026-08-13). Data as JSON: /api/errors/ae7936022b554eae. Report an issue: GitHub.