gchq/CyberChef · error · OperationError

Letter ${elem} not in LS47

Error message

Letter ${elem} not in LS47

What it means

LS47's checkKey() intends to reject keys containing a character outside its 49-character alphabet. NOTE: as written, the loop iterates `letters` (the constant alphabet) rather than `key`, so letters.indexOf(elem) is always >= 0 and this branch is effectively unreachable in the current code. The intended behaviour is to flag any key byte not in "_abcdefghijklmnopqrstuvwxyz.0123456789,-+*/:?!'()".

Source

Thrown at src/core/lib/LS47.mjs:105

        i = (i + 1) % 7;
    }
    return k;
}

/**
 * Checks the key is a valid key.
 *
 * @param {string} key
 */
function checkKey(key) {
    if (key.length !== letters.length)
        throw new OperationError("Wrong key size");
    const counts = new Array();
    for (let i = 0; i < letters.length; i++)
        counts[letters.charAt(i)] = 0;
    for (const elem of letters) {
        if (letters.indexOf(elem) === -1)
            throw new OperationError("Letter " + elem + " not in LS47");
        counts[elem]++;
        if (counts[elem] > 1)
            throw new OperationError("Letter duplicated in the key");
    }
}

/**
 * Finds the position of a letter in they key.
 *
 * @param {letter} key
 * @param {string} letter
 * @returns {object}
 */
function findPos (key, letter) {
    const index = key.indexOf(letter);
    if (index >= 0 && index < 49)
        return [Math.floor(index/7), index%7];
    throw new OperationError("Letter " + letter + " is not in the key");

View on GitHub (pinned to 4290ea7539)

Solutions

  1. Use deriveKey(password) which can only emit alphabet characters.
  2. If you build a key manually, restrict it to the exact alphabet string and validate with a regex like /^[_a-z0-9.,\-+*/:?!'()]+$/.
  3. Report the upstream bug: the for-loop should iterate `key`, not `letters`, so this guard actually fires.

Example fix

// Bug in src/core/lib/LS47.mjs: the loop iterates `letters` not `key`.
// before
for (const elem of letters) {
    if (letters.indexOf(elem) === -1) throw ...; // always false
}

// after (intended)
for (const elem of key) {
    if (letters.indexOf(elem) === -1)
        throw new OperationError("Letter " + elem + " not in LS47");
}
Defensive patterns

Strategy: validation

Validate before calling

const LS47_ALPHABET = "_abcdefghijklmnopqrstuvwxyz.0123456789,-+*/:?!'()";

function keyUsesOnlyAlphabet(key) {
  return [...key].every(c => LS47_ALPHABET.includes(c));
}

Type guard

function isAllLS47Chars(x): x is string {
  const A = "_abcdefghijklmnopqrstuvwxyz.0123456789,-+*/:?!'()";
  return typeof x === 'string' && [...x].every(c => A.includes(c));
}

Prevention

When it happens

Trigger: Intent: calling checkKey (via encrypt/decrypt/encryptPad/decryptPad) with a 49-char key that includes a character outside the LS47 alphabet (e.g. '@', uppercase letters, '\n'). In practice, with the current code the check never fires because the loop target is wrong.

Common situations: Hand-built key containing uppercase letters or symbols outside the alphabet; key encoded with characters from a related cipher (e.g. LS47+ which has a different alphabet); copy-paste introduced a stray character. The bug also masks these cases from users.

Related errors


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