データベースの暗号化、もしくはパスワードの保存方法のまとめ

追記: (2021-06-04)

いまだにこの記事へのアクセスが多いので、2021年現在におけるパスワードの取り扱いに関するベストプラクティス記事を紹介します。

cloud.google.com

(追記ここまで)

PSNの障害で盛り上がっていますが、クレジットカード番号は暗号化していたがパスワード含む個人情報は暗号化していなかったという点が注目されているようです。

というわけで、パスワードの保存方法についてまとめてみます。

前提となる暗号の話

いわゆる暗号化と呼ばれている技術には、以下の2種類があります。

1. 復号のための鍵があって、暗号化する前の「平文」に戻すことができる「暗号化」

ざっくり言えば、この2通りの操作ができます。

  • 平文と鍵1 ―(暗号化)→ 暗号文
  • 暗号文と鍵2 ―(復号)→ 平文

平文が暗号化される前のデータ、鍵と書かれているのが2048ビットだったりする暗号化の方法によって異なる長さのデータです。

さらに細かく言えば、暗号化のやり方は共通鍵暗号と公開鍵暗号という2種類があります。
共通鍵暗号は、上記の鍵1と鍵2が同じデータで良いもの、公開鍵暗号は、鍵1と鍵2をセットで作るだけの別のデータとなります。

2.鍵が無く、暗号化する前の「平文」に戻すことが出来ない「ハッシュ関数」

こちらは、一つのことだけが出来ます。

  • 平文 ―(ハッシュ関数)→ ハッシュ

ざっくり言えば、最終的に「平文の中身が必要かどうか」でどちらを使うかを決めます。例えば、パスワードであれば「入力されたパスワードが正しいかどうかは確認したいけど、パスワードがどんな文字化はどうでもよい」ので、ハッシュ関数を使うことが一般的です。
逆に、「決裁のためにクレジットカード会社に連絡する必要がある」「個人への連絡のため使う必要がある」クレジットカード番号や個人情報は、入力された平文そのものを残す必要があるので、平文に戻せないハッシュ関数ではなく、鍵を使った暗号化である必要があるわけです。

データベース上のデータの暗号化について

今回、クレジットカードについては暗号化されていたようですが、ここで重要となるのはその鍵の取扱い方です。当然ながら、クレジットカード番号は使うときには復号しなければいけないため、どこかにその復号するための鍵を保存しています。ここで重要となるのが、そのデータを共通鍵暗号と公開鍵暗号のどちらで暗号化していたかどうかです。

共通鍵暗号を利用していた場合

この場合、復号するための鍵=暗号化するための鍵ですので、個人情報を入力するシステムがデータベースに保存するときにその鍵を利用する必要があります。今回、PSNのどの部分が乗っ取られたのかによっては、いわゆるユーザ向けのフロント部分のシステムが漏れていたとすれば、この鍵も含めて漏洩してしまった可能性があり、当然ながらデータベースに暗号化されていた暗号文も全て複合されてしまいます。

公開鍵暗号を利用していた場合

一方、公開鍵暗号であれば暗号化用の鍵と復号用の鍵は別ですので、入力システムに含まれる鍵が漏洩しても、データベース上のデータが複合されることはありません。
ただし、この場合は入力システムはデータベースの内容を知ることが出来ないため、例えば個人情報の変更フォームで、今の入力されている内容を表示することが出来ません。この制限は、クレジットカード番号などであれば良いかもしれませんが、住所等ではなかなか厳しいと思います。

データベースへのパスワードの保存方法

パスワードをどうデータベースに保存するかは、いくつかのやり方があります。
なお、実際にデータベースに保存する段階で暗号化することも出来ますが、その内容を複合する必要があるので、フロント側で共通鍵暗号で元の内容に戻す必要があるので考えないことにします。

平文のまま保存

今回PSNで問題になっている部分です。(2011-05-02追記) パスワードについてはハッシュ化されていたという報道がありました。Saltの有無については不明です。
実はこの方法をとっているシステムは多く、例えばメールサーバへの接続に使われるAPOPや、SMTPの認証(SMTP-AUTH)でCRAM-MD5などを利用しているシステムでは、必ずパスワードを平文で保存しています。これは、その計算アルゴリズムの都合で、メールサーバ側が元の平文のパスワードを必要とするからで、通信路での暗号化のために、保存部分の暗号化を諦めたと言えます。

ハッシュ関数を通したハッシュを保存

平文のまま保存することに抵抗があるということで、ハッシュをデータベースに保存するというのが次の考え方です。これであれば、データベースの内容が漏洩した場合にも取得できるのはハッシュだけのため、ただちにパスワードが漏洩する、ということにはつながらないと言われていました。
ですが、昨今の計算機速度の向上や、IaaSの普及により、Rainbow tableと呼ばれる「あらかじめ計算されたハッシュ値から平文の対照表」が作成されてしまっています。相当に強力なパスワードを使っていない限り、元のパスワードがやはり取得されてしまいます。

さらに、ユーザにパスワードを入力させてサーバに送る段階で二種類の方法があります。

1. SSLなどで暗号化した通信で、生のパスワードをサーバに送信
こちらが一般的ですが、あくまで生のパスワードを送信するため、オレオレ証明書などの不適切なやり方をしていた場合には、生のパスワードが漏洩してしまいます。

2 暗号化せずハッシュをサーバに転送
昔のMicrosoftのフォルダ共有がこの方法を使っていました。当然ながら、通信路にハッシュがそのまま流れてしまっていますので、そのそもそのハッシュをそのまま再利用されてしまいますし、昨今であればRainbow tableから元のパスワードも知られてしまいます。そのため、いち早くより安全な方法に変更され、今ではこの方式は意図して有効にしない限りデフォルトでは利用できなくなっています。

Saltと呼ばれるパスワード毎の乱数を加えたハッシュを保存

パスワードのみをサーバに送る方法では、最も安全な方法となります。
これは、パスワードをハッシュ関数に掛ける前に、パスワードごとに乱数で生成したSaltと呼ばれるデータをくっつけます。そして、くっつけた内容に対してまとめてハッシュ関数を利用し、データベースにはSaltとハッシュの二つのデータを保存します。
ユーザが入力したパスワードと、データベースに保存しておいたSaltを元に、もう一度ハッシュを計算することで、データベース上に保存されたハッシュと比較してパスワードが正しいかどうかを確認します。

最近のLinuxサーバのパスワード保存(/etc/shadow)がこの形式です。「:」で区切られた2つめのフィールドに$区切りで書かれています。仮にパスワード「hoge」で生成したパスワードフィールドが以下のようになりました。

$1$NH53WILE$xB70B5omM8f5gYifmWIsj1
  • 先頭の「$1$」が、これはSalt付きハッシュSalt付きのMD5ハッシュであるのを示しています。
  • 次の$の手前までの「NH53WILE」の8文字がSaltです。
  • 最後の「xB70B5omM8f5gYifmWIsj1」がSalt付きハッシュです。

こうすることで、Salt付きハッシュが漏れても、「先頭がSaltである」という縛りがパスワード別に発生するため、Rainbow tableを利用するのが困難となります。ただ、Saltが短いと、Rainbow tableであらかじめ計算しておいた内容と合致する確率が高くなるため、ある程度長いほうが好ましいと思われます。

また、$1$ の1の部分が変わることで、MD5以外に以下のようなアルゴリズムが環境によっては使えるようです。

最初のフィールド(id) アルゴリズム
1 MD5
2a Blowfish (本流の glibc には入っていない&br;いくつかの Linux ディストリビューションで追加されている)
5 SHA-256 (glibc 2.7 以降)
6 SHA-512 (glibc 2.7 以降)

また、$id$ で始まらない場合は、古来からのDESを利用したハッシュとなります。最初の2文字から得た12ビットをSaltとし、後ろの8文字から得た56ビットがハッシュに相当します。

別の方法:ワンタイムパスワード

スクエアエニックスがFF11等で利用しているのが、このワンタイムパスワードです。

これは何をやっているかというと、「何らかの計算式で時間ごとに計算された数値をパスワードして利用する」というものです。事前に配られた「セキュリティトークン」と呼ばれるキーホルダー低後の大きさの物理デバイスを利用します。この物理デバイスには、「耐タンパー性」と呼ばれる「分解されても中の計算式がバレないようにする性質」があります。
ログインしたい時には、セキュリティトークンを起動し、そこに表示された数桁の数値をサーバに送ります。サーバ側でも同様の計算式で数値を求め、それが同じであることを確認します。

この方式では、セキュリティトークンを知っているサーバさえ重点的に安全にしておけば良いという利点があります。

まとめ

ざっくり纏めると以下のようになります。

  • フロントから取得が必要ないデータ(クレジットカード番号等)は公開鍵暗号が望ましい
  • フロントから取得するデータ(再編集の必要な個人情報等)は、データベース上のデータと同様にフロント側のサーバも安全でなければいけない
  • パスワードはある程度の長さのSaltを付けてハッシュ関数を通したものをデータベースに保存するべき

暗号化の専門家ではない一個人が整理したものですが、誤解している点があればコメントやTwitter等でご指摘くださると助かります。

追記 (2011-05-02)

パスワードはハッシュ化されていた(Saltの有無は不明)という報道があったため、該当部分に追記しました。また、アプリケーションサーバ経由で不正にツールを導入しての侵入ということなので、フロント部分が復号していたデータについては暗号化が解かれてしまっている可能性があります。

追記 (2011-05-04)

[twitter:@pcs35823]さんにご指摘いただいたとおり、shadowファイルのパスワードフィールドの説明が誤っていました。

該当部分を修正のうえ、他の方式についても追記しました。ご指摘ありがとうございました。