Skip to content

Instantly share code, notes, and snippets.

@nickcherry
Last active August 29, 2015 14:12
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 nickcherry/bb952cc12a9baee76a29 to your computer and use it in GitHub Desktop.
Save nickcherry/bb952cc12a9baee76a29 to your computer and use it in GitHub Desktop.
Caesar Cipher
# To run the script:
# 1. Download caesar_cipher.rb
# 2. Download dictionary.json to the same directory (https://gist.githubusercontent.com/nickcherryjiggz/a615b4a39abedb84c50f/raw/3a8cec1c61cc6431bb84bdca19cab5ffb758ab8e/dictionary.json)
# 3. Open your terminal
# 4. Navigate to the directory where caesar_cipher.rb and dictionary.json live
# 5. Run the following command: ruby caesar_cipher.rb
require 'json' # We'll need this to parse the dictionary.
require 'open-uri' # We'll need this to fetch the dictionary from Github, if it's not available locally.
class Shift
attr_accessor :position, :offset, :segment_length
def initialize(position, offset, segment_length=nil)
@position = position
@offset = offset
@segment_length = segment_length
end
def to_a
[position, offset, segment_length].compact
end
def to_s
to_a.to_s
end
def self.build_from_array(array)
array.collect {|shift| Shift.new(*shift) }
end
end
class CaesarCipher
ALPHABET = ('a'..'z').to_a + [' ']
ALPHABET_SIZE = ALPHABET.count
ALPHABET_LOOKUP = ALPHABET.each_with_index.each_with_object({}) {|(v,i), h| h[v] = i }
DICTIONARY_FILE_PATH = './dictionary.json'
DICTIONARY_URL = 'https://gist.githubusercontent.com/nickcherryjiggz/a615b4a39abedb84c50f/raw/3a8cec1c61cc6431bb84bdca19cab5ffb758ab8e/dictionary.json'
RAW_DICTIONARY_DATA = File.exists?(DICTIONARY_FILE_PATH) ? File.read(DICTIONARY_FILE_PATH) : open(DICTIONARY_URL).read
DICTIONARY = JSON.parse(RAW_DICTIONARY_DATA).each_with_object({}) {|w,d| d[w] = true }
class << self
def generate_key(plain_text)
plain_text.strip.chars.each_with_index.each_with_object([]) do |(char, i), key|
if i == 0 then key << Shift.new(i, generate_random_offset)
elsif char == ' ' and rand(0...10) == 0 then key << Shift.new(i + 1, generate_random_offset) end
end
end
def encrypt(plain_text, key)
apply_key plain_text, key
end
def decrypt(encrypted_text, key)
apply_key encrypted_text, key, true
end
def crack_key(encrypted_text)
normalize_key find_next_key(encrypted_text)
end
private
def generate_random_offset
rand(0...ALPHABET_SIZE)
end
def shift_char(char, offset)
ALPHABET[(ALPHABET_LOOKUP[char] + offset) % ALPHABET_SIZE]
end
def shift_until_space(text, starting_position, offset)
(starting_position...text.length).reduce('') do |word, position|
char = shift_char(text[position], offset)
return word if char == ' '
word + char
end
end
def find_shift_for_next_valid_word(text, starting_position, starting_offset)
(starting_offset...ALPHABET_SIZE).each do |offset|
word = shift_until_space(text, starting_position, offset)
if DICTIONARY.has_key? word
return Shift.new(starting_position, offset, word.length + 1)
end
end
nil
end
def find_next_key(encrypted_text, key=[], starting_position=0)
offset, shift, next_shift = 0, nil, nil
while offset < ALPHABET_SIZE
shift = find_shift_for_next_valid_word(encrypted_text, starting_position, offset)
return nil if shift.nil?
next_position = starting_position + shift.segment_length
return key.dup.push(shift) if next_position >= encrypted_text.length
next_shift = find_next_key(encrypted_text, key, next_position)
break if next_shift
offset = shift.offset + 1
end
key.dup.push(shift).push(next_shift) if next_shift
end
def apply_key(text, key, reverse=false)
accumulated_offset = 0
key.each_with_index.reduce('') do |result, (shift, i)|
offset = reverse ? -shift.offset : shift.offset
accumulated_offset += offset
segment_length = i + 1 < key.count ? key[i + 1].position - shift.position : text.length
result += text.slice(shift.position, segment_length).chars.reduce('') do |result, char|
result << shift_char(char, accumulated_offset)
end
end
end
def normalize_key(key)
return nil unless key
accumulated_offset = 0
key.flatten.select do |shift|
shift.offset = ALPHABET_SIZE - shift.offset - accumulated_offset
accumulated_offset += shift.offset
shift.position == 0 || shift.offset != 0
end
end
end
end
plain_text = 'known for its feather and wings and legs the chicken lays eggs and eggs and eggs known for the comb atop its crown the chicken lays eggs of white and brown known for its strut when taking a walk the chicken lays eggs bawk bawk bagawk'
key = CaesarCipher.generate_key plain_text
encrypted_text = CaesarCipher.encrypt plain_text, key
cracked_key = CaesarCipher.crack_key encrypted_text
decrypted_text = cracked_key ? CaesarCipher.decrypt(encrypted_text, cracked_key) : nil
LB = "\n\n"
puts "~" * 30 + LB
puts "Plain Text: #{ plain_text }" + LB
puts "Key: #{ key.map(&:to_a) }" + LB
puts "Encrypted Text: #{ encrypted_text }" + LB
if cracked_key.nil?
puts "Unable to crack the key. 8^(" + LB
else
puts "Cracked Key: #{ cracked_key.map(&:to_a) }" + LB
puts "Decrypted Text: #{ decrypted_text }" + LB
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment