Skip to content

Instantly share code, notes, and snippets.

@chischaschos
Created November 26, 2010 19:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chischaschos/717112 to your computer and use it in GitHub Desktop.
Save chischaschos/717112 to your computer and use it in GitHub Desktop.
A Playfair Cypher ruby example from the ruby core rubylearning.org course
source :gemcutter
gem "rspec"
GEM
remote: http://rubygems.org/
specs:
diff-lcs (1.1.2)
rspec (2.1.0)
rspec-core (~> 2.1.0)
rspec-expectations (~> 2.1.0)
rspec-mocks (~> 2.1.0)
rspec-core (2.1.0)
rspec-expectations (2.1.0)
diff-lcs (~> 1.1.2)
rspec-mocks (2.1.0)
PLATFORMS
ruby
DEPENDENCIES
rspec
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
require 'playfair_square'
require 'playfair_encoder'
require 'playfair_cipher'
puts "-----------------------------------------------------------"
puts "Welcome to Plyfair cipher"
puts "At any time pres <CTR + C> to exit or just press enter\n\n"
puts "-----------------------------------------------------------"
loop do
print "Input key: "
key = gets.chop!
exit(0) if key.empty?
pfc = PlayfairCipher.new(key)
puts "Formed key: %s" % pfc.key.to_s
print "Message to encode: "
message = gets.chop!
exit(0) if message.empty?
puts "Encoded message: %s" % pfc.encode(message).to_s
end
require 'playfair_square'
require 'playfair_encoder'
class PlayfairCipher
attr_reader :key
def initialize(key)
@square = PlayfairSquare.new(clean_key(key))
@key = @square.rows
@encoder = PlayfairEncoder.new(@square)
end
def encode(message)
message.gsub!(/[jJ]/i, 'I')
digraph_ary = prepare_digraph(message)
result_digraph = []
digraph_ary.each do |digraph|
result_digraph << (@encoder.row(digraph) || @encoder.col(digraph) || @encoder.rect(digraph))
end
result_digraph
end
def decode(message)
message.gsub!(/[jJ]/i, 'I')
digraph_ary = prepare_digraph(message)
result_digraph = []
digraph_ary.each do |digraph|
result_digraph << (@encoder.row(digraph, true) || @encoder.col(digraph, true) || @encoder.rect(digraph))
end
result_digraph
end
private
def prepare_digraph(message)
message = (message.upcase! || message).gsub(/[\s]/, '')
prepared_message = ''
index = 0
x_added = false
loop do
current_char = message[index].chr
last_prepared_char = prepared_message[-1]
if last_prepared_char && current_char == last_prepared_char.chr
prepared_message << (x_added ? 'Z' : 'X')
x_added = !x_added
else
prepared_message << current_char
index += 1
end
break if current_char == nil || index >= message.size
end
(prepared_message.size % 2 == 0 ? prepared_message : prepared_message << 'X').scan(/../)
end
def clean_key(key)
key.gsub!(/[jJ]/i, 'I')
cleaned_key_ary = key.gsub(/[^a-zA-Z]+/, '').upcase.scan(/./)
cleaned_key_ary = cleaned_key_ary.uniq! || cleaned_key_ary.uniq
cleaned_key_ary
end
end
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
require 'spec_helper'
require 'playfair_cipher'
describe PlayfairCipher do
subject { PlayfairCipher.new('') }
context 'with cleaned up key phrase' do
it 'coverts a key phrase to an array' do
subject.send(:clean_key, 'A Key Phrase').should be_an(Array)
end
it 'removes all spaces and non letter characters' do
subject.send(:clean_key, 'a b c d e f g').to_s.downcase.should eq('abcdefg')
subject.send(:clean_key, 'a 123 ').to_s.downcase.should eq('a')
subject.send(:clean_key, 'a !"#1 23dfg/(?\' ').to_s.downcase.should eq('adfg')
end
it 'all duplicated characters are ignored' do
subject.send(:clean_key, 'How are you doing today?').to_s.should eq('HOWAREYUDINGT')
end
it 'converts the key phrase to upper case' do
subject.send(:clean_key, 'abcdefg').to_s.should eq('ABCDEFG')
end
it 'creates squares' do
expected_square = "P L A Y F "
expected_square << "I R E X M "
expected_square << "B C D G H "
expected_square << "K N O Q S "
expected_square << "T U V W Z"
PlayfairCipher.new('playfair example').key.should eq(expected_square.split)
expected_square = "I L O V E "
expected_square << "R U B Y A "
expected_square << "C D F G H "
expected_square << "K M N P Q "
expected_square << "S T W X Z"
PlayfairCipher.new('I love ruby').key.should eq(expected_square.split)
expected_square = "F I R S T "
expected_square << "A M E N D "
expected_square << "B C G H K "
expected_square << "L O P Q U "
expected_square << "V W X Y Z "
PlayfairCipher.new('First Amendment').key.should eq(expected_square.split)
end
end
context 'prepare digraph' do
it 'should insert an X between consecutive identical characters' do
subject.send(:prepare_digraph, 'EE').should eq(['EX', 'EX'])
end
it "should append an X when a message's size is odd" do
subject.send(:prepare_digraph, 'EPS').should eq(['EP', 'SX'])
end
it 'should alternate X and Z when replacing consecutive indentical characters' do
subject.send(:prepare_digraph, 'EE PP YY').should eq(['EX', 'EP', 'ZP', 'YX', 'YX'])
end
it 'should work' do
expected_digraph = 'HIDETHEGOLDINTHETREXESTUMP'.scan(/../)
subject.send(:prepare_digraph, 'Hide the gold in the tree stump').should eq(expected_digraph)
end
end
context 'encoding messages' do
subject { PlayfairCipher.new('playfair example') }
it 'should encode a digraph located in the same row' do
subject.encode('PL').should eq(['LA'])
end
it 'should decode a digraph located in the same row' do
subject.decode('LA').should eq(['PL'])
end
it 'should encode a whole message' do
result = 'BM OD ZB XD NA BE KU DM UI XM MO UV IF'
subject.encode('Hide the gold in the tree stump').should eq(result.split)
end
it 'should decode a whole message' do
result = 'HI DE TH EG OL DI NT HE TR EX ES TU MP'
subject.decode('BMODZBXDNABEKUDMUIXMMOUVIF').should eq(result.split)
end
end
end
class PlayfairEncoder
def initialize(playfair_square)
raise "A #{PlayfairSquare} instance is expected" unless playfair_square.is_a? PlayfairSquare
@square = playfair_square
end
def row(digraph, reverse = false)
linear_digraph(:row, digraph, reverse)
end
def col(digraph, reverse = false)
linear_digraph(:col, digraph, reverse)
end
def rect(digraph)
char0 = digraph[0].chr
char1 = digraph[1].chr
cols = @square.cols
rows = @square.rows
cols_diff = cols.index(char1) / 5 - cols.index(char0) / 5
rows[rows.index(char0) + cols_diff] + rows[rows.index(char1) - cols_diff]
end
private
def linear_digraph(type, digraph, reverse = false)
square = type == :row ? @square.rows : @square.cols
row_index = 0
result = nil
char0 = digraph[0].chr
char1 = digraph[1].chr
while row_index < 6 do
iteration = row_index * 5
row = square[iteration..(iteration + 4)]
indexes = []
if (indexes[0] = row.index(char0)) && (indexes[1] = row.index(char1))
indexes.map! do |index|
if reverse
index += iteration - 1
else
index += iteration + 1
index == square.size ? 0 : index
end
end
result = square[indexes[0]] + square[indexes[1]]
break
end
row_index += 1
end
result
end
end
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
require 'spec_helper'
require 'playfair_cipher'
describe PlayfairEncoder do
subject do
pfc = PlayfairCipher.new("playfair example")
PlayfairEncoder.new(pfc.instance_eval { @square })
end
context 'digraph encoding' do
it 'should encode digraphs in same row' do
subject.row('PL').should eq('LA')
subject.row('AY').should eq('YF')
subject.row('PY').should eq('LF')
subject.row('RX').should eq('EM')
subject.row('KQ').should eq('NS')
subject.row('VW').should eq('WZ')
subject.row('YF').should eq('FI')
subject.row('WZ').should eq('ZP')
end
it 'should encode digraphs on same column' do
subject.col('DE').should eq('OD')
subject.col('LN').should eq('RU')
subject.col('LU').should eq('RA')
subject.col('FZ').should eq('MP')
end
it 'should encode rectangle digraps' do
subject.rect('HI').should eq('BM')
subject.rect('HT').should eq('BZ')
subject.rect('MP').should eq('IF')
subject.rect('TH').should eq('ZB')
subject.rect('EG').should eq('XD')
subject.rect('OL').should eq('NA')
subject.rect('DI').should eq('BE')
subject.rect('NT').should eq('KU')
subject.rect('ES').should eq('MO')
end
end
context 'digraph decoding' do
it 'should encode digraphs in same row' do
subject.row('LA', true).should eq('PL')
subject.row('YF', true).should eq('AY')
subject.row('LF', true).should eq('PY')
subject.row('EM', true).should eq('RX')
subject.row('NS', true).should eq('KQ')
subject.row('WZ', true).should eq('VW')
end
it 'should encode digraphs on same column' do
subject.col('OD', true).should eq('DE')
subject.col('RU', true).should eq('LN')
end
it 'should encode rectangle digraps' do
subject.rect('BM').should eq('HI')
subject.rect('BZ').should eq('HT')
subject.rect('IF').should eq('MP')
subject.rect('ZB').should eq('TH')
subject.rect('XD').should eq('EG')
subject.rect('NA').should eq('OL')
subject.rect('BE').should eq('DI')
subject.rect('KU').should eq('NT')
subject.rect('MO').should eq('ES')
end
end
end
class PlayfairSquare
SQUARE = (97..(97+25)).map(&:chr).map!(&:upcase)
SQUARE.delete('J')
attr_reader :rows, :cols
def initialize(key_ary)
raise "A #{Array} instance is expected" unless key_ary.is_a? Array
@rows = key_ary + (SQUARE - key_ary)
@cols = [rows[0..4], rows[5..9], rows[10..14], rows[15..19], rows[20..24]].transpose.flatten
end
end
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
require 'spec_helper'
require 'playfair_cipher'
describe PlayfairSquare do
describe 'fills square by default' do
subject { PlayfairSquare.new([]) }
it 'should return all square rows' do
expected_square = "A B C D E "
expected_square << "F G H I K "
expected_square << "L M N O P "
expected_square << "Q R S T U "
expected_square << "V W X Y Z "
subject.rows.should eq(expected_square.split)
end
it 'should return all square cols' do
expected_square = "A F L Q V "
expected_square << "B G M R W "
expected_square << "C H N S X "
expected_square << "D I O T Y "
expected_square << "E K P U Z "
subject.cols.should eq(expected_square.split)
end
end
end
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
$:.unshift(File.dirname(__FILE__))
require 'rubygems'
require 'rspec'
require 'playfair_square'
require 'playfair_encoder'
require 'playfair_cipher'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment