Haskell でヴィジュネル暗号を解いているのを見つけた。
cf. SECCON 2016 ヴィジュネル暗号をHaskellで解いてみる – 0x90
ヴィジュネル暗号の解説は Wikipedia に詳しい。見てみると簡単そうだったからやってみよう。もちろん Elixir で。
defmodule Vigenere do def encrypt(plain, key) do String.to_charlist(plain) |> Enum.zip(Stream.cycle(String.to_charlist(key))) |> Enum.map(fn {p, k} -> rem((p + k - 65 * 2), 26) + 65 end) |> to_string end def decrypt(cipher, key) do String.to_charlist(cipher) |> Enum.zip(Stream.cycle(String.to_charlist(key))) |> Enum.map(fn {c, k} -> rem((c - k + 26), 26) + 65 end) |> to_string end end IO.puts Vigenere.encrypt("CODE", "ARM") IO.puts Vigenere.decrypt("CFPE", "ARM")
^o^ > elixir vigenere.exs CFPE CODE
英大文字のみに対応。65 を足したり引いたりしてるのは A
~ Z
の文字と 0 ~ 25 を対応付けるため。
さて、上記のようにうまくいったわけだけど、一つだけハマりどころがあった。それは Elixir には剰余を求める mod
が見当たらないこと。代わりに余りを求める rem
を使ったわけだけど、最初 mod
と同じつもりで下のように書いたら、復号化に失敗した。
defmodule Vigenere do def encrypt(plane, key) do String.to_charlist(plane) |> Enum.zip(Stream.cycle(String.to_charlist(key))) |> Enum.map(fn {p, k} -> rem((p + k - 65 * 2), 26) + 65 end) |> to_string end def decrypt(cipher, key) do String.to_charlist(cipher) |> Enum.zip(Stream.cycle(String.to_charlist(key))) |> Enum.map(fn {c, k} -> rem((c - k), 26) + 65 end) |> to_string end end IO.puts Vigenere.encrypt("CODE", "ARM") IO.puts Vigenere.decrypt("CFPE", "ARM")
結果はこう:
^o^ > elixir vigenere.exs CFPE C5DE
O になるべきところが 5 になってしまっている。原因は mod
と rem
の違い。Haskell には両方あるので試してみると:
Prelude> mod (-10) 3 2 Prelude> rem (-10) 3 -1
仕方がないので、26 を足してから rem
を適用した。でもホントにこれでいいのか?
[追記]
上のスクリプトを見ると、encrypt
も decrypt
もほとんど同じで、違うのは map
の中の匿名関数だけだ。なので、共通部分をくくりだしてみた。
defmodule Vigenere do def encrypt(plain, key) do crypt(plain, key, fn {p, k} -> rem((p + k - 65 * 2), 26) + 65 end) end def decrypt(cipher, key) do crypt(cipher, key, fn {c, k} -> rem((c - k + 26), 26) + 65 end) end defp crypt(string, key, f) do String.to_charlist(string) |> Enum.zip(Stream.cycle(String.to_charlist(key))) |> Enum.map(f) |> to_string end end IO.puts Vigenere.encrypt("CODE", "ARM") IO.puts Vigenere.decrypt("CFPE", "ARM")
^o^ > elixir vigenere2.exs CFPE CODE
OK。うまくいってる。