gchq/CyberChef · error · OperationError

Invalid nonce length: ${nonce.length} bytes. XSalsa20 uses

Error message

Invalid nonce length: ${nonce.length} bytes.

XSalsa20 uses a nonce of 24 bytes (192 bits).

What it means

Thrown by XSalsa20.run in the non-Integer nonce branch when the decoded nonce byte array is not exactly 24 bytes. XSalsa20 requires a 192-bit (24-byte) nonce; the 'Integer' option derives an 8-byte nonce from args[1].string and is handled separately, so this check only applies to user-supplied nonce formats (Hex/UTF8/Base64...).

Source

Thrown at src/core/operations/XSalsa20.mjs:91

        const key = Utils.convertToByteArray(args[0].string, args[0].option),
            nonceType = args[1].option,
            rounds = parseInt(args[3], 10),
            inputType = args[4],
            outputType = args[5];

        if (key.length !== 16 && key.length !== 32) {
            throw new OperationError(`Invalid key length: ${key.length} bytes.

XSalsa20 uses a key of 16 or 32 bytes (128 or 256 bits).`);
        }

        let counter, nonce;
        if (nonceType === "Integer") {
            nonce = Utils.intToByteArray(parseInt(args[1].string, 10), 8, "little");
        } else {
            nonce = Utils.convertToByteArray(args[1].string, args[1].option);
            if (!(nonce.length === 24)) {
                throw new OperationError(`Invalid nonce length: ${nonce.length} bytes.

XSalsa20 uses a nonce of 24 bytes (192 bits).`);
            }
        }
        counter = Utils.intToByteArray(args[2], 8, "little");

        const xsalsaKey = hsalsa20(key, nonce.slice(0, 16), rounds);

        const output = [];
        input = Utils.convertToByteArray(input, inputType);

        let counterAsInt = Utils.byteArrayToInt(counter, "little");
        for (let i = 0; i < input.length; i += 64) {
            counter = Utils.intToByteArray(counterAsInt, 8, "little");
            const stream = salsa20Block(xsalsaKey, nonce.slice(16, 24), counter, rounds);
            for (let j = 0; j < 64 && i + j < input.length; j++) {
                output.push(input[i + j] ^ stream[j]);
            }

View on GitHub (pinned to 4290ea7539)

Solutions

  1. Provide exactly 24 bytes of nonce (48 hex characters, 24 UTF8 bytes, or 32 Base64 chars).
  2. Confirm args[1].option matches the nonce encoding.
  3. Use the 'Integer' nonce option if you want to specify a small counter as a number instead of raw bytes.

Example fix

// before: 24 hex chars -> 12-byte nonce
args:[{string:"<key>",option:"Hex"},{string:"aabb...24hex",option:"Hex"}, ...]
// after: 48 hex chars -> 24-byte nonce
args:[{string:"<key>",option:"Hex"},{string:"<48 hex chars>",option:"Hex"}, ...]
Defensive patterns

Strategy: validation

Validate before calling

function xsalsa20NonceRecipe(nonceStr, nonceOption) {
  if (nonceOption === "Integer") return {string:nonceStr, option:nonceOption};
  const n = Utils.convertToByteArray(nonceStr, nonceOption);
  if (n.length !== 24) throw new Error(`XSalsa20 nonce must be 24 bytes, got ${n.length}`);
  return {string:nonceStr, option:nonceOption};
}

Type guard

const isXsalsa20NonceLen = (bytes) => bytes.length === 24;

Try / catch

try { chef.bake(data, recipe); } catch (e) { if (/Invalid nonce length/.test(e.message) && /XSalsa20/.test(e.message)) { /* fix nonce */ } else throw e; }

Prevention

When it happens

Trigger: nonceType (args[1].option) is not 'Integer', so Utils.convertToByteArray is used; the resulting nonce is not 24 bytes. Example: a 24-character hex nonce is only 12 bytes (invalid); 48 hex chars = 24 bytes (valid).

Common situations: Selecting Hex for the nonce but supplying a UTF8-length nonce (or vice versa); reusing a 12-byte Salsa20 nonce with XSalsa20; truncating the nonce during copy-paste.

Related errors


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