gchq/CyberChef · error · OperationError

Not handshake data.

Error message

Not handshake data.

What it means

JA3Fingerprint.run() reads the first byte of the TLS record and requires 0x16 (Handshake content type). Anything else (or undefined when the stream is empty, since Stream.readInt returns undefined past the end) throws. OperationError surfaced as step output.

Source

Thrown at src/core/operations/JA3Fingerprint.mjs:64

                value: ["Hash digest", "JA3 string", "Full details"]
            }
        ];
    }

    /**
     * @param {string} input
     * @param {Object[]} args
     * @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 !== 1)
            throw new OperationError("Not a Client Hello.");

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

View on GitHub (pinned to 4290ea7539)

Solutions

  1. Verify the first byte equals 0x16; if not, re-slice the input to the record boundary.
  2. Make the inputFormat selector match the actual data (Hex/Base64/Raw).
  3. Capture/extract only the ClientHello record, not the whole session.
  4. If the data is application data (0x17) or another content type, you need a different starting point.

Example fix

// before
ja3.run('GET / HTTP/1.1\r\n', ['Latin1','Base64']); // first byte 'G'=0x47 -> Not handshake data.
// after
const rec = hexStartingWith16; // begins 16 03 01 ...
ja3.run(rec, ['Hex','Base64']);
Defensive patterns

Strategy: validation

Validate before calling

import Utils from "src/core/Utils.mjs";
function assertTlsHandshakeRecord(input, inputFormat) {
  const bytes = Utils.convertToByteArray(input, inputFormat);
  if (bytes.length < 1) throw new Error('empty input');
  if (bytes[0] !== 0x16) {
    throw new Error(`First byte 0x${bytes[0].toString(16)} is not a TLS Handshake (0x16). Check inputFormat and record boundary.`);
  }
  return bytes;
}

Type guard

function looksLikeTlsHandshake(bytes) {
  return bytes.length >= 5 && bytes[0] === 0x16 && bytes[1] === 0x03;
}

Prevention

When it happens

Trigger: Input is not a TLS record (HTTP, random bytes), or inputFormat does not match the data so convertToByteArray produced wrong bytes. Also when feeding a record that does not start at a record boundary (mid-stream) or an application-data record (0x17).

Common situations: Captured a full TCP stream instead of just the ClientHello; selected 'Hex' but pasted raw text (or vice versa); fed a ServerHello (JA3 not JA3S); started reading after the content-type byte.

Understand the failure class

Related errors


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