caesar

new TV

サイクリングと山登りと猫の番組しか映らないTVになっちゃった。そう、10年近く使ったTVが、NHKBSプレミアムしか映らなくなっちゃったんだ。改めて(やむにやまれず)このチャンネルを見てると、いかにNHKが手抜きしてるか、よく分かる。金返せ、再放送を見る為に、視聴税を払ってる積りはないぞ。

ぶつくさ言っててもしょうがない。運よくあべちゃんからおこずかいが出たので、78K(円)のTVを買ったよ。コロナで弱った経済の立て直しです。あぶく銭はさっさと使ってしまいましょう。こういう時は、年金生活者も強気であります。

アンドドロイドTVだった。ぐぐる戦略にしっかりTV屋は協力してるね。トロンはどうしたと言いたいぞ。

電波には、右巻きと左巻きが有る事をセットアップ作業中に知った。空から降ってくる電波は右巻き。ケーブルを流れてくるやつは、左巻きだったかな。どちらにも対応してるとか。そんなの知らなかった。いや、円偏波って事か。昔やったような覚えがあるぞと、電波少年は記憶をたぐっております。

リモコンは、ブルーツゥースで接続。進歩してるね。Wi-Fiも完備。勿論4Kなやつ。これだけ揃って、10年前の約半値だ。女房喜ぶユーチューブも大画面で閲覧可。ただ、USB-HDD内に保管してた秘蔵ビデオは見れない。OSが変わって古いTVのフォーマットを受け付けず。バッサリと初期化ですよ。

記念にpingしてみたぞ。

64 bytes from xxx.xxx.xxx.xxx: icmp_seq=0 ttl=64 time=76.434 ms
64 bytes from xxx.xxx.xxx.xxx: icmp_seq=1 ttl=64 time=10.355 ms
64 bytes from xxx.xxx.xxx.xxx: icmp_seq=2 ttl=64 time=6.086 ms
64 bytes from xxx.xxx.xxx.xxx: icmp_seq=3 ttl=64 time=6.400 ms
64 bytes from xxx.xxx.xxx.xxx: icmp_seq=4 ttl=64 time=10.204 ms
64 bytes from xxx.xxx.xxx.xxx: icmp_seq=5 ttl=64 time=10.579 ms

無線同士なんで、こんなものなのかな。で、次にやる事は port scan かな(をぃ)。何たって相手は、表示器がやたらに大きいパソコンですから。但し入力系は貧弱。

TF-IDF

と思っていたら、ぐぐる様の音声検索が使えるのね。リモコンにマイクが仕込まれていて、PTT(push to talk)宜しく、ボタンを押して、検索語句を喋れば、ぐぐるに思っている事が筒抜けになるぞ。黙っていればしっかりと、証拠に録音までされてしまう。人工知能の恐るべし。

いや、音声認識はまだまだ完成の域に達していない。俺様が喋るズーズー弁風な関西語をちゃんと理解しない。だったら、へいぐぐるとか言った後、クィーンズえんぐりっしゅ語で喋れー。 そんな事したら、尚更混乱して固まってしまうぞ。

手書き文字を認識しましょうとか、猫と犬の写真を区別してみましょうなんて言う、おめでたい例をやってちゃいかんのよ。時代は音声認識、そこから意味の理解へと進んでいるんだな。

んな訳で、人工知能を広く取り扱った本で俯瞰してみた。面白い事例を発見。

スポニチには三振と言う単語の登場回数(TF)が多い。一般の記事には三振なんて単語はあまり登場しない。これをIDFが大きい状態と言うそうな。

よってスポニチ(の野球記事)では、両者の積は大きくなる。それを捉えれば、何も知らないコンピュータでも、ああ野球の記事だと推測出来る。

オイラーに取って目から鱗なのは、IDFの表現。あまり登場しない単語を大きい値で表しておけば、後は掛け算だけで判定出来る。事前に統計を取って計算しておけば、後は演算スピードが速い掛け算だけですんじゃう。解析時間の短縮を狙っているんだな。

そんな事なんで、少し資料を探してみた。

自然言語処理の基礎技術!tf-idfを簡単に解説!

単語の重要度を測る?TF-IDFとOkapi BM25の計算方法とは

TF-IDF

これらをちゃんとやろうとしたら、 スーパーコンピュータ「富岳」TOP500、HPCG、HPL-AIにおいて世界第1位を獲得 こんなのを使わないといけないのかなあ? 素人には手が出ないぞ。

ruby cipher

前回やった、String.ordの逆関数は何か? SEE ALSOが出てこないので察してください。

(from ruby core)
------------------------------------------------------------------------
  str.ord   -> integer

------------------------------------------------------------------------

Returns the Integer ordinal of a one-character string.

  "a".ord         #=> 97

念の為、ri調べって事を強調しておこう。返値はintegerって言ってる。ならば、逆関数はinteger族に有るに違いない。族って言うのはクラスの別称。クラスは大文字で始める約束よと、あの人が言ってた事を思い出す。

=== Implementation from Integer
------------------------------------------------------------------------
  int.chr([encoding])  ->  string

------------------------------------------------------------------------

Returns a string containing the character represented by the int's value
according to encoding.

  65.chr    #=> "A"
  230.chr   #=> "\xE6"
  255.chr(Encoding::UTF_8)   #=> "\u00FF"

それらしいのが出てきた。これを組み合わせれば、数字転換暗号が出来そうだな。やってみるか。

sakae@pen:/tmp$ irb
irb(main):001:0> Encoding.find('locale')
=> #<Encoding:UTF-8>

sakae@pen:/tmp$ echo $LANG
en_US.UTF-8

ふむ、英語の習慣が身に付いている環境だけど、文字コードは世界標準だ。

それじゃ、簡単な暗号用のスクリプト

sakae@pen:/tmp$ cat enc.rb
#!/usr/local/bin/ruby
key=12354
hira = gets.chop
sz = hira.length
for i in 0 .. sz - 1
  puts( hira[i].ord - key )
end

そして、こちらは、復号用のスクリプト。両者共、共通鍵を取り合えず内蔵させてる。

sakae@pen:/tmp$ cat dec.rb
#!/usr/local/bin/ruby
key=12354
an = IO.readlines("|cat")
sz = an.length
for i in 0 .. sz - 1
  printf("%c", (an[i].to_i + key).chr(Encoding::UTF_8) )
end
sakae@pen:/tmp$ cat a.txt
紫陽花や 昨日の誠 今日の嘘

梅雨の季節なので、正岡子規さんの有名な句を借りてきた。(世界一短い三行詩って素敵) これを暗号文にしてみる。

sakae@pen:/tmp$ cat a.txt | ./enc.rb >c.txt
sakae@pen:/tmp$ cat c.txt
19689
26171
21103
66
-12322
13798
13731
44
23134
-12322
7816
13731
44
9686

味わいも何も無い、無味乾燥な数値になった。じゃ、復号してみる。

sakae@pen:/tmp$ cat c.txt | ./dec.rb ; echo
紫陽花や 昨日の誠 今日の嘘

ちゃんと復号出来たよ。日本語は、表意文字である漢字を多用するため、文字種が非常に多い。従って、例で挙げたkey値も非常に広い範囲を設定出来る。ちょっとやそっとで復号するのは難しいだろう。ちなみにkey値は、'あ' の文字コードである。

一つ注意が有る。平文にはなるべく半角文字を使うな。敵に解読の手がかりを与えてしまうぞ。 上の例で言うと、語句の区切りに半角のスペースを使っている。それが暗号文では、-12322 って数字になってる。

同じ数値が2箇所に表われている。しかも負の数だ。敵はこれに着目するだろね。何か所も現れるって事は、区切り文字じゃなかろうか。区切り文字と言ったら普通はスペースだろう。後は、これをヒントに辻褄合わせの鍵を推測する。ね、素人にも出来そうででしょ。

だったら、今世間を席巻しちゃった世界標準文字コードのUTFを止めればどうよ。Windowsでお馴染みの SHIFT_JIS とかUnixで昔流行った EUC_JP とか、ふみ を書く時に使った ISO_2022 とかでどうよ。

いずれも半角文字はASCIIコードだから、身を隠せないぞ。

そうそう、多用する irb は、裸で使うと ipython の機能に負けている。その最たる機能が補完だ。rubyを離れて20年も経つとクラスやらメソッドなんてすっかり忘れてる。そんな時は補完があれば大助かり。遅ればせながら、下記で起動しよう。冒頭の数文字を入れたらTABすれば良い。忘れてたのを思い出させてくれるぞ。

irb -r irb/completion

caesar

前回やったシーザー暗号。OpenBSDってか *BSD 界隈だけじゃあれなんで、Linux界にも移住と言うか移植してみる事にした。

元のソースはOpenBSDを入れないと手に入らないの? いいえ、そんな事はありません。 src/games/caesar/caesar.c ちゃんと、ピンポイントで、取ってこれます。

移住は、エラーとの闘い(って、程でもないけど)を済ませて

debian:tmp$ cc -g caesar.c -lm
debian:tmp$ cat c.txt
spwwz pgpcjzyp!  hpwnzxp ez esp hzcwo zq nzxafepc dntpynp!
debian:tmp$ cat c.txt | ./a.out ; echo
hello everyone!  welcome to the world of computer science!

どうやら移植完了です。これで終わってはもったいないので、少しコード鑑賞します。

/* adjust frequency table to weight low probs REAL low */
for (i = 0; i < 26; ++i)
        stdf[i] = log(stdf[i]) + log(26.0 / 100.0);

一見無駄なような変換(しかもlog関数で)してるけど、コメントを読むと、頻度表を調整して、低確率の重みを小さくする なんていう風になってます。

係数の対数を取って、評価値を対数圧縮しるんだな。この係数値は、普通の英文の文字頻度を表している。それと、暗号文の文字頻度を掛け算して、下記のように最大値(のindex)を求めている。

winnerdot = 0;
for (try = winner = 0; try < 26; ++try) { /* += 13) { */
        dot = 0;
        for (i = 0; i < 26; i++)
                dot += obs[i] * stdf[(i + try) % 26];
        if (dot > winnerdot) {
                /* got a new winner! */
                winner = try;
                winnerdot = dot;
        }
}

obsって配列は、暗号文の文字頻度だ。これが大きいとdot値が大きくなり過ぎるんで、係数を控え目にしてるんだな。

ちょっとlogして、係数値を変更してる所に興味があったので、追ってみる。最初は変換前

(gdb) set print array
(gdb) p stdf
$1 =   {8.0999999999999996,
  1.3999999999999999,
  2.7000000000000002,
  3.7999999999999998,
  13,
  :

途中まで変換した図。

(gdb) p stdf
$2 =   {0.74479041371178378,
  -1.0106014113453963,
  -0.35382187495632578,
  3.7999999999999998,
  13,
  :

マイナスの値が有るって事は、スレッショルドを log(26.0 / 100.0)で決めてるんだな。

gosh> (log (/ 26.0 100.0))
-1.3470736479666092
gosh> (exp 1.3470736479666092)
3.846153846153846

1以下のログを取ると負数になるんだな。これの絶対値になる元の値を求めると3.8か。と言う事は、3.8以下の頻度はカスです。無視してもいいよって決めてるんだな。いえね、26.0なんて言ういわくありげな数値が有ったので、少々気になったのさ。

このlog関数を使って、演算データが大きくならないようにする技法、それと基準データを乗算して、目的物を見つける方法って、上で出て来た TF-IDF とそっくりだな。IDFの方はめったに出てこない単語を炙り出す為、逆数を取ってるけどね。

こちらは、一番出て来るやつを探したいので、素直に統計データを使えばいいって違いはあるけどね。

haskell cipher

前回、さっと流してしまったhaskellの例題を、咀嚼してみる。ゆっくり噛むと滋養強壮になりますからね。まずは、少し復習をば。なんせ、すぐに忘れる脳になっちゃってますからね。

core technology

例題の中で、重要な役目を果たしているのは、統計データから集めた頻度表と、暗号文字の頻度表が、どれぐらい変位(シフト)してるかを算出する部分。

簡単にカイ二乗検定ってので求まるよって説明してる。統計データには統計ツールで対抗しろって事だな。

chisqr :: [Float] -> [Float] -> Float
chisqr os es = sum [((o-e)^2)/e | (o,e) <- zip os es]

暗号文字の頻度配列(os)と統計的な頻度配列(es)を比べてみなさい。誤差を加算して、それが少なければ一致してる可能性が高いとな。

_>  chisqr [5,1,2,3,4] [1,2,3,4,5]  -- 1項目シフト
17.283335
_>  chisqr [1,2,3,4,5] [1,2,3,4,5]  --  シフト無し
0.0
_>  chisqr [3,4,5,1,2] [1,2,3,4,5]  --  2項目シフト
11.383334

分かり易いように2つの配列の値は同一、但し、項目のシフト有り、無しで試してみた。

現場では、暗号文字の配列がキーの値だけずれた状態になってる。そのずれを、上の関数で見つけようって事だ。

_>  chisqr [1,3,3,4,5] [1,2,3,4,5]
0.5
_>  chisqr [2,3,3,4,6] [1,2,3,4,5]
1.7

実際は、頻度の傾向が2者共一致するなんて、稀な事だろう。それを想定して、暗号文字頻度を多少いじってみた。項目シフトよりは、検出値が小さいね。値の変動に対しては頑丈な作りになってる事が分かる。

詳しくは下記で。

カイ二乗検定 これは、分かり易い。haskellの関数と全く一緒の事をしてる。

カイ二乗検定

実際の利用場面では、暗号頻度リストを回転して、chisqrを計算。これを繰り変えて、一番少ない値をマークしたシフト数を、鍵として採用してる。上手い方法だね。

main

例題は、実験をやり易いように、関数が置いてあるだけ。こいつにmainを足して、コンパイルすれば、晴れて caesar の haskell版に成るかな。えと、mainでは、crackを呼べばいいんだな。暗号文は外からやって来た汚いやつ。それを平文に変換してから表示すれば良い。

汚いデータは中の方まで波及しない。こんなんでいいのかな。

main :: IO ()
main = do
  str <- getContents
  print $ crack str

そして、haskell-mode中で、コンパイル。

-*- mode: haskell-compilation; default-directory: "/tmp/" -*-
HsCompilation started at Fri Jun 26 13:27:15

ghc -Wall -ferror-spans -fforce-recomp -c /tmp/mycipher.hs

/tmp/mycipher.hs:36:22-28: warning: [-Wtype-defaults]
    * Defaulting the following constraints to type `Integer'
        (Integral b0)
          arising from a use of `^' at /tmp/mycipher.hs:36:22-28
        (Num b0) arising from the literal `2' at /tmp/mycipher.hs:36:28
    * In the first argument of `(/)', namely `((o - e) ^ 2)'
      In the expression: ((o - e) ^ 2) / e
      In the first argument of `sum', namely
        `[((o - e) ^ 2) / e | (o, e) <- zip os es]'
   |
36 | chisqr os es = sum [((o-e)^2)/e | (o,e) <- zip os es]
   |                      ^^^^^^^

HsCompilation finished at Fri Jun 26 13:27:16

文句を言われつつも、最終目的地のアプリは作成されず。

-rw-r--r--  1 sakae  wheel   1451 Jun 26 13:09 mycipher.hs
-rw-r--r--  1 sakae  wheel   1262 Jun 26 13:27 mycipher.hi
-rw-r--r--  1 sakae  wheel  32192 Jun 26 13:27 mycipher.o

ここまでしか、やってくれない仕様なの。ならば、続きはghcでって事かな。

ob$ ghc mycipher.hs
[1 of 1] Compiling Main             ( mycipher.hs, mycipher.o ) [flags changed]
Linking mycipher ...
ob$ ls -l mycipher
-rwxr-xr-x  1 sakae  wheel  2296696 Jun 26 13:34 mycipher*

でけた。

ob$ echo "spwwz pgpcjzyp!  hpwnzxp ez esp hzcwo zq nzxafepc dntpynp!" | ./mycipher
"hello everyone!  welcome to the world of computer science!\n"

動いた。


This year's Index

Home