ここ数年だけでもSSLに対する攻撃方法やバグが何度も見つかっています。SSLで通信を暗号化していてもパスワード認証時のトラフィックを解読されてしまえば、パスワードが漏洩していまいます。パスワードが判ってしまえば、攻撃者は何度でも被害者のアカウントを利用できます。
脆弱性でなくても、SSLも無効化したMITM(中間者攻撃)も可能です。SSLだから安心、とは考えられません。これを何とかできないか、考えてみます。
参考:
チャレンジレスポンス形式の認証
SSL通信が解読されてもパスワードを盗まれない方法といっても新しい方法ではありません。様々な認証方式で利用されている「チャレンジレスポンス」形式の認証方法を用いるだけです。チャレンジレスポンス形式の認証方式は平文通信を前提としたシステムで広く利用されています。SSL通信が無効になってしまう事例が何度も続いています。暗号化通信でも緩和策としてチャレンジレスポンスを用いても良いでしょう。
チャレンジレスポンス形式の認証はHTTPダイジェスト認証やSTMP認証、PPP認証などで利用されている昔ながらの方式です。私のブログでも紹介したはずですが、Googleのサイト検索では見つからないようです。
前置きが長くなりましたが、チャレンジレスポンス認証の仕組みを解説します。チャレンジレスポンス認証には暗号理論的に安全なハッシュ関数を利用します。暗号理論的に安全なハッシュ関数とは不可逆で、ハッシュ値から元の値を算出したり、推測したりすることが不可能なハッシュ関数です。現在はSHA256以上のハッシュ関数が安全と考えられています。
仕組みは簡単です。サーバー(認証するシステム)が認証リクエストを受けた場合、ランダムに生成された「チャレンジ」をクライアント(ユーザー名、パスワードを送信するシステム)に送ります。クライアントは「チャレンジ」と「パスワード」から生成したハッシュ値(「レスポンス」)とユーザー名をサーバーに送信します。その際、平文のパスワードはサーバーに送信しません。サーバー側では送信済みのチャレンジとユーザーのパスワードは既知に情報です。送信されてきたレスポンスがチャレンジとパスワードから生成したハッシュ値と一致することで、パスワードが正しいか確認します。
まとめると以下のような手順になります。
- クライアント: サーバーに認証開始を要求
- サーバー: チャレンジを生成・保存しチャレンジを送信
- クライアント:チャレンジとパスワードからレスポンスとなるハッシュ値を計算。ユーザー名とレスポンスを送信
- サーバー: サーバー側に保存されているチャレンジとパスワードからレスポンスを計算。ユーザーが送信したレスポンスと一致するか確認。
注:実際にチャレンジレスポンスを実装する場合、認証が完了したらサーバー側のチャレンジを削除します。削除しないと再生攻撃が可能になります。
チャレンジレスポンスの制限
現在のセキュリティ標準に準拠したシステムではパスワードはハッシュ化されて保存されているはずです。この為、この認証を利用するにはクライアント側で、サーバーと同じハッシュ方式でパスワードハッシュを生成し、チャレンジとのハッシュ値を取得しなければなりません。解説した手順では再生攻撃にも脆弱です。
- ハッシュ化されたパスワードの場合、パスワードハッシュ値自体を平文パスワードと同様に保護する必要がある
- パスワードをハッシュ値しない場合、平文パスワードを使用しなければならない
- 解説した手順では、ネットワークに送信されたレスポンスとユーザー名をそのまま再生して認証リクエストを送信した場合、認証できてしまう
メールサーバーを設定した事がある方なら認証方式によってはパスワードを平文で保存しなければならない事をご存知だと思います。パスワードが平文で保存されている理由は、プロトコルの仕様により平文での保存が必要だからです。
複雑なハッシュ化方式を採用している場合、クライアント側でも同じ方法でハッシュ値を計算する必要があります。ネットワークに平文パスワードが流れないメリットはありますが、クライアント側でハッシュ値を計算するのは明らかなデメリットです。セキュリティ対策のトレードオフとしてどう考えるかは難しいですが、私はやめた方が良いと思います。
RFC 2617で定義されているダイジェスト認証は紹介したチャレンジレスポンス形式の認証より、少し複雑な手順で3の再生攻撃を防いでいます。しかし、この方法にも問題があります。HTTP認証は認証情報をリクエスト毎にHTTPヘッダーとして送信します。RFC 2617ではアクセスカンターを用い、毎回異なるレスポンスを送信することにより再生攻撃から防御しています。つまりサーバーとクライアントの状態が同期している事が前提となっています。
HTTPプロトコルはステートレスです。クライアントは非同期でリクエストを送信します。この為、リクエストが不正なリクエストになってしまう場合もあります。HTTPダイジェスト認証は互換性の問題が原因で広くは使われませんでしたが、互換性以外にも同期の問題があります。この為、本格的なWebアプリケーションではHTTPダイジェスト認証は利用されません。
結局どうすれば良いのか?
チャレンジレスポンスを利用するのはあまり現実的でない事が分ったと思います。やはり多要素認証やクライアント証明による認証の方が優れています。多要素認証を用いるにはSMSや時間ベースの鍵生成器(ソフトウェアまたはハードウェア)などが必要です。無料のクライアント証明を発行するサービスもあります。しかし、クライアント証明は証明書の発行とインストールが簡単とは言い難いです。やはり別デバイスを用いた多要素認証の方が現実的でしょう。
チャレンジレスポンスより、Google AuthenticatorやTwilioを使った認証の導入を検討する方が良いのではないか、と考えています。ということで多要素認証を使いましょう。
JavaScriptのハッシュ関数/HMAC関数も必要です。以下のライブラリはSHA-1/2/3に加えてHMACもサポートしています。
Google Authenticatorを使った2要素認証はそのうち紹介したいと思います。
追記
書こうと思いつつ何年も経ってしまいました。
HTTPSを使っていてもパスワードを平文で送受信すると漏洩リスクがあります。チャレンジレスポンス形式の認証を導入する場合はHMACを使うと良いです。
再生攻撃防止にはいくつかの方法があります。タイムスタンプを利用する方法(有効期限内なら再生攻撃が可能だがサーバー側にDBが不必要)、シリアルナンバーを利用する方法(再生攻撃は不可能だがサーバー側にDBが必要)、一度限り有効な一時キーを使い有効なキーを管理する方法(再生攻撃は不可能だがサーバー側にDBが必要)などがあります。
パスワードは十分に長い文字列(最低限でも120バイト以上)を受け入れるようにしましょう。