パスワードをソルトつきハッシュ化してDBに保存するのがベストプラクティス…とは限らないという話
フレームワークの責務とセキュリティ - MugeSoの日記についての感想文です。
世の中にはたくさんの通信プロトコルが存在し、中には、特定の条件でパスワードを含む文字列をハッシュ化した値を検証しなければならないものも含まれています。
例えば、HTTP Digest認証の場合は、MD5("realm:user:password")を保存しておく必要がありますし、APOPの場合は生のパスワードを、CRAM-MD5の場合はMD5("password")を保存しておく必要があったはず。
で、こういった様々なプロトコルに対応可能な認証データベースを準備しようとすると、パスワードを復号可能な方式で保存しておく必要があります*1。
ただ、パスワードを復号可能な方式で保存するとか、開発者あるいは管理者としてやりたくないというのはもちろんそうなので。で、長期的には世の中どこへ向かってるかというと:
- 選択肢a) 三者間認証を使う(OAuthとかそういうやつ)
- 選択肢b) 経路をTLS(SSL)暗号化し、パスワードは平文のまま認証サブシステムまで到達する。認証サブシステムは独自の最適と考える方式でパスワードを保存する*2
って感じなのかなと思います。
こういう経緯があるので、パスワードを、独自のやり方でハッシュ化するのを強制する、というのをフレームワークレベルでやるのは良くないよ*3というのが僕の考えです(注: MugeSoさんは、そのような強制をすべきと主張しているのではないとのこと*4)。
PS. もちろん、アプリケーション要件的に、生パスワードがアプリケーションまでわたってくるという前提をおけるのであれば、ソルトつきハッシュ化を「アプリケーションデベロッパー」が選択すべきだと思いますし、それを推奨するようなフレームワークであれば良いのかなと思います。
PS2. ソルトつきハッシュ以外の方法でパスワードを保存するなんて怖い、っていう人は「Kazuho's Weblog: パスワードが漏洩しないウェブアプリの作り方 〜 ソルトつきハッシュで満足する前に考えるべきこと」をあわせてお読みください
*1:今後対応する必要がありうる全ての方式のプロトコルに対応した形で、あらかじめ(一方向)暗号化したパスワードデータベースを用意しない限りにおいて、です。また、共通鍵暗号化を用いて保存する場合でも、ECBモードではなくCBCモードを使うべきでしょう。参照: Adobeサイトから漏えいした暗号化パスワードはなぜ解読されたか | 徳丸浩の日記
*2:パスワードが常に平文のまま認証サブシステムにまで到達する世の中になれば、ソルト付きハッシュで保存できるようになる
*3:それは結局、上で述べた「特定の条件でパスワードを含む文字列をハッシュ化した値を検証しなければならない」というアンチパターンに陥る話なので
*4:参照: mugeso on Twitter: "@kazuho 反応ありがとうございます。 件のエントリではBasic認証の実装をFWに取り込む場合の話ですので、他の認証方法に関して制限を設けるということではありません。"
Inline JSONPの長所について (続: サーバサイドからクライアントサイドのJavaScriptを呼び出す際のベストプラクティス)
サーバサイドからクライアントサイドのJavaScriptを呼び出す際のベストプラクティス - kazuhoのメモ置き場 の件、id:tokuhirom がさくっと HTML::CallJS というモジュールを書いて公開してくれた (Shipped HTML::CallJS - tokuhirom's blog 参照) ので、どういう Inline JSONP を使うとどういう形で書けるか、ぱっと例をあげたいと思います。
まず、サーバサイドのプログラム。
以下は perl で、tokuhirom の HTML::CallJS と Text::MicroTemplate を使っている例。
JavaScript の呼出を保存する配列を用意し、そこに呼出をどんどん追加していっています。一定条件下でのみ特定のクライアントサイド処理を呼び出したり、配列の各要素について呼び出したりすることも簡単にできます。
# Inline JSONPで呼び出すコードを入れるためのリスト my @inlineJsonP; push @inlineJsonP, call_js("foo", { a => 1}); # まず foo を呼ぶ if (...) { push @inlineJsonP, call_js("bar", { ... }); # 一定条件が成立したらbarを実行 } for my $e (@list) { push @inlineJsonP, call_js("baz", $e); # 配列の各要素を引数としてbazを実行 } # レンダリングエンジンを実行 my $html = $renderer->({ ... # other args to template engine inlineJsonP => [ map { encoded_string($_) } @inlineJsonP ], })
次にテンプレートのコード。
HEAD要素の終了部に Inline JSONP の SCRIPT 要素を展開するコードを入れる形がいいでしょう。Text::MicroTemplate の場合は、以下のような感じになるかと思います。HEAD内でいいので、サービス全体で共通のヘッダ用テンプレートに仕込む形になります。
<html> <head> ... ? for my $e (@{$_[0]->{inlineJsonP}}) { <?= $e ?> ? } </head> <body> ...
最後にJavaScriptのコード。
エントリポイントを定義するだけです。
function foo(arg) { ... } function bar(arg) { ... } function baz(arg) { ... }
では、この手法のメリットが DOM ベースでの情報受け渡しに比べて何が便利なのか、あらためて確認してみましょう。
- 呼び出す必要のあるクライアントサイドのコードが増えたり、引数が変わったりしてもテンプレートを変更する必要がない
- クライアントサイドのコードを呼出しタイミングや条件をサーバサイドで制御できる
- クライアントサイドの処理が関数として抽象化されているのでテストしやすい
- HTML内に人間が読んで意味不明な情報が混入しない
別の言い方をすると、DOMベースでの情報受け渡しは、グローバル変数を使っているのと同じなのです*1。関数呼出を使った方がカプセル化できて見通しがよくなるのは当然ではないでしょうか。
PS. 「Inline JSONP」という名前は はてなブックマーク - rryuのブックマーク / 2013年11月6日 からいただきました。rryu++
*1:というより、RPCを実行する際にファイルに呼出し情報を書いているようなもの、というのが正しい
サーバサイドからクライアントサイドのJavaScriptを呼び出す際のベストプラクティス
JavaScript文字列のエスケープ – yohgaki's blog に対して、
最近だと id="hoge" data-foo="<% bar %>" しておいて $("#hoge").data('foo') でとりだすのが主流かと思います。
はてなブックマーク - JavaScript文字列のエスケープ – yohgaki's blog
のように、
- そもそもJavaScriptコードを動的生成すべきでない
- JavaScriptコードに渡す変数はHTMLノードを経由すべきだ
というような反論がついています。
が、はたしてそうでしょうか。
僕には、元の記事の手法も、HTMLノードを経由する手法もあまり好ましくない*1ように思えます。
そもそも、HTML生成時にXSS脆弱性が発生しがちなのは、
- タグや静的な文字列と動的に生成される文字列が混在し
- 埋め込まれる多数の文字列を正しくエスケープしなければならない
という事情ゆえです。これは、HTMLが人間が閲覧しやすいように生成されるものである以上、仕方がない制限です。
ですが、サーバサイドのプログラムからクライアントサイドのJavaScriptに情報を渡す場合には、この制限を気にする必要はありません。ですから、「データの受け渡し箇所を局所化する」ことで脆弱性の発生する可能性を低めるべきだと思います。
具体的には、どうすべきか。
ひとことでいうと、JSONPと同形式の呼出をサーバサイドで生成しSCRIPTタグとして埋め込む、という手法を採るべきだと思います。
実例をあげると、以下のような感じでしょうか。
<script> function doit(argsAsJson) { ... } </script> <script> doIt({ a: 123, b: "hello" }); // このステートメントを動的生成する </script>
動的生成するコードはJSONPの場合とほぼ同じ。以下の手順で生成できます。
この方式のメリットは以下のとおりになります。
- HTML文書内に、意味不明な情報が混ざらない
- クライアントサイドのプログラムとデータが明確に分離されるので、テストがしやすい
デメリットとしては、エンコード手法が HTML の場合と異なるという点がありますが、先に述べたように JavaScript へデータを渡す場合は処理を局所化できるので、上記のメリットと比べると些細なことかと思います。
注: 全面的に書き換えました。元は https://gist.github.com/kazuho/7329584
*1:JavaScriptに渡すべき情報が増えるたびにHTMLテンプレートの修正が必要になるとか
ディレクトリの変更を監視して、任意のコマンドを再起動する話
plackup -R とか grunt-contrib-watch とか、ウェブアプリケーションの処理系とかビルドツールには、割とこの手のものが組み込まれているんだけど、Windows を無視できるなら、*1外部ツールを使えばいいわけで。
具体的には App-watcher-0.11 - watch the file updates - metacpan.org をインストールして
% watcher --dir src -- cmd args...
みたいな感じで起動すれば、src ディレクトリに変更があると cmd を SIGTERM で終了して再起動してくれるから捗ると #soozy で聞きました。
参考文献:
mmapのほうがreadより速いという迷信について
@ITに以下のような記事が出て、
今回からしばらくの間は、まったく逆の例、つまり使うとプログラムの処理性能が上がるというシステムコールを紹介していく。システムコールを呼ぶ回数は少ない方が処理性能は高くなるという原則は変わらないが、呼び出しておくと処理性能が向上するシステムコールというものが存在するのだ。こうしたシステムコールを使わないでいることは、とてももったいない。
今回紹介するシステムコールは「mmap(2)」だ。ここでは詳しく仕組みを解説しないが、mmap(2)は、プログラムの処理性能に必ず良い影響を与える。
やはりあった? 高速化に効くシステムコール (1/2):知ってトクするシステムコール(3) - @IT
それを真に受けたのか、「Go言語でmmapシステムコールを使ったファイル読み込みの高速化検討とC言語のコンパイラの話 - ryochack.blog」のようなブログエントリも上がっている。
が、「mmapだと必ず速くなる」なんて迷信ですから!!!
これらの記事で紹介されているベンチマークで read が mmap よりも遅く見えるのは、非常に大きなバッファを確保しているから。正しいコードを書けば、シーケンシャルアクセスを行うケースにおいて read(2) が mmap(2) より大幅に遅いということは、まず起こらない。むしろ、read(2) のほうが mmap(2) よりも速くなるというケースも実際には多い。
たとえば、上記のうち後者の記事の read を使うベンチマークを適切なバッファサイズを使うよう変更すると、僕の手元では read(2) を使う版(ソースコード)のほうが高速になる (MacBook Pro / Mountain Lion で計測、テストデータは32MB)。
アクセス方式 | 処理時間 | |
---|---|---|
read | 0.028 | |
mmap | 0.035 |
Linux でも mmap(2) がシーケンシャルアクセスの場合に性能が出ないという問題が知られていたという経緯があって、ガチャピンで知られる kosaki さんが 2.6.34 で入ったパッチを紹介する記事(革命の日々! 2.6.34のused once ページに対する改善をcopybenchで検証してみた)を過去に書いたりしている(が、この記事の時点でも、まだread/writeを使った方が速い)。
まあそういうものなので、迷信です。
ついでにいうと、mmap(2) でdisk I/Oページフォルトが起こるとスレッドが固まるので、その点からも、非同期プログラム*1では mmap を避けて非同期 I/O システムコールを使うべきです。
かっとなって書いたが後悔はしていない。
*1:Goって非同期プログラミングするための言語ですよね
30秒でわかるオープンソースライセンスまとめ
「よくわかるFOSSライセンスまとめなんてないよねー」と煽られたので3分で書く。
オープンソースライセンスは、以下の3種類に大別される。
代表的なライセンス | 改変部分のソースコードの開示が必要 | リンクして使う、他のソフトウェアのソースコード開示が必要 |
---|---|---|
GPL (コピーレフト型) | ○ | ○ |
LGPL /MPL (準コピーレフト型) | ○ | × |
BSDL / MITL (非コピーレフト型) | × | × |
自作のソフトウェアをオープンソースで公開する場合、
とするのが無難。非コピーレフト型はMITLのほうがBSDLよりも明確だと言われることが多い(そしてどちらを選んでも問題ない)。
※表の出典は OSS ライセンスの比較および利用動向ならびに係争に関する調査
より詳しく知りたい方へ:
ライセンスの解釈については、専門家による解説を参照することをおすすめします*1。
ネット上にある参考文献として僕が参考にしているのは、上記調査報告書の他、OSI*2の顧問弁護士だったRosen氏による下記文献です。日本語訳もネットで公開されててすばらしい。
MySQL用にランキング専用ストレージエンジンを作る話
前提:
- ゲームに限らずランキング機能が必要になるケースは多い
- つまり需要はある
- だが、MySQLで高速なランキング表示は難しい
- 具体的に言うと、以下の要件を満たすのが不可能
- 1行の更新コストが要素数Nに対して O(log N) 以下
- 任意のランキング位置周辺のSELECTコストが O(log N) 以下
- 具体的に言うと、以下の要件を満たすのが不可能
- ならば、専用のストレージエンジンを作ればいいのではないか
いつやるか? 今でしょ! 以下理由
- MySQL 5.5以降?だとストレージエンジンをまたぐトランザクションがまともになってるはず*1
- ランキング専用でいいから、テーブル構造とか固定でいい(つまり実装が簡単!)
- ランキング専用だから、テーブル・ロックで十分(つまり実装が簡単!)
- 更新すると順位がずれる(つまりテーブルの大部分に影響がある)ので行ロック実装するメリットが小さい*2
- ランキング専用でいいから、全データをメモリにもっても問題ない(つまり実装が簡単!)
まあ僕は、今のところやるつもりないけど。