gchq/CyberChef · error · OperationError

Not handshake data.

Error message

Not handshake data.

What it means

Thrown by parseTLSRecord() in TLS.mjs when the first byte of the record (the TLS content type) is not 0x16. Value 0x16 designates a Handshake record; this parser only decodes handshake records. Any other content type (0x14 ChangeCipherSpec, 0x15 Alert, 0x17 ApplicationData, 0x16 itself absent) or non-TLS data is rejected up front before further parsing.

Source

Thrown at src/core/lib/TLS.mjs:30

/**
 * Parse a TLS Record
 * @param {Uint8Array} bytes
 * @returns {JSON}
 */
export function parseTLSRecord(bytes) {
    const s = new Stream(bytes);
    const b = s.clone();
    const r = {};

    // Content type
    r.contentType = {
        description: "Content Type",
        length: 1,
        data: b.getBytes(1),
        value: s.readInt(1)
    };
    if (r.contentType.value !== 0x16)
        throw new OperationError("Not handshake data.");

    // Version
    r.version = {
        description: "Protocol Version",
        length: 2,
        data: b.getBytes(2),
        value: s.readInt(2)
    };

    // Length
    r.length = {
        description: "Record Length",
        length: 2,
        data: b.getBytes(2),
        value: s.readInt(2)
    };
    if (s.length !== r.length.value + 5)
        throw new OperationError("Incorrect handshake length.");

View on GitHub (pinned to 4290ea7539)

Solutions

  1. Confirm the input is the bytes of a single TLS handshake record whose first byte equals 0x16.
  2. If parsing a stream, locate each record by reading the 5-byte TLS record header (type, version, length) and only pass handshake (0x16) records to this function.
  3. If you actually need to parse Alert/ApplicationData, this parser does not support those — use a full TLS dissector.

Example fix

// before
parseTLSRecord(appDataRecordBytes); // first byte 0x17
// after: extract the handshake record first
const rec = findRecordByType(stream, 0x16);
parseTLSRecord(rec);
Defensive patterns

Strategy: validation

Validate before calling

function isHandshakeRecord(bytes) {
    return bytes instanceof Uint8Array && bytes.length >= 1 && bytes[0] === 0x16;
}
if (!isHandshakeRecord(bytes)) {
    throw new Error(
        `Input is not a TLS handshake record (content type byte = 0x${(bytes[0] ?? 0).toString(16)}). ` +
        `parseTLSRecord only decodes handshake (0x16) records.`
    );
}
const r = parseTLSRecord(bytes);

Type guard

function isHandshakeRecord(bytes) {
    return (bytes instanceof Uint8Array || Array.isArray(bytes)) &&
        bytes.length >= 1 && bytes[0] === 0x16;
}

Try / catch

try {
    record = parseTLSRecord(bytes);
} catch (e) {
    if (e instanceof OperationError && /Not handshake data/.test(e.message)) {
        // skip non-handshake records when walking a stream
        continue;
    }
    throw e;
}

Prevention

When it happens

Trigger: Calling parseTLSRecord() with bytes that are not a TLS handshake record: an ApplicationData record (0x17), an Alert (0x15), a full capture where the handshake is buried mid-stream, raw application traffic, or unrelated binary data. Also when the byte order is reversed or a TLS 1.3 encrypted ClientHello is fed in after encryption.

Common situations: Parsing a packet capture from the wrong record offset; feeding the entire TCP stream rather than the first record; trying to parse a ServerHello fragment that arrived alone after the ClientHello; using the parser on DTLS or QUIC framing which differ.

Understand the failure class

Related errors


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