【Ruby】シーザー暗号(Caesar cipher)をrubyで作ってみた

# 初めに
古代ローマから伝わるもっとも古い暗号化技術の一つであるシーザー暗号をrubyで作ってみました。使われるのはアルファベットとシフトさせる番号だけ。とてもシンプルですが今でも使えるしちゃんと機能します。今回は学習した内容を元に記事を書いてみました。

※内容に間違いなどがある場合はご指摘をよろしくお願いします。

# シーザー暗号とは
普通の英語で書いた文字列をシフトナンバーを利用してずらし、文字列を暗号化したものです。古代ローマのガイウス・ユリウス・カエサル（シーザー）が使用したことから、シーザー暗号（Caesar cipher）と呼ばれました。 


# 原理
 <img width="350px" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/966135/171b18eb-4d4f-52d5-e7d5-a3bcda61efdb.png">
①暗号化
アルファベットの文字列を特定のシフトナンバーを使ってずらしたら完成です。例えば、"abc"という文字列をシフトナンバー"1"で１文字ずつ右にずらすと"bcd"になります。この"bcd"がシーザー暗号です。

②復号
このように暗号化された文字列（暗号文）を暗号化される前の文字列にするのが復号です。シーザー暗号の復号はシフトナンバーさえ分かれば簡単に出来ちゃいます。上記の例で"１文字"ずつ右にずらした文字を逆に左に１文字ずつずらすだけです。"bcd"→"abc"

# 結果物
プログラムを実行したら「暗号」か「復号」のどちらかをユーザーに入力してもらいます。その後、文字列とシフトナンバーを入力。「暗号」もしくは「復号」した文字列が出力されます。

# 作業手順
①やりたいことをまず文章で書く（アルゴリズム）
②細かな問題の記述
③文章を元にフローチャートを書く
④プログラムを書く

#大きな流れ
- アルファベットを用意する
- 「暗号」か「復号」かどちらかを入力する
- 文字列を入力する
- シフトナンバーを入力する
- 結果を表示させる
- もう一度やり直すかプログラムを終了するか選ばせる

# 細かな作業&ロジック
- シフトナンバーがアルファベットの数よりも大きい数字だった場合の対応については余りを利用
- シフトナンバーの数分だけ文字列をずらす必要があるため、それに対応出来るようにアルファベットは配列の形で用意する
- 配列のアルファベット上でずらした結果を知りたいので、それぞれの文字列に対してシフトする前のポジションとシフトした後のポジションを格納する変数を用意する
- ずらした新しいそれぞれのポジションを足していくことで「暗号」か「復号」した文字列を求める

# フローチャート
![スクリーンショット 2021-04-23 9.56.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/966135/a36e0be2-3364-136b-ae0e-2d4330ad5fcc.png)
![スクリーンショット 2021-04-23 9.56.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/966135/ece1a9f9-f6e2-8d5a-47fb-117304a13425.png)

# まずは~~暗号~~暗号化or復号する関数を作る
まず「最初の文字列、シフト数、暗号か復号かのどちらか」の３つの引数を取る関数を作成します。
グローバル変数でアルファベットも忘れずに用意します。

```ruby
#アルファベットを配列で用意しグローバル変数alphabetに格納。シフトナンバーが26文字を超える場合も想定し、a~zの文字列を2回。合計52文字を格納
$alphabet =  ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']

#~~暗号~~暗号化or復号を行う関数を作成
def cipher(start_text, shift, cipher_direction)
  end_text = ""     #完成した文字列を格納する変数を初期化
  if cipher_direction == "復号"    #復号だった場合の処理
    shift *= -1
  end
  split_start_text = start_text.split("")  #入力したテキストを配列にする
  split_start_text.each do |char|   #配列から１文字ずつ取り出しcharという変数で処理
    if $alphabet.include?(char)    #スペースなどの空文字がない場合の処理
      position = $alphabet.index(char)    #index関数を使い、文字のcharが$alphabetのどの添字（index）=位置なのかを探し、変数positionに格納
      new_position = position + shift     #charの位置から右にシフトさせる数(shift数)を足して、新しい位置を求める
      end_text += $alphabet[new_position]   #上記で求めた新しいindexを$alphabet配列の添字として代入。最終的に出来上がる文字列をend_textにして、その変数に足していく
    else         #スペースなどの空文字が入っている場合の処理
      end_text += char   #$alphabetの中に存在しない、空文字もend_textに足す
    end
  end
  puts "#{cipher_direction}文は「#{end_text}」です。"   #結果を出力する
end
```
機能しているのか確認します。暗号化対象の「i love you」、シフト数「６」、「暗号」を引数にして結果を出力。

```ruby
cipher("i love you",6,"暗号")  #rubyファイルの最後に追記

❯ ruby caesar_cipher.rb         #コマンドラインで実行
暗号文は「o rubk eua」です。
```

# メインの処理を記述
loop文を使って「いいえ」を入力するまで繰り返し実行されるようにします。また、シフト数が26を超えてしまった場合は26を割った余りをシフト数に再代入し、エラーが起きないようにします。

```ruby
#いいえを入力するまで、繰り返し処理を行う
loop do
  puts "「復号」かもしくは「暗号」を入力してください。"   
  direction = gets.chomp
  puts "文字列を入力してください。"
  input_text = gets.chomp
  text = input_text.downcase
  puts "シフト数を入力してください。"
  input_shift = gets.to_i

  shift = input_shift % 26   #シフト数が26文字を超えた場合には26で割った数の余りでシフト数を求める

  cipher(text, shift, direction)  #cipher関数の実行

  puts "もう一度やりますか？「はい」か「いいえ」を入力してください。"
  restart = gets.chomp
  if restart == "いいえ"            #いいえを入力した場合はさようならが出力され、プログラムを終了
    puts "さようなら"
    break     #break文でプログラムを終了
  end
end
```
実行結果は以下の通り

```cl
❯ ruby caesar_cipher.rb
「復号」かもしくは「暗号」を入力してください。
暗号
文字列を入力してください。
i love you
シフト数を入力してください。
10
暗号文は「s vyfo iye」です。
もう一度やりますか？「はい」か「いいえ」を入力してください。
いいえ
さようなら
```

#参考記事
https://ja.wikipedia.org/wiki/%E3%82%B7%E3%83%BC%E3%82%B6%E3%83%BC%E6%9A%97%E5%8F%B7
http://www.iwakuni-h.ysn21.jp/teachers/yamashita/h30site/site26/f2605/h2605.html
https://qiita.com/may88seiji/items/ce9396a4c267a3d449ae
https://www.sejuku.net/blog/60402
https://26gram.com/ruby-loop