Ciphers

Substitution

Every letter swaps for its cell in a mixed alphabet. Hand it the whole alphabet or just a keyword, it's 26! keys either way.
IDsubstitution05 / 57classical · substitution-keyed
Cipher / Mixed alphabet

Substitution

One shuffled alphabet, keyed by a word.

Keyspace
26!
Decode
same options back
Works on
A-Z, the rest passes
Family
1 in mixed alphabet
required 1 / 1

Options

keystring
required
Cipher alphabet of 26 letters, ? for a letter left unknown, or a keyword the rest of A-Z follows

Access

Createcreate("substitution")
CLIciphers encode substitution 'FLEE AT ONCE' --key ZEBRAS
Tryplayground with the sample above

The one from the newspaper puzzle page. Every letter gets one stand-in, and it's the same one every time. Caesar does that too, but Caesar only slides the alphabet. Here the alphabet is shuffled any way you like. That's 26! keys, about 2^88. Nobody tries them all.

The key

key comes in two shapes. Exactly 26 letters is a whole cipher alphabet, taken as it is: the first cell is what A becomes, the second is B, and so on. Each letter shows up once. A repeat is an InvalidOptionError, not a quiet keyword with some other mapping. Anything else is a keyword. Its letters go first, repeats dropped, then the rest of A to Z in order. So ZEBRAS turns into ZEBRASCDFGHIJKLMNOPQTUVWXY, and that's Wikipedia's example:

ts
const substitution = create("substitution");
substitution.encode("FLEE AT ONCE. WE ARE DISCOVERED!", { key: "ZEBRAS" }).text; // "SIAA ZQ LKBA. VA ZOA RFPBLUAOAR!"
substitution.decode("SIAA ZQ LKBA. VA ZOA RFPBLUAOAR!", { key: "ZEBRAS" }).text; // "FLEE AT ONCE. WE ARE DISCOVERED!"

Case stays put. Spaces, digits and everything outside A to Z pass through untouched.

A key with holes

What if you only know part of the key? recover solves a substitution from the ciphertext alone. But it can only name letters the text actually uses, and the rest come back as ?. Hand that key straight to decode. A ? cell gives ?, and every letter you do know reads fine:

ts
substitution.encode("Bad", { key: "ZEB?ASCDFGHIJKLMNOPQTUVWXY" }).text; // "Ez?"
substitution.decode("Ez?R", { key: "ZEB?ASCDFGHIJKLMNOPQTUVWXY" }).text; // "Ba??"

A ? only counts in a whole alphabet: 26 cells, each letter at most once. Anywhere else it's an InvalidOptionError. Why so strict? A recovered key that lost a cell on the way would turn into a keyword and shift every letter after the gap. Quietly. No thanks.

Atbash and all 25 Caesar shifts are substitutions too, just with keys you could write down from memory. Counting letters breaks every one of them the same way.