A TADS3/adv3 module providing several simple cipher algorithms

Another little TADS3/adv3 module. This one provides several simple cipher algorithms: simpleCipher github repo.

IMPORTANT: None of the provided algorithms are safe for any modern cryptographic use. They’re intended to make it easier to implement in-game cryptographic puzzles, which are (by their nature) intended to be solved/broken.

Usage is pretty straightforward. Most of the algorithms are implemented in global singletons which provide an encode() and decode() method. A few examples:

Rot13

// Result is 'Gurer vf n fznyy znvyobk urer.'
enc = rot13.encode('There is a small mailbox here.');

// Result is 'There is a small mailbox here.'
dec = rot13.decode(enc);

Caesar Ciphers
For Caesar ciphers, both encode() and decode() take a second argument indicating the shift to use:

// Result is 'Qebob fp x pjxii jxfiylu ebob.'
enc = caesar.encode('There is a small mailbox here.', -3);

// Result is 'There is a small mailbox here.'
dec = caesar.decode(enc, -3);

Arbitrary Monoalphabetic Substitution Ciphers

The module also supplies an abstract class for implementing arbitrary monoalphabetic substitution ciphers. In order to use it, you have to create an instance, which will then work like the other examples. The constructor takes two arguments: the first is the cipher alphabet; and the optional second argument is the plaintext alphabet, defaulting to the standard Latin alphabet if none is given.

// Declare the cipher instance
// This uses the default plaintext alphabet, so the cipher
// is:
//      plaintext       ABCDEFGHIJKLMNOPQRSTUVWXYZ
//      ciphertext      EKMFLGDQVZNTOWYHXUSPAIBRCJ
// That is, A -> E, B -> K, C -> M, and so on.
cipher = new SimpleCipherMonoalphabetic('EKMFLGDQVZNTOWYHXUSPAIBRCJ');

// Result is 'Pqlul vs e soett oevtkyr qlul.'
enc = cipher.encode('There is a small mailbox here.');

// Result is 'There is a small mailbox here.'
dec = cipher.decode(enc);

Simple M3 Enigma Cipher

Finally, the module provides a toy version of the M3 Enigma machine, specifically one without a plugboard (to make it easier to solve, because the assumption is that all ciphers presented in a game are intended to be solved).

The module provides an enigma singleton, but you’ll have to create an EnigmaConfig instance to pass to encode() and decode(). The config specifies the rotors to use (the module provides the historical I, II, and III rotors), the reflector to use (the module provides the historical B and C reflectors), and the encryption key. Note that the encryption key must have exactly as many characters as the machine has rotors (the key literally just being the starting settings for the rotors).

// Create the config
cfg = new EnigmaConfig();

// Set the encryption key.  Note that the number of letters
// must match the number of rotors used in this config.
cfg.setKey('ABC');

// Set the rotors.  Arg is a list of the rotor IDs, in order
// from left to right.  Rotors I, II, and III are defined
// in the module, others can be added.
cfg.setRotors([ 'III', 'II', 'I' ]);

// Set the reflector.  Arg is the reflector ID.  The module
// provides declarations for reflectors B and C.
cfg.setReflector('B');

// Result will be "XOGNZ BBHUW QRBLQ HURWN PUJRM".  Note
// that output is divided into 5-character groups, and the
// final group will be padded if the input length isn't
// a multiple of five.  In this case the input, after all
// spaces and punctuation are removed, is 14 characters, so
// the last character is padding.  This will show up
// in the decode as a trailing 'X'.
// Result will be 'XOGNZ BBHUW QRBLQ HURWN PUJRM'
enc = enigma.encode('There is a small mailbox here.', cfg);

// Result will be 'THERE ISASM ALLMA ILBOX HEREX'.  Messages
// are always stripped of non-alphabetic characters and
// converted to upper case.  The X is the result of the
// padding described above.
dec = enigma.decode(enc, cfg);

I’ll just reiterate that this is just intended to make it easier to implement codes and crytographic puzzles in games. None of the algorithms provide any sort of meaningful security in a modern environment.

8 Likes

Minor (and somewhat frivolous) update: I added plugboard and ring setting support to the enigma singleton. This makes it mostly feature-equivalent to the actual M3 Enigma. New stuff is handled by new methods/properties on EnigmaConfig:

  • setRing('ABC') sets the ring setting for the rotors, from left to right, to be A, B, and C. If not specified, defaults to AAA (no offset).
  • setPlugboard([ 'SA', 'MP', 'LE' ]) configures the plugboard to swap S and A, M and P, and L and E. Up to 13 pairs of letters can be swapped, with no letters repeated.
  • padOutput = true if the padOutput property is true, then output will be padded so the length is a multiple of five. Default is true

I really can’t imagine a whole lot of games needing this, but for whatever reason after slapping together the trivial version of this it started bugging me that there were missing features. So ta-da.

2 Likes

I feel like @Piergiorgio_d_errico would find this useful in a game idea. :grin:

1 Like

Unfortunately the module doesn’t implement the M4 naval Enigma. Maybe if I get bored.

Speaking of which, I just hammered out a commit adding a vigenere singleton that implements the Vigenère cipher. Which, confusingly, wasn’t devised by Blaise de Vigenère but rather Giovan Bellaso. But that’s nomenclature for you.

2 Likes

Actually the ULTRA problem in the Med was 99% luftwaffe (long history…)

really short history:
Italian Navy don’t use Enigma, after the non-stellar experience with the commercial mods D and K during the spanish civil war, and used rather strong manual decoding, accepting the resulting hysteresis, but the aeronaval cooperation with the LW requires the trasmittal of messages to LW HQ in Rome, whose coded with their Enigma and transmitted by radio…

OTOH, Italian Navy exploited the peninsula, that is, orders was transmitted from Supermarina to Taranto and Spezia thru cable, so sometimes the surprise was on the Italian side, when LW was NOT informed of the moviments…

sorry for the heavy OT, and
Best regards from Italy,
dott. Piergiorgio.

2 Likes

And even the Kriegsmarine were using the M3 before they started deploying the Triton (M4) units on February 1, 1942 (although even after that the M4 could be used in a configuration that made it cryptographically identical to the operation of the M3).

I was kind of trying to make a joke that I really wasn’t attempting to implement a “historically accurate” Enigma emulator; my original intent was just to provide a sort of generic rotor machine implementation (as a sort of aid for implementing cryptographic puzzles). Even restricting the module to a posited “historical era” of say July 1941 to February 1942 (the sort of heyday of Bletchley Park decrypts of U-boat traffic) there are many additional rotors that were historically available that aren’t implemented by the module (I just put in I, II, and III as the obvious choices and then when tinkering the other night added IV and V because I happened to remember the mnemonic for their lug settings (“Royal Flags Wave Kings Above”) and so didn’t have to dust off any of my physical references on the subject because the wikipedia article on the Enigma rotors declines to mention this important operational detail).

I will say that as part of testing the module code I compared the output with various settings to a number of online Enigma simulators and, after quite a bit of digging, discovered that at least two of them appear to contain what I believe is an implementation error (I don’t know if the ones I tried share a codebase or if it’s two separate occurances of the same/a similar error) that causes them to output incorrect (a-historical) output. And since I don’t have an actual physical M3 Enigma to test against I’m not 100% certain that the module output is “correct”, although it is “functional” (in that it will encrypt messages in a way that it itself can decrypt, even if it could not encrypt or decrypt messages for use with a physical M3).

Although typing this all out, it occurs to me that I didn’t implement the double-stepping anomaly. Which I supoose would be easy enough to add.

2 Likes

One more, probably last, feature bump.

First, the module now has an autokey singleton. It provides a simple autokey cipher.

Also, I added some optional debugging stuff for the Enigma cipher.
When compiled with -D ENIGMA_DEBUG the cipher will output the encryption process step by step. For example, something like:

encoding A
   A -> C: III
   C -> M: I
   M -> W: II
   W -> V: reflector
   V -> X: II
   X -> Q: I
   Q -> H: III
encoded A -> H

This tells you that the input letter is “A”; passing right to left through the rightmost rotor (in this case the III rotor) A was transformed into C; passing right to left through the middle (I) rotor C became M; right to left through the left (II) rotor produced W; passing through the reflector yielded V; and then passing back through the three rotors left to right produced X, Q, and finally H.

In this case the only rotor that stepped was the rightmost “fast” rotor, which steps for every letter. Stepping of the fast rotor is never logged, but any time any other rotor steps it appears in the input at the appropriate point.

1 Like