MongoDB (2)

今年のノーベル文学賞は、春樹に変わってイシグロさん。春樹は何時も候補に挙がって沸かせて くれるけど、幸運の女神さんは微笑んでくれない。微笑むのは、あの国の賭け好きの会社なり。

ミーハーな女房は、早速図書館の蔵書検索に走る。そして絶望を知る。リクエスト待ちが数十人 ですって。諦めて、本屋に走るが、影も形も無しの状況。

穴馬、大穴なんで、早川書房さんは何も用意してなかったんだな。春樹本は発表前から山積みに なって、当選を狙っていたのにね。

日本は昔から外圧に弱い。外国で絶賛されたものをありがたがる習性が染みついているな。 自分でどうこうしようと言う態度無し。

この間、本屋に行ったら、山積みされてた。商機を逃してなるものかとばかり、大増刷されて た。みんな文庫本。ハードカバーで、これ見よがしに飾ろうとした人は、あてが外れましたな。

女房も、一冊買ってきた。読み始めた所らしいけど、春樹よりは読みやすいとの事。

何処かの出版会社の社長が言ってたな。図書館は文庫本を買わないでくださいと。オイラーも そう思うぞ。安く読めるんだから、自分で買えよ。図書館はそんなに、民衆に迎合する必要無し。出版業界を応援してあげなさい!!

オイラーは、時代遅れと言うか、名作をゆっくり読んでいる。陸王みたいにタイアップした やつは興味無し。

たまたま、NHKのサラ飯で出て来た、故米原真理さんに興味を覚えて、遅ればせながら手に してみた本がある。『噓つきアーニャの真っ赤な真実』って本。題名から、なんかおちょくって るなと、思ったけど、これ、オイラー的には、大当たり。チェコの学校で机を並べた級友を 20年だかたってから、訪ね充てる物語が3編収録されているんだけど、よくもまあ探し出したなと、感心しきり。体験談だけに、リアリティーが有って、楽しく、そしてちょっぴり悲しい 部分もあって、楽しめましたよ。

面白いので、別の本も読んでみるかな。

binary

前回は、hexeditなんて道具を振り回したものだから、面白いバイナリーものの話は無いものだろうかと、探してみたんだ。そしたら、

就職面接でプログラムの解読を求められた! なんてのに出くわした。ぐぐるの試験を彷彿させるな。一種の暗号解読。知恵と忍耐。

暗号がらみで、 たのしいXOR暗号入門なんてのも。Haskellて指定した訳でもないのに、Haskellが出てくるのは、そういう方面に 向いているから?

暗号解読と言うと、解読して平文のメッセージが出てくれば成功って事で、解説されてる。 平文がbinaryの場合は、どうするねん?

簡単な話で言うと、helloをgzipで圧縮。これで平文はbinaryになる。このbinaryに暗号化を する。さあ、これを解読して、元の平文、helloを復元してみろって訳。

gzipで圧縮されたbinaryは、痕跡が残る。

$ echo hello world > aa
$ gzip -c aa > xx
$ file xx
xx: gzip compressed data, was "aa", last modified: Fri Nov 10 14:44:40 2017, from Unix

暗号をデコードする度に、fileコマンドにかけて、何か出てくるか観察する手口が考えられる。 で、このファイルコマンドのネタ帳がmagicというファイルになってるんで、注目点を 洗い出してみる。(邪悪なLinuxでは、このネタ帳がバイナリーになってる。まるで、 binary大好きな某OSみたいだな。)

$ grep gzip /etc/magic
 :
# gzip (GNU zip, not to be confused with Info-ZIP or PKWARE zip archiver)
0       string          \037\213        gzip compressed data

そうなってるか、確認。

$ hexdump -C xx
00000000  1f 8b 08 08 58 bb 05 5a  00 03 61 61 00 cb 48 cd  |....X..Z..aa..H.|
00000010  c9 c9 57 28 cf 2f ca 49  e1 02 00 2d 3b 08 af 0c  |..W(./.I...-;...|
00000020  00 00 00                                          |...|

この痕跡を消さないと、容易に推測されちゃうな。はて、どうする?

色々手を打てる。アイデア勝負。木は森に隠せとか、切り刻んで、ミンチにしちゃうとか。一度 暗号化しちゃうとか(上の例だと、xxってファイルのSHA512ぐらいでハッシュを取り、それをRC4のキーにするとか)。そういう手を使ったものを、どう解読するかCIAの人に聞いてみたいものだ。

gzip

上でgzipが出てきたので、少し調べておく。

fileコマンドを使った時、圧縮前のファイル名と作成日時が出てきた。それって、ファイル コマンドがネタ帳を元に少し解析してくれたの? 上のダンプを眺めると、確かにファイル名が 埋められているのが観察出来る。よって、ファイル名から足がつかないように、痕跡は消しておけ。

日時は、多分1970年からの経過秒になってると思われるけど、ちと、何処から始まってるか、 目視では分からない。こういう時は、ソースに当たるに限る。

ほとんどの機能をlibzに負っている。本体がどうこうより、ヘッダーファイルに注目だな。 libzと対になるのが、/usr/include/zlib.h

/*
     gzip header information passed to and from zlib routines.  See RFC 1952
  for more details on the meanings of these fields.
*/
typedef struct gz_header_s {
    int     text;       /* true if compressed data believed to be text */
    uLong   time;       /* modification time */
    int     xflags;     /* extra flags (not used when writing a gzip file) */
    int     os;         /* operating system */
    Bytef   *extra;     /* pointer to extra field or Z_NULL if none */
    uInt    extra_len;  /* extra field length (valid if extra != Z_NULL) */
    uInt    extra_max;  /* space at extra (only when reading header) */
    Bytef   *name;      /* pointer to zero-terminated file name or Z_NULL */
    uInt    name_max;   /* space at name (only when reading header) */
    Bytef   *comment;   /* pointer to zero-terminated comment or Z_NULL */
    uInt    comm_max;   /* space at comment (only when reading header) */
    int     hcrc;       /* true if there was or will be a header crc */
    int     done;       /* true when done reading gzip header (not used
                           when writing a gzip file) */
} gz_header;

親切な事に、参照すべきRFCまで載せてくれている。 RFC1952

このRFCとヘッダーを見比べてみると、場合によってはファイル上で存在しないメンバーも 有る事が分かる。ただ、osの種類は、RFCの文書から容易に知れる事は良い事だな。(場合に よっては、あちこちをつつかないと、判明しない事が有るからね。)

OSの種類と更新日を、変更してみるかな。hexeditの出番だな。

$ file xx
xx: gzip compressed data, was "aa", last modified: Mon Jul 13 10:08:00 1970, from NTFS filesystem (NT)

こんな昔に、マイクロソフトなんて有ったっけ? ゲイツはかーちゃんの乳を吸ってたかもな? ああ、彼は1955年生まれだったから、かーちゃんじゃなくてGFの乳って事もあり得るな。ねーちゃんのケツばかり追いかけていたら、Windowsは生まれなかったかもよ。

大げさにソースを引っ張り出したり、RFCを参照までしてみたけど、もう少しカジュアルな方法が有る。上で挙げた、magicをじっと見つめる方法。

>4      ledate          >0              \b, last modified: %s
>9      byte            =0x00           \b, from FAT filesystem (MS-DOS, OS/2, NT)
>9      byte            =0x01           \b, from Amiga
>9      byte            =0x02           \b, from VMS
>9      byte            =0x03           \b, from Unix
>9      byte            =0x04           \b, from VM/CMS
>9      byte            =0x05           \b, from Atari
>9      byte            =0x06           \b, from HPFS filesystem (OS/2, NT)
>9      byte            =0x07           \b, from MacOS
>9      byte            =0x08           \b, from Z-System
>9      byte            =0x09           \b, from CP/M
>9      byte            =0x0A           \b, from TOPS/20
>9      byte            =0x0B           \b, from NTFS filesystem (NT)
>9      byte            =0x0C           \b, from QDOS
>9      byte            =0x0D           \b, from Acorn RISCOS

4バイト目からのデータが0以上だったら、ローカルの時間で表されて更新日時。冒頭の>(複数の場合有り)は、gzipと判明した後に行われる検査レベルを表す。9バイト目は、それぞれ一意のフラグを表す。アミガだとかVMSだとか、亡霊が潜んでいるな。

$ file xx
xx: data

冒頭の2バイトを破壊してみた。fileコマンドのネタ帳にも載っていない形式になっちゃったんで、データですって、当たり前の事しか言わなくなった。忍法隠れ見の術だな。

ああ、上で痕跡を消すのに、ハッシュを取っちゃえって案をあげたけど、これは却下だな。 こんな事をしたら、誰も解読出来なくなっちゃう。

どうしても元ファイルを軽く暗号化したいなら、ファイル長を求める。それを漢数字に直す。 (中国人は読めちゃうから、かなもしくはカタカナがいいかな)文字コードは、時代遅れの EUC-JPなりISO-2022-JPを使う。そしてそれをsha512ぐらいにして、キーを得る。 平文ファイル自身をキーにするしかけだ。簡単にshell語で組めると思うので、やってみれ。

ああ、shell語は汎用だから、haskell語がいいか。

頭を捻ってコードを起こすのもいいけど、unix屋さんは有るものを貼り合わせて、作りあげて しまうのが流儀。暗号化なら直ぐにSSL一族を思い出すな。でも、これって普通にWindowsやリナに入ってるでしょ。BSD系はどうよって調べてみると、FreeBSDにエニグマが入っていた。

ドイツ軍の有名な暗号装置。これを模したもんが公開されてる。

[fb11: tmp]$ echo hello world | enigma > bb
Enter key:
[fb11: tmp]$ hexdump -C bb
00000000  91 f2 f3 e6 66 b3 f6 ae  5a 38 eb 18              |....f...Z8..|

ハロワを暗号化してみた。確かに、ばらけているな。

[fb11: tmp]$ enigma myKey < bb
hello world

そして、復号化。キーをコマンドラインから与えるという、無防備な事をしちゃった。 bashのヒストリーを見れば、一目瞭然じゃん。

エニグマはチューリングにより解読されるはめになった。今なら容易にコンピュータの馬鹿力で 解読出来ちゃうだろう。そういう心配性な人は、 bdesを使うと、少しは安心出来るぞ。

[fb11: tmp]$ echo hello world | bdes > z
Enter key:
[fb11: tmp]$ hexdump -C z
00000000  96 9c c5 cc 1e fd 44 6b  ec 02 d2 28 f7 50 0c d2  |......Dk...(.P..|
[fb11: tmp]$ cat z | bdes -d
Enter key:
hello world

安全を気にするOpenBSDには、enigmaもbdesも入っていない。これは、親分からの使うな危険と いう暗黙のメッセージです。まあ、SSL系を素直に使っておk。

$ openssl version
LibreSSL 2.6.3
$ echo hello world | openssl aes-256-cbc -e > xx
enter aes-256-cbc encryption password:
Verifying - enter aes-256-cbc encryption password:
$ hexdump -C xx
00000000  53 61 6c 74 65 64 5f 5f  34 22 83 31 fe ee 2c 5d  |Salted__4".1..,]|
00000010  9f 09 6b 96 9a 9e 34 90  d8 dc 8b d5 e8 54 bd 98  |..k...4......T..|
$ cat xx | openssl aes-256-cbc -d
enter aes-256-cbc decryption password:
hello world

mongo

余りbinaryばかりやっていても秋るので、先週始めたmongoに戻ります。これってリナの特許じゃ無いよね。OpenBSDにも有るか確認。有れば、天下公認です。

$ grep mongo JAIST
  mongodb-3.2.13p0.tgz                                  42M
  py-mongo-3.4.0.tgz                                   463K
  py3-mongo-3.4.0.tgz                                  457K

操作は、pythonからどうぞって、現代風ですな。

ここに、特徴とオブジェクトのidの付け方の説明が出てた。 MongoDBをpythonから利用する

MongoDB超入門

MongoDBの薄い本

mongoDB on Haskell

へそ曲がりなオイラーは、pythonとかを敬遠して、Haskellです。集積地で探したら、 The mongoDB packageが、一番人気だったので、そのHomeへ行ってみた。 MongoDB driver for Haskell

ちゃんとしたドキュメントが有るので、迷わないだろう。ドッカーで使うってのがお勧めみたいだ。まあいいや、debian上でやってみる。

まずは、Quick exampeleが動くかだな。

deb9:tmp$ stack new db simple
deb9:tmp$ cd db/
deb9:db$ ls
db.cabal  LICENSE  README.md  Setup.hs  src  stack.yaml
deb9:db$ emacs src/Main.hs

ダミーのプロジェクトを作って、Main.hsを例に置き換える。 そして、ビルドで、必要品を教えてもらう。

deb9:db$ stack build
db-0.1.0.0: configure (exe)
Configuring db-0.1.0.0...
db-0.1.0.0: build (exe)
Preprocessing executable 'db' for db-0.1.0.0...
[1 of 1] Compiling Main             ( src/Main.hs, .stack-work/dist/x86_64-linux
-nopie/Cabal-1.24.2.0/build/db/db-tmp/Main.o )

/tmp/db/src/Main.hs:6:1: error:
    Failed to load interface for ‘Database.MongoDB’
    Use -v to see a list of the files searched for.

/tmp/db/src/Main.hs:10:1: error:
    Failed to load interface for ‘Control.Monad.Trans’
    Perhaps you meant
      Control.Monad.Fail (from base-4.9.1.0)
      Control.Monad.Trans.RWS (needs flag -package-key transformers-0.5.2.0)
      Control.Monad.Fix (from base-4.9.1.0)
    Use -v to see a list of the files searched for.

適当にdb.cabalに追加して、再度ビルド

mongoDB-2.3.0: copy/register
db-0.1.0.0: configure (exe)
Configuring db-0.1.0.0...
db-0.1.0.0: build (exe)
Preprocessing executable 'db' for db-0.1.0.0...
[1 of 1] Compiling Main             ( src/Main.hs, .stack-work/dist/x86_64-linux
-nopie/Cabal-1.24.2.0/build/db/db-tmp/Main.o )

/tmp/db/src/Main.hs:10:1: error:
    Failed to load interface for ‘Control.Monad.Trans’
    It is a member of the hidden package ‘mtl-2.2.1’.
    Perhaps you need to add ‘mtl’ to the build-depends in your .cabal file.
    Use -v to see a list of the files searched for.
Completed 57 action(s).

まだエラーなんで、最終的にdb.cabalを下記のようにした。

  build-depends:       base >= 4.7 && < 5
                       , transformers
                       , mongoDB
                       , mtl

これで、ビルドが成功した。mongoDBって、セキュリティがらみで、随分と色々なモジュールを コンパイルしてたぞ。オイラー的には、もっとカジュアルな奴でいいんだけどね。

deb9:db$ stack exec db
All Teams
[ name: "Red Sox", home: [ city: "Boston", state: "MA"], league: "American"]
[ name: "Yankees", home: [ city: "New York", state: "NY"], league: "American"]
[ name: "Mets", home: [ city: "New York", state: "NY"], league: "National"]
[ name: "Phillies", home: [ city: "Philadelphia", state: "PA"], league: "National"]
National League Teams
[ name: "Mets", home: [ city: "New York", state: "NY"], league: "National"]
[ name: "Phillies", home: [ city: "Philadelphia", state: "PA"], league: "National"]
New York Teams
[ name: "Yankees", league: "American"]
[ name: "Mets", league: "National"]
()

赤い靴下ってボストンがホームグラウンドなんだ。オイラーは、そんなよその国のことはおろか、日本のチームもよく知らん。

こういう例をたたき台にして、誰かさんは馬のDBを作るだろう。なんたって最初から、がちがちにテーブル設計する必要無し。

基礎データをJRAから買ってきて、取り合えずのDBを作る。後は、自分のノウハウをDBに追加してく。

昔の会社の同僚は、お舟に凝ってて、自前のDB(もどき)を、EXCELで作ってた事を知ってるぞ。舟で当てて、御殿に住んでるのかなあ?

db shell

上で作ったデータが残っているはずなので、お試しをしてみる。

deb9:tmp$ mongo
MongoDB shell version: 3.2.11
connecting to: test
> show dbs
baseball  0.000GB
local     0.000GB
> use baseball
switched to db baseball
> db.stats()
{
        "db" : "baseball",
        "collections" : 1,
        "objects" : 4,
        "avgObjSize" : 105,
        "dataSize" : 420,
        "storageSize" : 32768,
        "numExtents" : 0,
        "indexes" : 1,
        "indexSize" : 32768,
        "ok" : 1
}
> show collections
team

登録されてるDBを確認。DBを切り替え。状態をチェック。コレクションってのがRDBMSで言う、 テーブルの事らしい。

> db.team.find()
{ "_id" : ObjectId("59f6c9ff6e955226ab000000"), "name" : "Yankees", "home" : { "city" : "New York", "state" : "NY" }, "league" : "American" }
{ "_id" : ObjectId("59f6c9ff6e955226ab000001"), "name" : "Mets", "home" : { "city" : "New York", "state" : "NY" }, "league" : "National" }
{ "_id" : ObjectId("59f6c9ff6e955226ab000002"), "name" : "Phillies", "home" : { "city" : "Philadelphia", "state" : "PA" }, "league" : "National" }
{ "_id" : ObjectId("59f6c9ff6e955226ab000003"), "name" : "Red Sox", "home" : { "city" : "Boston", "state" : "MA" }, "league" : "American" }
> db.team.find( {}, {_id:0} )
{ "name" : "Yankees", "home" : { "city" : "New York", "state" : "NY" }, "league" : "American" }
{ "name" : "Mets", "home" : { "city" : "New York", "state" : "NY" }, "league" : "National" }
{ "name" : "Phillies", "home" : { "city" : "Philadelphia", "state" : "PA" }, "league" : "National" }
{ "name" : "Red Sox", "home" : { "city" : "Boston", "state" : "MA" }, "league" : "American" }

全件確認。idが邪魔なので、除いて表示。0とか1は、false、trueの意味とは、変な所で、JavaScriptの素性をさらけ出すのね。

> db.team.find( {}, {_id:0, home:1} )
{ "home" : { "city" : "New York", "state" : "NY" } }
{ "home" : { "city" : "New York", "state" : "NY" } }
{ "home" : { "city" : "Philadelphia", "state" : "PA" } }
{ "home" : { "city" : "Boston", "state" : "MA" } }
> db.team.find( {}, {_id:0, home:{city:'Boston'}} )
Error: error: {
        "waitedMS" : NumberLong(0),
        "ok" : 0,
        "errmsg" : "Unsupported projection option: home: { city: \"Boston\" }",
        "code" : 2
}

homeフィールドだけ検索。そして絞り込もうとすると、あえなくエラーを喰らった。何か逃げの手がありそうだな。Haskell版では実行出来ていたからね。

> db.team.find( {}, {_id:0, 'home.state':'NY'} )
{ "home" : { "state" : "NY" } }
{ "home" : { "state" : "NY" } }
{ "home" : { "state" : "PA" } }
{ "home" : { "state" : "MA" } }
> db.team.find( {}, {_id:0, {'home.state':'NY'}} )
2017-11-11T17:05:35.403+0900 E QUERY    [thread1] SyntaxError: invalid property id @(shell):1:26

なかなか、思い通りに行かないなあ。

> db.team.find( {'home.city': 'Boston'} )
{ "_id" : ObjectId("59f6c9ff6e955226ab000003"), "name" : "Red Sox", "home" : { "city" : "Boston", "state" : "MA" }, "league" : "American" }
> db.team.find( {'home.city': 'Boston'}, {_id:0, home:1} )
{ "home" : { "city" : "Boston", "state" : "MA" } }

で、ちょっと想像を働かせたら、思い通りになった。

mongodb web console

MongoDB - 闘うITエンジニアの覚え書き

SQL脳に優しいMongoDBクエリー入門

こちらあたりが、mongoを使う上でのまとめになるかな。まあ、shellから、db.help()とか やってもいいんだけど、cookbook的によいかも。

それで、Javascriptがあちこちに顔を出す。大体、dbへの格納もJSONをバイナリーにしたもの だしね。Javascriptなら、ブラウザーとつるんで、悪さをするのは、得意中の得意のはず。 そう思って資料を漁ると、web consoleですってさ。

Debianのデフォでは、Webは危険(あくまで、個人的感想です)って事で、有効になっていない。設定ファイルを下記のように変更すると、ブラウザーからアクセス出来るようになる。

deb9:~$ sudo vi /etc/mongodb.conf
httpinterface=true
rest=true
deb9:~$ firefox http://localhost:28017

ああ、設定変更したら、サーバーを再起動するのよ。そうすれば、ブラウザーからアクセス出来るようになる。

後は、下記の例を参照。

MongoDBとブラウザだけで旅の記録をつけてみるか

説明によると、地理的情報を扱うのも得意だそうなので、行動記録を残すのも簡単だろう。

consoleから、各種の状態が取れる。例えば、buildinfo

{ "version" : "3.2.11",
  "gitVersion" : "009580ad490190ba33d1c6253ebd8d91808923e4",
  "modules" : [],
  "allocator" : "tcmalloc",
  "javascriptEngine" : "mozjs",
  "sysInfo" : "deprecated",
  "versionArray" : [ 
    3, 
    2, 
    11, 
    0 ],
  "openssl" : { "running" : "OpenSSL 1.0.2l  25 May 2017",
    "compiled" : "OpenSSL 1.0.2j  26 Sep 2016" },
  "buildEnvironment" : { "distmod" : "",
    "distarch" : "x86_64",
    "cc" : "cc: cc (Debian 6.2.1-6) 6.2.1 20161212",
    "ccflags" : "-fno-omit-frame-pointer -fPIC -fno-strict-aliasing -ggdb -pthread -Wall -Wsign-compare -Wno-unknown-pragmas -Wno-nonnull-compare -Wno-overflow -Winvalid-pch -Werror -O2 -Wno-unused-local-typedefs -Wno-unused-function -Wno-deprecated-declarations -Wno-unused-const-variable -Wno-unused-but-set-variable -Wno-missing-braces -fno-builtin-memcmp",
    "cxx" : "g++: g++ (Debian 6.2.1-6) 6.2.1 20161212",
    "cxxflags" : "-g -O2 -fdebug-prefix-map=/build/mongodb-NMhPVq/mongodb-3.2.11=. -fstack-protector-strong -Wformat -Werror=format-security -Wnon-virtual-dtor -Woverloaded-virtual -Wno-maybe-uninitialized -std=c++11",
    "linkflags" : "-Wl,-z,relro -fPIC -pthread -Wl,-z,now -rdynamic -fuse-ld=gold -Wl,-z,noexecstack -Wl,--warn-execstack",
    "target_arch" : "x86_64",
    "target_os" : "linux" },
  "bits" : 64,
  "debug" : false,
  "maxBsonObjectSize" : 16777216,
  "storageEngines" : [ 
    "devnull", 
    "ephemeralForTest", 
    "mmapv1", 
    "wiredTiger" ],
  "ok" : 1 }

JSON様々だな。