module Playfair
Encryption/decryption using Playfair
ciphers (en.wikipedia.org/wiki/Playfair_cipher)
Public Instance Methods
Public: builds the 5 x 5 grid for our Playfair
cipher.
Parameter(s)¶ ↑
key
- String
: the key forms the first key.length
letters of the 5 x 5 grid from left to right (after repeats are omitted), followed by the remaining letters of the alphabet in order. In this algorithm, the letter ‘Q’ is removed from the table entirely to make it 5 x 5.
Return Value¶ ↑
Array
: a two-dimensional array of characters (5 x 5).
Example¶ ↑
+build_table(‘KRYPTOS’)+
(See EXAMPLE_PLAYFAIR_TABLE
in kryptos.rb
for the result.)
# File lib/dekryptos/playfair.rb, line 30 def build_table(key) key = key.upcase.split('').uniq grid = [[], [], [], [], []] grid_letters = key rest_of_grid = ('A'..'Z').to_a rest_of_grid.each do |letter| grid_letters << letter unless key.include? letter end grid_letters.delete('Q') grid.each_with_index do |_, index| grid[index] = grid_letters[index * 5, 5] end end
Public: decrypts ciphertext using a Playfair
cipher.
-
If both elements of the pair are in the same row, we add the element to the left to the ciphertext.
-
If the elements of the pair are in different rows, we store the location as a (row, index) tuple in
matches
for evaluation as follows:-
Comparing two
matches
at a time, if they share an index value, they must be in the same column. We then push the values in the preceding rows to the ciphertext string. -
If the two
matches
do not share an index value, they must form nonconsecutive vertices (opposing corners) of a rectangle. We then push their complements to the ciphertext string, which is equivalent to simply swapping their indices. -
After comparing each set of two
matches
, we shift them off thematches
array and continue.
-
Parameter(s)¶ ↑
ciphertext
- String
: the text to be decrypted. grid
- Array
: the grid generated by build_table
.
Return Value¶ ↑
String
: the decrypted text.
Example¶ ↑
+encrypt(‘CYYCOTCYFOZL’, EXAMPLE_PLAYFAIR_TABLE)+ +=> “ATTACKATDAWN”+
# File lib/dekryptos/playfair.rb, line 130 def decrypt(ciphertext, grid) plaintext = [] pairs = bigramify(ciphertext) matches = [] pairs.each do |pair| grid.each_with_index do |row, index| if row.include?(pair[0]) && row.include?(pair[1]) plaintext << row[(row.index(pair[0]) + 9) % 5] plaintext << row[(row.index(pair[1]) + 9) % 5] else matches[0] = [index, row.index(pair[0])] if row.include?(pair[0]) matches[1] = [index, row.index(pair[1])] if row.include?(pair[1]) end end unless matches.empty? if matches[0][1] == matches[1][1] plaintext << grid[(matches[0][0] + 9) % 5][matches[0][1]] plaintext << grid[(matches[1][0] + 9) % 5][matches[1][1]] 2.times { matches.shift } else plaintext << grid[matches[0][0]][matches[1][1]] plaintext << grid[matches[1][0]][matches[0][1]] 2.times { matches.shift } end end end plaintext.join('') end
Public: encrypts plaintext using a Playfair
cipher.
-
If both elements of the pair are in the same row, we add the element to the right to the ciphertext.
-
If the elements of the pair are in different rows, we store the location as a (row, index) tuple in
matches
for evaluation as follows:-
Comparing two
matches
at a time, if they share an index value, they must be in the same column. We then push the values in the ensuing rows to the ciphertext string. -
If the two
matches
do not share an index value, they must form nonconsecutive vertices (opposing corners) of a rectangle. We then push their complements to the ciphertext string, which is equivalent to simply swapping their indices. -
After comparing each set of two
matches
, we shift them off thematches
array and continue.
-
Parameter(s)¶ ↑
plaintext
- String
: the text to be encrypted. grid
- Array
: the grid generated by build_table
.
Return Value¶ ↑
String
: the encrypted text.
Example¶ ↑
+encrypt(‘ATTACKATDAWN’, EXAMPLE_PLAYFAIR_TABLE)+ +=> “CYYCOTCYFOZL”+
# File lib/dekryptos/playfair.rb, line 73 def encrypt(plaintext, grid) ciphertext = [] pairs = bigramify(plaintext) matches = [] pairs.each do |pair| grid.each_with_index do |row, index| if row.include?(pair[0]) && row.include?(pair[1]) ciphertext << row[(row.index(pair[0]) + 1) % 5] ciphertext << row[(row.index(pair[1]) + 1) % 5] else matches[0] = [index, row.index(pair[0])] if row.include?(pair[0]) matches[1] = [index, row.index(pair[1])] if row.include?(pair[1]) end end unless matches.empty? if matches[0][1] == matches[1][1] ciphertext << grid[(matches[0][0] + 1) % 5][matches[0][1]] ciphertext << grid[(matches[1][0] + 1) % 5][matches[1][1]] 2.times { matches.shift } else ciphertext << grid[matches[0][0]][matches[1][1]] ciphertext << grid[matches[1][0]][matches[0][1]] 2.times { matches.shift } end end end ciphertext.join('') end
Private Instance Methods
Private: converts text to bigrams for processing by our encryption/decryption methods.
-
If we encounter a double-letter bigram, we break it up with an X.
-
If we end up with an odd number of letters, we pad with a final X.
-
We replace any Qs with Xs (since
build_table
omits Qs).
Parameter(s)¶ ↑
text
- String
: the text to convert to bigrams.
Return Value¶ ↑
Array
: an array of two-character strings (bigrams).
# File lib/dekryptos/playfair.rb, line 175 def bigramify(text) text_array = text.upcase.split('') text_array.each_with_index do |char, index| if index.even? if char == text_array[index + 1] text_array.insert(index + 1, 'X') text = text_array.join('') bigramify(text) end end end text_array << 'X' if text_array.length.odd? text = text_array.join('') text.gsub!('Q', 'X') text.split(/(.{2})/).reject { |pair| pair if pair.empty? } end