まぁまずはこの呟きを確認してくれ。
「パスワードを暗号化して保持する」
— える.jar(undeploying) (@ellnore_pad_267) 2019年11月11日
「その後画面に普通に表示する」
「別なユーザがパスワードを編集可能にする」
ウケるを通り越して頭痛くなってくるだろ。
このツイートを見て「ヤベーじゃん」って素直に思える人はこの日記は読む必要がありません。
逆に、「ん?」って思ったけど何が悪いか解らない、みたいな非技術者サイドの人は読んだ方が良いでしょう。
暗号化とハッシュ化の違い
最大の違いは元に戻せる(=復号化可能)かどうか。
- 暗号化は元に戻せる。
- ハッシュ化は元に戻せない。
これが最大の違い。
これを踏まえて、パスワードをはじめとするセンシティブデータ*1のシステム保存に関するセキュリティ対策の姿勢は大きく以下の3つ(+1)に分けられます。
- 平文*2で保存する。(論外)
- 暗号化して保存する。(不十分)
- ハッシュ化して保存する。(及第点)
暗号化のイメージ
有名なシーザー暗号の例。
I am a pen. (私は、ペンです。)
これを全てアルファベット順で2文字ズラしてみます。
K co c rgp. (けー、こー、、、く、るぎゅっぷ???)
これは「アルファベット順で2文字ズラす」というルール(←これを暗号鍵という)を知っていれば、暗号文をもとの文(←これを平文という)に戻す事が出来ます。
シーザー暗号の弱点はシンプル過ぎるという事。
特に英文の場合 I am
というフレーズはほぼ必ず出現するので、この部分がバレると必然的に暗号鍵がバレて、結果すべてのデータがバレます。
フレーズが出現せずとも「シーザー暗号かな?」と思ったら、取り敢えずアルファベット26文字なので1~25個ズラしを全部試してみりゃやっぱりバレます。
つまり、あまりにも原型を維持し過ぎているのがこの暗号方式の弱点です。
シーザー暗号の他に有名なのは武田信玄式暗号など、何れにせよ「一定のルールで変換し、一定のルールで元に戻す」というのが「暗号化・復号化」という技術になります。
勿論これは物凄く原始的で単純な暗号化の例であって、実際に世の中で利用されている暗号化技術はこんなレベルのものではないです。
ハッシュ化のイメージ
ハッシュ化とはつまり こういうこと です。
いや、実際にこれが本質を現しているんですよ。
しかもこの例えの秀逸なのは、後から出て来る「塩」ともマッチするんですよね。
ハッシュ化 は、暗号化の超つえーバージョンです、元には戻せません。
I am a pen. (私は、ペンです。)
これをハッシュ化すると
jmiaoizwo;hfiepwfhjieoa;fjf9042ujr9ifa43j9a3jitwj
みたいな事になります。
(ちなみにこれはキーボードを適当にぶっ叩いただけなので本当はハッシュ化でも何でもありません)
高校数学とかで習った「関数」という概念を思い出してください。
関数 y = f(x)
というと、ある値 x
が定まると y
の値がただひとつ定まる、というものでしたね。
「二次関数」を思い出してくれると解ると思いますが、逆は成り立たないんですよね。
つまり、ある値 y
を定めたとしても、その元になった x
がただひとつ定まるとは限らない。
これが「関数 y = f(x)
の関係」で、ざっくりいうとハッシュ化というのはコレです。
上記の例で行くと、
y = f(x)
jmiaoizwo;hfiepwfhjieoa;fjf9042ujr9ifa43j9a3jitwj = f( I am a pen. )
ということです。
I am a pen.
から jmiaoizwo;hfiepwfhjieoa;fjf9042ujr9ifa43j9a3jitwj
を得る事は出来るが、逆は出来ないということです。
ポテトからハッシュドポテトを作ることは出来るが、一度ハッシュドポテトにした物から元の生ジャガイモに戻すことは出来ない。
早い話が「非可逆変化」であるというのがハッシュ化の最大の特徴です、元には戻せない。
なお、ハッシュ化したデータの事をハッシュ値と呼び、ハッシュ値を求める手段をハッシュ関数などと呼びます。
何故、パスワード暗号化だけでは不十分なのか。
さて、それではここからが本題です。
- 暗号化とハッシュ化の違い
- 何故、パスワード暗号化だけでは不十分なのか。
- パスワードは暗号化であるべきか?ハッシュ化であるべきか?
- 美味しいハッシュドポテトに学ぶパスワードの戦略
- 【余談】或いはこの記事を書いた経緯について。
平文保存の問題点
流石にこんなトンデモプロジェクトは今日び無い、と信じたい所ですが・・・。
論外。
当たり前だるるぉ!!
情報流出とかした時にノーガードで良い訳無いだろ、いい加減にしろ!!
暗号化の問題点
平文が論外なのは良いとして、暗号化は何故ダメなのか?
鍵、それがバレたら終わり。
IT, 'それ' が見えたら終わり。
暗号化の問題点は「元に戻せてしまうこと」です。
暗号化ってのは復号化とセットの概念であって、いわゆる可逆変化なんですね。
なので、本質的にこれは元に戻す必要がある場面でのみ使用するべきものです。
暗号化通信ってのはそうですよね。
ショッピングサイトサイトでカートに入れた商品の情報だったり、選択した支払い方法や決済情報だったり、通信を行う2者間(ショッピングサイトと客)では元のデータをやり取りしたいが、通信を盗み見ている第三者(すーぱーはっかー)からは情報を守りたい、こういう場合は「暗号化・復号化」である必要があります。
さて、じゃあログイン認証時のパスワードは「元に戻す必要がある場面」と言えるでしょうか?
答えはノーです。(詳細は後述)
悪意ある管理者
情報漏洩以外にも、悪意ある管理者がデータベースを直接見て、パスワードや重要な個人情報を抜いて行くと言うルートが存在します。
データが暗号化されていたとしても、暗号化のルール(暗号鍵)を知っている人間からすると無力無力ゥな訳です。
暗号鍵を持つものは全知全能の神と思って下さい。
暗号鍵を持つものに逆らってはいけません。
例えばAmazonのデータベースの暗号鍵を持っている人間は、あなたがどんなAVを購入したか、Kindleでどんな本を読んでいるか、AmazonPrimeでどんな映画を観たのかなど、すべての情報が赤裸々に筒抜けなのです、多分。
よってAmazonは神、証明終了。
これは別に管理者に限らないんですよね。
情報管理が杜撰な会社だったり、リテラシーの低い会社だったりすると、ソースコードの中に思いっ切りこの「暗号鍵」となる情報がベタ書きになってたりします。
本来は良くないんだけど、開発に十分な予算が割り当てられておらず多忙だったり、或いはこれらに関する責任者の意識・知識が低いと、きちんとした「鍵管理」がされず、非常に良くない状態で「暗号鍵」が人の目に晒されている危険があるのが実情です。
こうなったらおしまいです、管理者であらずとも、そのソースコード管理システム(gitやsvnなど)にアクセスできる人間なら誰もが暗号鍵を入手できてしまいます。
実際に、データベースへの接続情報や、その時に使用する暗号化キーなどが、仮の値になっておらず正規の値で記載されている例は少なくないです。
(とは言っても、別な手段で接続可能なアクセスポイントを制限していたり、特定の踏み台サーバからじゃないと繋がらなかったり、その他の防御用法でガードされているのが一般的ですが)
その鍵を入手出来るのは、管理者だけに限らずその会社の社員はもちろん、一時的に外注で入っているどっかの誰かや、或いはオフショアでやってきた外国人かも知れません。
この先に待っている最後の砦は、職業倫理だとか良心だとか、そういうフワッとしたものだけです。
実際に、Amazonさんでこんな事件があったのは記憶に新しいですよね。
米金融大手のCapital Oneから、クレジットカードの発行を申請した個人や企業など1億600万人あまりの個人情報が流出した事件。容疑者として米連邦捜査局(FBI)に逮捕されたソフトウェアエンジニアの女はAmazonの元従業員だったと伝えられている。
ここまで来ると、ちょっと話が逸れてきたけど、要するに脅威は外部からに限った話ではないという事ね。
この一点だけ、押さえといて下さい。
ハッシュ化の問題点
暗号化の場合、暗号鍵がバレてしまえば元のデータを簡単に入手出来てしまう、というのが暗号化の問題点でした。
じゃあハッシュ化に問題はないのか?
と言うとハッシュ化にも弱点はあります。
暗号化もハッシュ化も、それぞれ「一定のルール」を定めて暗号化・ハッシュ化を行っているので関数性があります。
(この「一定のルール」は、通常はあるシステムの中で一定です。場合によってはルールを複数設けて使い分けするみたいな事も手段としては考えられますが。)
ここで言う関数性ってのは、それこそ前述の高校数学的な意味での関数性で、「ある入力値に対して、常に一定の出力値を得る」、つまり結果が常に一定ということ。
結果が常に一定ということは、たまたま同じパスワードを設定した人がいたら、その人のハッシュ値は同じ値になるということです、当然だよなぁ?
パスワードに使用可能なのが大小アルファベットと数字と一部記号だったとして、1文字からn文字まで全組み合わせを徹底的に試してやり、ハッシュ値が一致するものが見付かったら、恐らくそれが元のパスワードでしょうと言う攻撃が成立します。
この攻撃方法を、総当たり攻撃、ブルートフォースアタック と言います。
これはコンピュータの計算能力に比例して攻撃力が上がるので、現時点では時間が掛かり過ぎてワリに合わないとしても、将来的に量子コンピュータが実用レベルになった場合は簡単に突破可能になったりと、シンプルな力技であるが故にスペックによるゴリ押しが強くなる攻撃方法です。
念能力で言えばガチガチの強化系です、ウボォーギンみたいなものです。
ブルートフォースアタックの話をしたのでついでにこちらも。
「よく使われるパスワードリスト」というものを聞いた事がある人もいるでしょう。
「パスワードを忘れないようにという意図から、よく使われるパスワード」というものが存在する訳です。
取り敢えずダメ元でこのよく使われるパスワードを試しに打ち込んでみる、と言う攻撃が成立します。
これを 辞書式攻撃(辞書攻撃) と言います。
この攻撃方法は結構優秀で、以下のようなメリットがあります。
- 攻撃のコストが低い(ブルートフォースアタックに比べて、試す択が少ない)
- この手のパスワードを使っている人間は、同一パスワードの使い回しや、類似する頻出パスワードを使っている可能性が高く、攻撃成功時のメリットがでかい。
ブルートフォースアタックを試しつつ辞書式攻撃も併用するなど、複数の攻撃方法のハイブリッド方式でパスワード突破を試みる、といった事も考えられます。
他にも レインボーテーブル とか、セキュリティまわりはほんと色々ある。
勿論これはハッシュ化に固有の弱点とかではなく、暗号鍵を持っていない状態でのパスワード暗号化に対する攻撃としても成立します。
当然だね。
とかくセキュリティまわりは難しいし、この辺は攻撃者とそれに対する防御者による、頭のいい連中の繰り広げた頭脳戦によるイタチゴッコで日々進歩してるのだから恐ろしい、、、。
今ある常識的手段も、数週間後には非常識で時代遅れなものになってるかも知れない。
パスワードは暗号化であるべきか?ハッシュ化であるべきか?
閑話休題。
暗号化とハッシュ化については、ざっくりですが以上で説明した通り。
では、改めて「パスワードは暗号化であるべきか?ハッシュ化であるべきか?」を考えます。
結論、暗号化である必要はない。
争点は「暗号化したパスワードを元に戻す必要があるか?」という点になります。
パスワードを仮に暗号化した状態で保存したと仮定して、ユーザがログインを試みた場合のパスワード検証手段は以下の2通り考えられます。
- パターン1
- 入力されたパスワードを暗号化する
- 保存されている暗号化パスワードと突合する
- 一致すれば認証OK
- パターン2
- 保存されている暗号化パスワードを取り出し複合化する
- 入力されたパスワードと突合する
- 一致すれば認証OK
※実際には、ハッシュ値には「衝突」と言う可能性*3がありますが、パスワードハッシュの場合はこれは「許容」されている*4事が殆どですね。
取り敢えずどちらでもパスワードの確認は可能ですね。
となると、パターン2で復号化する意味とは?
ってなるので、別にパスワードって暗号化である必要ないよね?
パスワードであっても、パスワードを暗号化した値であっても、兎に角「アカウント登録時に何を入力したのかを知っていないと当てられない」事に変わりはないんだから、認証に関して言うと復号化出来る必要なくね?
その復号化した値をどこか別な所で使う?
「パスワードを忘れた時に再通知するために、もとに戻せないと困るだろう」
という説があり、実際にそういう思想で「パスワードを暗号化=複合可能な状態で保持する」としたシステムは実際に存在するけど、今はこの考え方は古いとされています。
今現在の常識としては、パスワードを忘れた場合は「設定したパスワードを通知する」のではなく、「パスワードを再設定する仕組みを用意する」という方向にシフトしています。
昨今、常識的に作られたシステムに於いては、ログイン画面に パスワードを忘れた場合
みたいなリンクがあって、リンクを踏むと「アカウント登録時に設定したメールアドレス」に対してパスワードのリセット通知メールが飛んで、そのメールにパスワードのリセットと再設定を行うためのURLが記載されており、このURLは一定時間で使えなくなるよ、、、みたいなパターンになってますよね。
パスワードを忘れた場合は「再通知」ではなく「再設定」にすると言う思想に於いては、パスワードは復元可能である必要はありません。
よって、この場合はパスワードは「暗号化」ではなく「ハッシュ化」して持つべき(何かあった時に、元のパスワードがバレ難いため)となります。
美味しいハッシュドポテトに学ぶパスワードの戦略
取り敢えず、以上から本プロジェクト(?)では「パスワードは暗号化じゃなくハッシュ化する」という方針になったと仮定します。
その場合、前述した通りハッシュ化にも弱点があり、それに対してどのような対策を取るのが一般的なのかを知っておくべきです。
現状「ストレッチング」と「パスワードソルト」という考え方が一般的かと思います。
ストレッチング、芋は良く潰そう。
ハッシュ化のハッシュと、ハッシュドポテトのハッシュって、読みが同じってだけじゃなくて語源がそもそも同じなんすよ、同じ意味*5なの。
ハッシュドポテトは、雑に潰すよりシッカリ細かく潰した方が美味しいですよね?
そんなの好みによるから一概に言えないだルルォ!!??
話が進まないので、細かく潰した方が美味しいということにしておいて下さい。
ハッシュドポテトは繰り返しハッシュした方が良い。
パスワードのハッシュ化も、繰り返しハッシュしたほうが良い。
パスワードのハッシュ化も、一回やるより何回もやった方がデータが複雑になるので、攻撃者がハッシュ値の解析をしようとする時に必要な計算量を増やすことが出来る、つまり時間稼ぎになる訳ですね。
(勿論、システムが普通にハッシュ計算する時にも計算コストが掛かるので、無制限にやればやるほど良いかって言うと性能を犠牲にするんですけど)
そもそも、パスワードってデータサイズとして十分な大きさが無いんですよ。
せいぜい8文字以上、長くても10文字20文字程度でしょ、文字種を多少混在させた所でさほど計算オーダを大きく増やす効果は無い(やらないよりマシ、っていう程度)ですし、根本的にハッシュと相性良くないんですよね。
あ、そうそう、レガシーなパスワードポリシーよりもパスフレーズ(複数の単語の組み合わせ)の方が、覚え易くてしかもデータ量を増す効果は大なので、パスフレーズ使った方が良いってのが昨今の流行りですよ。
たまに「パスワードは8文字以上32文字以下」みたいに上限切ってるシステムもありますよね。
下手をすると32文字どころか16文字とか12文字とかで打ち止めとか言う、ドチャクソに天井が低いクレイジーなシステムもありますが、ああいうのは猛省して下さい。
そもそも下限はいいとして、なんで上限があるんだよ、、、。
パスワードに上限なんかいらねぇんだよ、当然だるるぉ??
21億文字まで使えるようにしるるぉ。(極端)
ここでちょっと余談。
先にもちらっと「ハッシュ値の衝突」ということを述べたと思いますが、そもそもなんでそんな事が起きるの?という話。
さっきハッシュ化について説明した時、意図的に外した特徴があります。
- 固定長のデータが生成される
一般的に、ハッシュ関数というのは「入力データサイズはまちまちだが、出て来るハッシュ値は一定のデータサイズに揃えられる」という特徴があります。
一定のデータサイズに揃えられる、つまりハッシュ値は有限であると言う事です。
面白いのは、入力データがたとえ空文字列 ""
であっても、シッカリとハッシュデータが生まれると言う事です。
入力がからっぽだったからといって、出力もからっぽではない、ってのはちょっと面白いですよね? ね?
このへんはWikipedia様の ハッシュ値の例 - Wikipedia に詳しいので、その辺を見てくれれば「固定長の有限なデータが生成される」という話にイメージが付くと思います。
とにかく、パスワードのハッシュ化では、一回じゃなく複数回やる。
ハッシュ関数を複数回通すことで、複雑なデータ変換を何度も行う。
これにより、うまい具合にパスワードのハッシュ値を盗んだ攻撃者が居たとしても、そのハッシュ値から元のパスワードを得るには「ハッシュ値のもとになったハッシュ値のもとになったハッシュ値のもとになったハッシュ値の更に元になった、、、どこまでやらすねん!!」となります。
ハッシュ値から元の値を一回特定するだけでも大変なのに、それを何度もやらせるのですから「なんか大変そうだな」ということは何となく分かると思います。
これをストレッチングと言います。
多くのハッシュ化ライブラリ・ハッシュ関数では多重化度を指定出来るようになってると思うので、これで指定します。
ソルト、味付けに塩を忘れずに。
さて、ストレッチングによって計算量を増やすと言う防御を試みました。
が、このままでは前述した通り「関数性」がそのまま取り残されています。
同じ入力に対しては同じ値が出て来る問題ですね。
ではこれに対する防御は何かというと、答えは簡単。
パスワードに勝手に別なデータをくっつけてハッシュ化しちまうことです。
ユーザが入力したパスワード自体は同じであっても、システムがそれに別なデータをくっつけてハッシュ化する事によって、ハッシュデータを異なるものにするということです。
この時くっつける「別なデータ」のことを「ソルト=塩」と言います。
ハッシュドポテトを作る時、味付けに塩をまぶす訳です。
まぶす塩が異なれば別な味になるように、ユーザに応じて塩を使い分けることで、仮に同じパスワードだったとしても異なるハッシュデータを生成することで、攻撃者を混乱させることが出来ます。
味付けをしていないハッシュドポテトは美味しくない、と覚えておいて下さい。
まとめ
美味しいハッシュドポテトを作るには
Streching
良く潰すことSalt
味付けの塩を忘れないこと
お腹空いてきましたね、何の話でしたっけ。
あ、そうそう。
「パスワードを暗号化して保持する」
「その後画面に普通に表示する」
「別なユーザがパスワードを編集可能にする」
ウケるを通り越して頭痛くなってくるだろ。
ここまで真面目に読んだ方は、この「仕様」がヤベーって事が良くお解りになったかと思います。
【余談】或いはこの記事を書いた経緯について。
先日書いたこの日記のとおりなんですが。
案件、クビになりました。
ぼく個人としては、システムユーザの事を第一に考えて、技術者としての職業倫理に従って最善を尽くそうと頑張ったつもりなんですけどね、どうやら利害が合わなかったようです。
まぁ、そういうこともあるよね。
かわいそうだな、って思う人が居たら、巻末の「Amazon欲しい物リスト」から何か送って下さいw
若しくはこちらの有料noteをご購入のうえ、カレーを作ってみて美味しかったらサポートもよろしくおねがいします。
*1:取り扱い注意みたいなもん。
*2:何も手を加えずそのまま
*3:ハッシュ値の衝突。ハッシュ関数・ハッシュアルゴリズムによって理論上発生する問題。異なる別な入力から、たまたまハッシュ値が一致するケースが無いわけではない。
*4:衝突の可能性―つまり異なるパスワードが同一のパスワードハッシュ値を持つ可能性―が十分に小さく、現実的には無視できるレベルである。ハッシュ値を偶然に衝突させられる可能性は考慮するレベルではなく、ハッシュ値を当てられるということは恐らく元々の入力値は一致していた、とみなしても「実用上は問題ない」ということ。
*5:細かく潰すとか、ぐちゃぐちゃにするとか、刻んだ肉料理って意味なんだってさ。