Earlier this month, on my other blog, I wrote about the Playfair Cipher, a polygraphic substitution cipher invented by Sir Charles Wheatstone in 1854.
At the time I mused about writing some Python to automate using the cipher, and now I’ve done that, so here it is:
You’ll need either to be already familiar with the Playfair cipher or to have read my post or the wiki article. Here I’m assuming you know how it works and won’t describe it.
Suffice to say both encoding and decoding share three basic tasks:
- Conditioning the input.
- Finding letter pairs in the grid.
- Encoding/Decoding the grid pairs.
Only the third step differs (slightly) due to how decoding is different from encoding.
§
The code is reasonably simple. As usual, I created a class.
Here’s the class and its constructor (and its string appearance):
class Playfair (object): A2Z = 'ABCDEFGHIKLMNOPQRSTUVWXYZ' def __init__ (self, keyword): rs = range(5) kw = [] for ch in keyword.upper(): if (ch in self.A2Z) and (ch not in kw): kw.append(ch) az = [a for a in self.A2Z if a not in kw] cs = list(reversed(kw+az)) self.grid = [[[cs.pop(),r,c] for c in rs] for r in rs] def __str__ (self): a = [str(row) for row in self.grid] return '\n'.join(a) #
So a new instance takes the keyword (or phrase) used to encrypt or decrypt messages. Using that, it creates the Playfair grid.
Note that the key is forced to upper case and uses only unique allowed chars (lines 7–9). The next two lines (10 and 11) append the unused alpha chars to the key, which creates a string 25-characters long (note that “J” is ignored here). The last line (12) creates the actual 5×5 Playfair grid.
The string appearance of an instance is a string version of the grid.
§
To encode
a string, call the encode method:
def encode (self, plaintext): self.msg = self._condition(plaintext) self._ps = [self._find_pair(m) for m in self.msg] self._gs = [self._encode(p0,p1) for p0,p1 in self._ps] self.enc = [(g[0][0], g[1][0]) for g in self._gs] self.txt = ''.join([a[0]+a[1] for a in self.enc]) return self.txt #
The method takes a plaintext message which it first conditions (see below). Then it generates a list of grid pairs, which it encodes (lines 2–4).
The enc
instance member (line 5) is a list of encoded pair tuples, and the txt
member (line 6), which is returned, is a string of those pairs.
The output is a continuous string of encoded characters:
VOPATIQCPAYHKTAGHQYFNAWAQZCQLOCQFW
The user breaks this up into random groups and adds spurious punctuation:
VOP AT IQCPAY, HKTAG. HQYF: NAW A QZCQLO CQFW!
To create the final cipher version. (I thought about writing some code to automate that, but been there done that, wrote the post.)
§
The decode
method is very similar (with an added bit):
def decode (self, ciphertext): self.msg = self._condition(ciphertext) self._ps = [self._find_pair(m) for m in self.msg] self._gs = [self._decode(p0,p1) for p0,p1 in self._ps] self.enc = [(g[0][0], g[1][0]) for g in self._gs] self.txt = ''.join([a[0]+a[1] for a in self.enc]) self.txt = self.txt.rstrip('Q') ix = self.txt.find('Q') while 0 < ix: if self.txt[ix-1] == self.txt[ix+1]: self.txt = self.txt[0:ix]+self.txt[ix+1:] ix = self.txt.find('Q', ix+1) return self.txt #
The first two lines are exactly the same as in the encode
method. The third line differs only in calling the _decode
method rather than the _encode
method.
The enc
instance member is still a list of encoded pair tuples, and the txt
member is still returned.
Lines 7–12 first remove any trailing ‘Q’ characters and then remove instances where a ‘Q’ comes between matching letters. (This is necessary to remove ‘Q’ characters inserted to break up those matching pairs, so BALLPIT becomes BALQLPIT.)
§
The real meat is in the helper methods that condition the input, find pairs, and encode or decode.
Here’s the _condition
method:
def _condition (self, message): mesg = [] state = 0 tempc = '' for ch in message.upper(): if ch.isalpha(): if ch == 'J': ch = 'I' if state: if ch == tempc: t = (tempc, 'Q') mesg.append(t) continue t = (tempc, ch) mesg.append(t) state = 0 else: tempc = ch state = 1 if state: t = (tempc, 'Q') mesg.append(t) return mesg #
Essentially, it forces the message to upper case, ignores anything not an alpha char, converts the letter ‘J’ to ‘I’, and breaks the message into two-character pairs (while inserting ‘Q’ characters to break up matching pairs as described just above).
It returns a list of two-character tuples.
§
Here’s the _find_pair
method:
def _find_pair (self, pair): c0,c1 = pair rc0,rc1 = (0,0), (0,0) for row in self.grid: for col in row: if col[0] == c0: rc0 = (col[1],col[2]) if col[0] == c1: rc1 = (col[1],col[2]) return rc0,rc1 #
It takes a two-character tuple and returns a tuple of two tuples, each a row-column coordinate of the input character.
§
Lastly, the _encode
method:
def _encode (self, rc0, rc1): if (rc0[0]==rc1[0]) and (rc0[1]==rc1[1]): # Same row and column... raise RuntimeError('Matching pair!') if rc0[0] == rc1[0]: # Same row... c0 = self.grid[rc0[0]][(rc0[1]+1)%5] c1 = self.grid[rc1[0]][(rc1[1]+1)%5] return c0,c1 if rc0[1] == rc1[1]: # Same column... c0 = self.grid[(rc0[0]+1)%5][rc0[1]] c1 = self.grid[(rc1[0]+1)%5][rc1[1]] return c0,c1 # Different row and column... c0 = self.grid[rc0[0]][rc1[1]] c1 = self.grid[rc1[0]][rc0[1]] return c0,c1 #
And the _decode
method, which is nearly the same:
def _decode (self, rc0, rc1): if (rc0[0]==rc1[0]) and (rc0[1]==rc1[1]): # Same row and column... raise RuntimeError('Matching pair!') if rc0[0] == rc1[0]: # Same row... c0 = self.grid[rc0[0]][(rc0[1]-1)%5] c1 = self.grid[rc1[0]][(rc1[1]-1)%5] return c0,c1 if rc0[1] == rc1[1]: # Same column... c0 = self.grid[(rc0[0]-1)%5][rc0[1]] c1 = self.grid[(rc1[0]-1)%5][rc1[1]] return c0,c1 # Different row and column... c0 = self.grid[rc0[0]][rc1[1]] c1 = self.grid[rc1[0]][rc0[1]] return c0,c1 #
The decode process uses slightly different rules than the encode process.
§
And that’s the Playfair class. To use it:
def encode_message (keyword, message): coder = Playfair(keyword) return coder.encode(message) #
Just create an instance and call the encode method. The same process for decoding, just call the decode method instead.
∅
Pingback: The Playfair Cipher | Logos con carne