I really like Ruby and want to get better at it. Any input is appreciated.
Description
This program prompts the user for a string which is ciphertext resulting from a Caesar cipher given (English) plaintext and a key on the range [0, 25]. The program then uses a table of character frequencies of the English language to perform simple cryptanalysis to determine the top 5 most probable shift keys, and displays the resulting plaintext when these keys are used to decrypt the ciphertext.
It should also be noted that the only input expected is letters and whitespace, but I think it works with most punctuation.
Code:
class Cryptanalysis
def initialize
@alphabet = ('A'..'Z').to_a.join
@english_frequency = { :A => 0.080, :B => 0.015, :C => 0.030,
:D => 0.040, :E => 0.130, :F => 0.020,
:G => 0.015, :H => 0.060, :I => 0.065,
:J => 0.005, :K => 0.005, :L => 0.035,
:M => 0.030, :N => 0.070, :O => 0.080,
:P => 0.020, :Q => 0.002, :R => 0.065,
:S => 0.060, :T => 0.090, :U => 0.030,
:V => 0.010, :W => 0.015, :X => 0.005,
:Y => 0.020, :Z => 0.002 }
end
def decrypt_caesar(ciphertext, shift)
i = shift % @alphabet.size
decrypt = @alphabet
encrypt = @alphabet[i..-1] + @alphabet[0...i]
ciphertext.tr(encrypt, decrypt)
end
def analyze(ciphertext)
chars = ciphertext.gsub(/[^A-Z]/i, '').split(//)
ciphertext_frequency = character_frequency(chars)
correlation_of_frequency = Hash.new
alphabet = @alphabet.split(//)
(0..25).each { |i|
sum = 0.0
alphabet.each { |c|
e = alphabet.index(c)
sum += ciphertext_frequency[c.to_sym] * @english_frequency[alphabet[(26 + e - i) % 26].to_sym]
}
correlation_of_frequency[i] = sum
}
correlation_of_frequency.sort_by { |_, v| -v }.take(5).to_h
end
def character_frequency(chars)
frequency = Hash.new
inc = 1.0 / chars.size
@alphabet.each_char { |c| frequency[c.to_sym] = 0.0 }
chars.each { |c| frequency[c.to_sym] += inc }
frequency
end
end
crypt = Cryptanalysis.new
puts 'Enter a string to analyze'
text = gets.chomp.upcase
result = crypt.analyze(text)
puts 'Top 5 possible shifts: '
choice = 1
result.each do |k, v|
puts '%10s) Shift amount: %2s Correlation: %%%.4f Result: %s' % [choice, k, v, crypt.decrypt_caesar(text, k)]
choice += 1
end