『暗号の国のアリス』(著:結城浩)の中でシーザー式暗号というものが取り上げられています。これは、暗号化したい平文のアルファベットをそれぞれ、アルファベット順にn文字前後にずらすといったものです。例えば、"abc"なら3文字ずらしで"def"といった具合です。
実装法1 - テーブルを使う
どうしようか迷い、最初はテーブルを使うものにしました。つまり、string配列→char配列にし、それぞれの文字のn文字先を、予め用意したテーブルから持ってきます。この場合のキモは、n文字先がテーブルのどの要素に成るかの計算です(と言っても簡単なクイズレベルですけど)。難点としては、コードが長くなるのと、for文の中にforeachが入り二重ループになりそうなことです。
この時、大文字(0x41~0x5A)と小文字(0x61~0x7A)を分けるのが面倒だったので、最初の文字列を.ToLower()し、小文字に揃えました(今回はこれでオッケー)。
実装法2 - 拡張メソッドを使う
で、さっきのページでC#の拡張メソッドを見て、「なんだもっと簡単に書けるじゃん」ということで、結局次のようになりました。
static void Main(string[] args) { var str = "PELCGBTENCUL"; //対象文字列 foreach(var i in Enumerable.Range(0, 26).ToArray()) { Console.WriteLine("{0} : {1}", i, new string(str.ToLower().Select(c => (char)('a' + ((i + c - 'a') % 26))).ToArray())); } }
すると、以下のようになり、13文字後ろにずらす(or??文字前にずらす)と、元の平文と思われる"cryptography"が分かります。
拡張メソッドはお勉強中なのですが、実践できる機会があってよかったです。
コードとしては、
拡張メソッドのように、関数の戻り値から、また数珠つなぎのように関数を呼び出し、1行に収めるのはなかなか楽しいですね。
結局、元の平文が人間味のある∧既知の単語∧平文候補が1つを覗いて辞書に載ってる単語じゃないとだめなのでは…
こればかりは、使い捨てパッドが解読不可能である数学的証明を知らないと、真実に辿り着けなさそうですね…
それと、暗号はやはり戦争に於いても利用されたようですが(ナチスドイツのエニグマetc)、「どうやって鍵を秘密裏に運ぶか」ということに苦心したんですね。前線の兵に秘密鍵を渡す方法というのも気になってきました。
総あたり(といっても26回)した時の例 |
コードとしては、
- str.ToLower()で、まず小文字に統一します。
- .Select()で、変数str中の各要素(char)に対して、そのi文字先(ただし、'z' + iなどはASCIIの小文字の範囲を超えるので、i文字先(i + c)から、先頭の文字を引き(- 'a')、更に26で割ることでどのアルファベットか割り出し(% 26)、オフセット0x61のために'a'を足す。という面倒なことをしています。
- .ToArray()でchar[]にしています。
- new string(...)は、char[]→stringにするときの常套手段みたいです。でもnewした割りにはその変数すぐ消えるし、なんかちょっと気持ち悪いですね…
ぎもん
この本では、この後の使い捨て暗号(ワンタイムパッド)が解読不可な理由として、「全ての平文の候補が登場するため、どれが答えかわからない」と説明されています。これには、全てが同じ平文とかが登場するから、と例で示されています。でも、シーザー式暗号のn(0 < n < 26)文字復元をしても、結局どれが平文か分からないような気もする…?(もしかしたら平文は単語ではないのかもしれない)結局、元の平文が人間味のある∧既知の単語∧平文候補が1つを覗いて辞書に載ってる単語じゃないとだめなのでは…
こればかりは、使い捨てパッドが解読不可能である数学的証明を知らないと、真実に辿り着けなさそうですね…
それと、暗号はやはり戦争に於いても利用されたようですが(ナチスドイツのエニグマetc)、「どうやって鍵を秘密裏に運ぶか」ということに苦心したんですね。前線の兵に秘密鍵を渡す方法というのも気になってきました。