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):
002| ”’Playfair encryption class.”’
003| A2Z = ‘ABCDEFGHIKLMNOPQRSTUVWXYZ’
004|
005| def __init__ (self, keyword):
006| rs = range(5)
007| kw = []
008|
009| for ch in keyword.upper():
010| if (ch in self.A2Z) and (ch not in kw):
011| kw.append(ch)
012|
013| az = [a for a in self.A2Z if a not in kw]
014| cs = list(reversed(kw+az))
015| self.grid = [[[cs.pop(),r,c] for c in rs] for r in rs]
016|
017| def __str__ (self):
018| a = [str(row) for row in self.grid]
019| return ‘\n’.join(a)
020|
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 9–11). The next lines (13 and 14) append the unused alpha chars to the key, which creates a string 25-characters long (note that “J” is ignored here). The last line (15) 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:
002| ”’Encode a message.”’
003| # Condition the message for encoding…
004| self.msg = self._condition(plaintext)
005|
006| # Find pairs of letters in the grid…
007| self._ps = [self._find_pair(m) for m in self.msg]
008|
009| # Get the encoded pairs of letters…
010| self._gs = [self._encode(p0,p1) for p0,p1 in self._ps]
011|
012| # Process the pairs…
013| self.enc = [(g[0][0], g[1][0]) for g in self._gs]
014|
015| # Combine pairs to get (encoded) text…
016| self.txt = ”.join([a[0]+a[1] for a in self.enc])
017|
018| # Returned the encoded text…
019| return self.txt
020|
The method takes a plaintext message which it first conditions (see below). Then it generates a list of grid pairs, which it encodes (lines 4–10).
The enc instance member (line 13) is a list of encoded pair tuples, and the txt member (line 16), 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):
002| ”’Decode a message.”’
003| # Condition the message for decoding…
004| self.msg = self._condition(ciphertext)
005|
006| # Find pairs of letters in the grid…
007| self._ps = [self._find_pair(m) for m in self.msg]
008|
009| # Get the decoded pairs of letters…
010| self._gs = [self._decode(p0,p1) for p0,p1 in self._ps]
011|
012| # Process the pairs…
013| self.enc = [(g[0][0], g[1][0]) for g in self._gs]
014|
015| # Combine pairs to get (decoded) text…
016| self.txt = ”.join([a[0]+a[1] for a in self.enc])
017|
018| # Special handling for Q…
019| self.txt = self.txt.rstrip(‘Q’)
020| ix = self.txt.find(‘Q’)
021| while 0 < ix:
022| if self.txt[ix–1] == self.txt[ix+1]:
023| self.txt = self.txt[0:ix]+self.txt[ix+1:]
024| ix = self.txt.find(‘Q’, ix+1)
025|
026| # Returned the decoded text…
027| return self.txt
028|
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 19–24 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:
002| ”’Condition the message text.”’
003| mesg = []
004| state = 0
005| tempc = ”
006|
007| for ch in message.upper():
008| if ch.isalpha():
009| if ch == ‘J’:
010| ch = ‘I’
011| if state:
012| if ch == tempc:
013| t = (tempc, ‘Q’)
014| mesg.append(t)
015| continue
016| t = (tempc, ch)
017| mesg.append(t)
018| state = 0
019| else:
020| tempc = ch
021| state = 1
022| if state:
023| t = (tempc, ‘Q’)
024| mesg.append(t)
025| return mesg
026|
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:
002| ”’Find letter pair in grid.”’
003| c0,c1 = pair
004| rc0,rc1 = (0,0), (0,0)
005|
006| for row in self.grid:
007| for col in row:
008| if col[0] == c0:
009| rc0 = (col[1],col[2])
010| if col[0] == c1:
011| rc1 = (col[1],col[2])
012|
013| # Return letter pair…
014| return rc0,rc1
015|
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:
002| ”’Get encoded letter pairs.”’
003| if (rc0[0]==rc1[0]) and (rc0[1]==rc1[1]):
004| # Same row and column…
005| raise RuntimeError(‘Matching pair!’)
006|
007| if rc0[0] == rc1[0]:
008| # Same row…
009| c0 = self.grid[rc0[0]][(rc0[1]+1)%5]
010| c1 = self.grid[rc1[0]][(rc1[1]+1)%5]
011| return c0,c1
012|
013| if rc0[1] == rc1[1]:
014| # Same column…
015| c0 = self.grid[(rc0[0]+1)%5][rc0[1]]
016| c1 = self.grid[(rc1[0]+1)%5][rc1[1]]
017| return c0,c1
018|
019| # Different row and column…
020| c0 = self.grid[rc0[0]][rc1[1]]
021| c1 = self.grid[rc1[0]][rc0[1]]
022| return c0,c1
023|
And the _decode method, which is nearly the same:
002| ”’Get decoded letter pairs.”’
003| if (rc0[0]==rc1[0]) and (rc0[1]==rc1[1]):
004| # Same row and column…
005| raise RuntimeError(‘Matching pair!’)
006|
007| if rc0[0] == rc1[0]:
008| # Same row…
009| c0 = self.grid[rc0[0]][(rc0[1]–1)%5]
010| c1 = self.grid[rc1[0]][(rc1[1]–1)%5]
011| return c0,c1
012|
013| if rc0[1] == rc1[1]:
014| # Same column…
015| c0 = self.grid[(rc0[0]–1)%5][rc0[1]]
016| c1 = self.grid[(rc1[0]–1)%5][rc1[1]]
017| return c0,c1
018|
019| # Different row and column…
020| c0 = self.grid[rc0[0]][rc1[1]]
021| c1 = self.grid[rc1[0]][rc0[1]]
022| return c0,c1
023|
The decode process uses slightly different rules than the encode process.
§
And that’s the Playfair class. To use it:
002| ”’Encode Message.”’
003|
004| # Get a new Playfair encoding instance…
005| coder = Playfair(keyword)
006|
007| # Encode the message and return coded text…
008| return coder.encode(message)
009|
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