gchq/CyberChef · error · OperationError

Not handshake data.

Error message

Not handshake data.

What it means

JA3SFingerprint.run() reads the first byte of the TLS record and requires 0x16 (Handshake). Anything else (or undefined for empty input) throws. OperationError surfaced as step output. Mirror of the JA3 check but for the server side.

Source

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

                value: ["Hash digest", "JA3S 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 !== 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.");

View on GitHub (pinned to 4290ea7539)

Solutions

  1. Verify the first byte is 0x16; re-slice to the record boundary if not.
  2. Match inputFormat to the real data (Hex/Base64/Raw).
  3. Extract only the ServerHello record.
  4. If the byte is 0x17 (application data), you are past the handshake.

Example fix

// before
ja3s.run('HTTP/1.1 200 OK\r\n', ['Latin1','Base64']); // 'H'=0x48 -> Not handshake data.
// after
const rec = serverHelloStartingWith16;
ja3s.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, inputFormat does not match the data (convertToByteArray yields wrong bytes), the record does not start at a record boundary, or an application-data/other content type was fed.

Common situations: Captured a full session instead of just the ServerHello; inputFormat selector mismatch; fed an HTTP response or random bytes; started reading mid-record.

Understand the failure class

Related errors


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