Rubyでシーザー暗号を総当たりで解いてみた
シーザー暗号とは
シーザー暗号とは、ジュリアス・シーザー(Julius Caesar)が考案した暗号で、アルファベットを何文字かシフトさせることで暗号化を行います。
例えば、3文字シフトさせて暗号化する場合、a
はd
になります。
xyz
など最後の文字は、先頭に循環させてabc
とします。
アルファベットの数が26個あるので、暗号文に対して25通りの複合化パターンを試せば、どれかは正解の平文にたどり着けることになります。
1文字暗号化するたびに、シフトする文字数を変更することで、より複雑に暗号化することができますが、今回はそこまで考慮しません。
Rubyで解読するスクリプトを書いてみる
ある文字列を暗号化した以下の文字を解読してみたいと思います。
以下のスクリプトを書いて、暗号文に対して25通りの複合処理を試してみます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File: decode.rb
class Caesar
attr_reader :replace_from, :replace_to
def initialize(rot: 3)
rot = rot
alphabets = "abcdefghijklmnopqrstuvwxyz"
@replace_from = alphabets + alphabets.upcase
@replace_to = (alphabets[rot..] + alphabets[..(rot-1)]) + (alphabets[rot..] + alphabets[..(rot-1)]).upcase
end
def encode(plain)
plain.tr(@replace_from, @replace_to)
end
def decode(secret)
secret.tr(@replace_to, @replace_from)
end
end
class << Caesar
def brute_force_attack_dump(secret)
(1..25).map do |rot|
decoder = Caesar.new(rot: rot)
{
rot: rot,
plain: decoder.decode(secret)
}
end
end
end
# main
if __FILE__ == $0
while line = gets
line.chomp!
puts "Secret #{line}:"
Caesar.brute_force_attack_dump(line).each do |decoded_object|
puts "[ROT:#{decoded_object[:rot].to_s.rjust(2)}] #{decoded_object[:plain]}"
end
end
end
Caesar
クラスは、何文字シフトさせるかをインスタンス作成にrot
で指定することで、その値に従った暗号化処理と複合化処理を提供します。
そして、今回の解読は、クラスメソッドbrute_force_attack_dump
で実施しており、このメソッドに暗号文を渡すと、暗号文を総当たりで複合化した結果が返ってきます。
処理としては、以下のことをやっています。
rot
文字シフトさせた文字列で複合するCaesarインスタンス
を作成- 与えられた暗号文を複合化する
- 上記をrot:1~25まで繰り返す
- 結果をオブジェクトのリストで返す
実際に実行すると以下の一覧が得られます。
この中から、文章を構成していそうな単語を含む文章を抽出してみます。
今回は、is
という文字列を含むものを抽出してみると以下の1行がヒットします。
スペースやドットが削除されているため、少しわかりずらいですが、I am a cat. There is no name yet.
という文章が10文字シフトして暗号化されていたことがわかりました。
まとめ
古典暗号として広く知られているシーザー暗号
について学びました。
単純な仕組みですが暗号化途中でシフトする文字数を変えるなどすると、組み合わせが増えていくので、その場合の解読方法なども今後考えてみたいです。
おまけ
暗号化は以下のスクリプトで実施しています。
1
2
3
4
5
6
7
8
9
10
if __FILE__ == $0
plain_text = 'IamacatThereisnonameyet'
encoder = Caesar.new(rot: ARGV[0].to_i)
puts "ROT: #{ARGV[0].to_i}"
puts "Replace from: #{encoder.replace_from}"
puts "Replace to : #{encoder.replace_to}"
puts "Plain text: #{plain_text}"
puts "Secret message: #{encoder.encode(plain_text)}"
end
実行結果