暗号解読

暗号クラブ

図書館の新書コーナーに、 暗号クラブ なんて言う、お子様向けの本が何冊か置いてあった。丁度opensslがらみで、ごにゅごにょやってたものだから、お子様モードで暗号やれよって言うご神託と思って、1冊だけ借りてきた。 ええ、あくまで孫に読み聞かせる風を装ってね。女房には、馬鹿にされましたよ。

このシリーズは人気みたいで、すでに18巻も出てるとか。前回の中坊向けの素数の話と言い、最近は、こういう基礎からの学習がトレンドなんですかね。

色々な暗号が紹介されてる。手旗信号とかモールス信号とか元素暗号とか。。初めて聞くよ、元素暗号って。

金は元素記号でAuだ。だから金と書いて有ったら、それはAと思ってくれ。こんな調子でZnは亜鉛ね。科学が得意な人は、すらすら解読出来るはず。ただQで始まる元素は、まだ無い(誰もそんな名前を付けないから)。

ruby

本を読むだけじゃ、受け身の姿勢。ここはもう、自分で解いてみたいぞ。ネットを漁ったら、 おあつらえ向きなやつが出てきた。 暗号解読に挑戦! ですって。

ruby語で解説されてるので、まずはrubyを入れる所からだな。

OpenBSDでは、ご丁寧な事に、2.5, 2.6, 2.7の3種が用意されてた。親切なメンテナさんもいるものだ。迷わず、一番新しいのを頼んだ。

ob$ ruby -v
ruby 2.7.1p83 (2020-03-31 revision a0c7c23c9c) [x86_64-openbsd]

そこまではいいんだけど、インストールの最後で

If you want to use this package as your default system ruby, as root
create symbolic links like so (overwriting any previous default):
 ln -sf /usr/local/bin/ruby27 /usr/local/bin/ruby
 ln -sf /usr/local/bin/erb27 /usr/local/bin/erb
 ln -sf /usr/local/bin/irb27 /usr/local/bin/irb
 ln -sf /usr/local/bin/rdoc27 /usr/local/bin/rdoc
 ln -sf /usr/local/bin/ri27 /usr/local/bin/ri
 ln -sf /usr/local/bin/rake27 /usr/local/bin/rake
 ln -sf /usr/local/bin/gem27 /usr/local/bin/gem
 ln -sf /usr/local/bin/bundle27 /usr/local/bin/bundle
 ln -sf /usr/local/bin/bundler27 /usr/local/bin/bundler
 ln -sf /usr/local/bin/racc27 /usr/local/bin/racc
 ln -sf /usr/local/bin/racc2y27 /usr/local/bin/racc2y
 ln -sf /usr/local/bin/y2racc27 /usr/local/bin/y2racc

こんなに関係者が居たっけ? bundleなんて知らないなあ。Ruby Dependency Managementですってさ。レールからの強い要望で用意するはめになりましたって事かな。

ri

riってのは、道案内をしてくれるみたいだ。生憎ri用の原稿が無いので、自分で作る。

gmake install-doc
  :
./miniruby -I./lib -I. -I.ext/common  ./tool/runruby.rb --extout=.ext  -- 
--disable-gems -r./x86_64-openbsd6.7-fake ./tool/rbinstall.rb --make="gmake" 
--dest-dir="" --extout=".ext" --mflags="" --make-flags="" --data-mode=0644 
--prog-mode=0755 --installed-list .installed.list --mantype="doc" 
--install=rdoc --rdoc-output=".ext/rdoc" --html-output=".ext/html"
installing rdoc:       /usr/local/share/ri/2.7.0/system

御覧の通り、minirubyが事前に作成されて、そいつを動かしてインストールするみたいだ。 結構な時間がかかったぞ。

が、原稿が見つからない。何処を期待してるかと思ったら

ob$ ri --list-doc-dirs
/usr/local/share/ri/2.7/system
/usr/local/share/ri/2.7/site
/home/sakae/.rdoc

実際にインストールされてたのは、2.7.0/ だった。こういうのは、よくある事だね。

ob$ ri

Enter the method name you want to look up.
You can use tab to autocomplete.
Enter a blank line to exit.

>> F ;; TAB
FalseClass        Fiddle            Find              FrozenError
Fcntl             File              Float
Fiber             FileTest          FloatDomainError
FiberError        FileUtils         Forwardable

補完が効くのね。これで、取り合えずスイスイ行くかな。

ひょっとして、 ruby27-ri_docs-2.7.1.tgz は、メンテナさんが用意してくれてた奴かな。 もう遅いぞ。まあ、いいか。

解く

準備も整ったので、早速解読。暗号文は

ob$ cat c.txt
spwwz pgpcjzyp!  hpwnzxp ez esp hzcwo zq nzxafepc dntpynp!  nzxafepc dntpynp td
l mldtd zq esp xzopcy tyqzcxletzy lyo nzxxfytnletzy epnsyzwzrj dfns ld mtr olel
lylwjdtd, lt, lyo lwdz nzxafepc rlxp.  awpldp pyuzj esp hzcwo zq nzxafepc dntpyn
p.  dpp jzf lrlty le seead://end.n.etepns.ln.ua/ndmzzv/

じっと睨んで解く

文をじっくり眺めると、小文字のアルファベットだけが変換されてる。それ以外の記号はそのまま。

最後の単語は、どうやらURLっぽいな。これに気が付けば、もう解けたよ。

seeadってのは、httpsのはず。どれぐらい文字コードがシフトしてるか調べてみる。

ob$ irb
irb(main):001:0> 's'.ord - 'h'.ord
=> 11

文字をコードに直すメソッドが思い出せなくてriで格闘したのは秘密だ。ascii(1)しちゃった方が早かったか。

 96  `    97  a    98  b    99  c   100  d   101  e   102  f   103  g
104  h   105  i   106  j   107  k   108  l   109  m   110  n   111  o
112  p   113  q   114  r   115  s   116  t   117  u   118  v   119  w
120  x   121  y   122  z   123  {   124  |   125  }   126  ~   127 del

暗号を解いた、その先にあったのは、宝石ザクザクじゃなくて、 コンピュータサイエンスの世界へようこそ! こんな所でした。がっかりするな。身に付けた教養は、一生の宝だぞ。

辞書攻撃

辞書(単語帳)を使って、総当たり攻撃やってみたかったのよ。

ob$ cat c.txt | tr ' ' '\n' | wc
      47      43     297

暗号文の単語数を数えてみると、43個あった。上手く暗号が解ければ、辞書にある単語にマッチする確率が高くなるはず。

ob$ cat kdec.rb
#!/usr/local/bin/ruby
# hukugo.rb

angobun='spwwz pgpcjzyp!  hpwnzxp ez esp hzcwo zq nzxafepc dntpynp!  nzxafepc dn
tpynp td ....'

#===== kokokara program hontai =====
hirabun = dec(ARGV[0].to_i, angobun)
puts(hirabun)

まず、元から有ったhukugo.rbをちょっと改造。c.txtの内容を冒頭に埋め込み。メインのdec関数内に、外部からkey値を与えるようにした。そうしておいて、このスクリプトを部品として使うように、下記を書いた。kdec.rbの実行属性を付けるのを忘れるな。rubyの置き場所は要調整の事。

#!/bin/sh

for k in `jot 25`
do
    cnt=0
    for w in `./kdec.rb $k | sed -e 's/  / /g' | tr ' ' '\n'`
    do
             grep -E ^"$w"$ /usr/share/dict/words > /dev/null
             if [ $? -eq 0 ]; then
                 cnt=`expr $cnt + 1`
             fi
    done
    echo $k ' -> ' $cnt
done

ずらす値をkとして、forで回す(linuxな人は、jotの部分をseqに変更しておく事)。ループ内では、成功(辞書に有った)したと言うかマッチ数のカウンターを用意。

内側のforでは、復号を試み、結果のワードを行に分解。sedをかませているのは、スペース2個を1個にしてるだけ。スペース2個は文の区切りなんだろうね。

grepで単語が完全マッチするか、辞書を検索。成功したら(grepの終了ステータスを調べる)カウントアップ。後は、シフト量と成功数を表示。

ob$ ./run.sh
1  ->  2
2  ->  1
3  ->  1
4  ->  2
5  ->  1
6  ->  1
7  ->  2
8  ->  1
9  ->  1
10  ->  3
11  ->  37
12  ->  4
13  ->  2
14  ->  1
15  ->  2
16  ->  1
17  ->  4
18  ->  1
19  ->  3
20  ->  1
21  ->  1
22  ->  3
23  ->  2
24  ->  4
25  ->  6

のんびりと走って結果が出ました。マッチ数が少ないのは、単語の最後にドットからカンマやらビックリマークが付いているからです。

ango.rbを使って、新たな暗号を作成。それを解読させてみた。

1  ->  2
2  ->  1
3  ->  1
4  ->  2
5  ->  37
6  ->  1
7  ->  1
:
24  ->  2
25  ->  1

炙り出しの様に、くっきりと浮かび上がってきますなあ。辞書攻撃は何の知識も必要としない、強力な手法であります。キーの探索範囲が狭い場合は試してみるべし。

Case Haskell

灯台下暗しである。haskell本に、小気味いいコードが掲載されてた。例題にうってつけなんですかね。

ob$ ghci
GHCi, version 8.6.4: http://www.haskell.org/ghc/  :? for help
Prelude> :l cipher.hs
[1 of 1] Compiling Main             ( cipher.hs, interpreted )
Main> crack "spwwz pgpcjzyp!  hpwnzxp ez esp hzcwo zq nzxafepc dntpynp!"
"hello everyone!  welcome to the world of computer science!"

例題に有った暗号文の先頭をcrackしてみた。ちゃんと謎解きしてるね。

(eval-after-load "haskell-mode"
  '(progn
     (define-key haskell-mode-map (kbd "C-c C-t") 'haskell-process-do-type)
     (define-key haskell-mode-map (kbd "C-c C-c") 'haskell-compile)
     (define-key haskell-mode-map (kbd "C-c C-z") 'haskell-interactive-switch)
     (define-key haskell-mode-map (kbd "C-c C-l") 'haskell-process-load-file)))

emacsからhaskell-modeを使うなら、こんな設定をしておくと便利かな。

rot13

上で出て来たシーザー暗号は、余りに有名なものだから、コマンドとして(ゲームエリアに)用意されてる。

NAME
     caesar, rot13 - decrypt caesar cyphers

SYNOPSIS
     caesar [rotation]
     rot13

暗号クラブの一員になった積りで、試してみる。

ob$ caesar 2
abc         ;; input
cde         ;; output
ob$ rot13
abc         ;; input
nop         ;; output

caesar 13 と指定したやつは、rot13と呼ばれている。また、caesarで暗号にしたものは、負数のキーを与える事で、復号出来る。

The frequency (from most common to least) of English letters is as
follows:

      ETAONRISHDLFCMUGPYWBVKXJQZ

Their frequencies as a percentage are as follows:

      E(13), T(10.5), A(8.1), O(7.9), N(7.1), R(6.8), I(6.3), S(6.1),
      H(5.2), D(3.8), L(3.4), F(2.9), C(2.7), M(2.5), U(2.4), G(2),
      P(1.9), Y(1.9), W(1.5), B(1.4), V(.9), K(.4), X(.15), J(.13),
      Q(.11), Z(.07).

暗号解読をやる人の常識。文字の出現頻度がmanにも掲載されてた。欧文モールスコードのトンツー具合と比べてみような。

ソースは、/usr/src/games/caesar の中。

#define ROTATE(ch, perm) \
        isupper(ch) ? ('A' + (ch - 'A' + perm) % 26) : \
            islower(ch) ? ('a' + (ch - 'a' + perm) % 26) : ch

勿論、上で挙げた頻度テーブルもソース中で利用されている。

ob$ cat c.txt | caesar
hello everyone!  welcome to the world of computer science!  computer science is
a basis of the modern information and communication technology such as big data
analysis, ai, and also computer game.  please enjoy the world of computer scienc
e.  see you again at https://tcs.c.titech.ac.jp/csbook/

etc

暗号戦 敵の最高機密を解読せよ

暗号解読の一般的な手順(古典的)

暗号解読

上のどれを見ても、復号した時、人間可読の文字が表れる事を仮定してるようだ。一捻りして、平文に何等かの変換を施しておいて、それを暗号にしたらどうだろう。 そうすれば正しく復号出来たとしても、人には読めないはず。

真っ先に思い付く変換というか加工は、base64化だ。世のOSにはそのためにbase64って言うずばりなコマンドが用意されている。OpenBSDにはズバリが無い代わりにb64encodeとかが用意されてるけど、対象がファイルって事で、少々使い難い。

そんな訳で、nkfに頼ってみる。

ob$ echo -n hello | nkf -MB ; echo
aGVsbG8=
ob$ echo aGVsbG8= | nkf -WmB ; echo
hello

最後に入れてるechoは、改行して見易くする為で直接は関係無い。

で、じっくり見ると、最後にpadの = が入っている。これでは、もうバレバレだね。 やや、base64 and ASCII85 encoding awk scripts base85とか言うのが有るな。知らないのはオイラーだけ? デファクトスタンダードらしい。

python3のbase64モジュールに登録されてるんだから、オレオレフォーマットと言う訳ではなさそうだ。

_b85alphabet = (b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
                b"abcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~")

def b85encode(b, pad=False):
    """Encode bytes-like object b in base85 format and return a bytes object.
        :

プログラマーが使いそうな文字が結構隠せるのね。でも残念な事に、コロンとかスラッシュは、エンコードされたデータ中には出てこない。そこから推測されちゃうか。

だったら、徹底的に隠すって事で、平文をgzとかzipで圧縮してまえ。そのままだと、fileコマンドで正体がバレバレになってしまう。木は森に隠せって事で、そのファイルの前後に、適当なランダムデータをパッディングすれば良い。本当のファイルの位置情報は、冒頭に置けばいいな。

どうでっしゃろ、オイラーのアイデア。CIAから利用させて下さいって、お願いされたりして。 そんな苦労をするなら、トリプルaesでも、素直に使っとけ。ごもっとも。


This year's Index

Home