But there's a problem. The pos function maps a to 0, b to 1, and so on. But another plausible (and often used) version of the affine cipher maps a to 1, b to 2, and so on, before doing the multiplication step.

Rather than making a fixed choice in the code, we should add another keyword parameter, one_based, and use that to modify the calculation as needed. If one_based is True, we add one to the result of pos and subtract one just before calling unpos. That gives this code:

Deciphering

Deciphering affine ciphers is, in principle, no more complicated than enciphering them: convert the ciphertext letter to a number, undo the enciphering process, then convert the result back to a letter. Undoing the calculation is just rearranging the forumla above:

When deciphering, we know the values of c and m and want to find p. But we can easily find the value of c for any particular p and m, as \( c = p \times m \pmod{26} \). If go through all the possible values of p and m and find the correct c for each combination, we can build a lookup table modular_division_table, indexed on m and c, that gives the correct value of p.

We can implement that in Python as a dict comprehension. The keys to the dict are the 2-tuple (pair) of m and c : the value for each key is the corresponding value of p.

The affine cipher only gives unambiguous results when the multiplier is coprime to 26, that is when the multiplier doesn't have any common factors with 26, and 26 = 2 × 13. That means only odd numbers, apart from 13, can sensibly be used as multipliers with affine ciphers.

We could catch the use of these multipliers in the ciphering and deciphering steps, but I'm not sure it would gain much. It's just something to be aware of when it comes to breaking ciphers, as it halves the number of keys we need to look at.

Breaking

Breaking the affine cipher is exactly the same as breaking the Caesar cipher: try all the possible keys and see which one gives text that looks most like English. In this case, the key has three parts: the multiplier, the adder, and whether the cipher is one-based. That means there are three nested for loops to try all possible keys.

The only wrinkle is the set of multipliers used: the range function counts from 1 in steps of 2, so generates 1, 3, … 25. The if in the list comprehension removes 13.

Code

Acknowlegements

The affine cipher is named after an affine transformation in geometry. Basically, an affine transformation of a point or shape is some scaling of the original (i.e. a multiplication) and a translation (i.e. an addition). But you don't need to know about that for this cipher. ↩︎