Digest認証をGo言語で実装して挙動を理解する

前回のBasic認証に引き続き、今回はDigest認証について見ていきます。

Digest認証

前回の記事でBasic認証はセキュリティ的にガバガバなことがわかりました。
次は、Basic認証よりは強固なDigest認証を見ていきます。
少し複雑になります。

動作手順

  1.  クライアントがサーバーにリクエストを送る
  2. サーバーが、401レスポンスコードと一緒に認証領域や認証方式に関する情報やnonceを返す
  3. クライアントが認証領域をユーザーに提示し、ユーザー名、パスワードの入力を求める
  4. 入力されると、それらの情報と、先ほど受け取った値から新たな文字列を作成し、認証ヘッダを追加して再び送信する
  5. サーバー側も同じ計算をし、送られてきたものが正しいかどうかを確認する
  6. 認証が合っていれば、リクエストを処理し、間違っていれば再び401レスポンスコードを返す

実際に試してみる

では、golangで実装したサーバーとcurlコマンドでこのDigest認証を触りながら理解していきます。

上記のコードを「digest.go」などのファイル名で保存して、$ go run digest.goでサーバーを立ち上げます。

Digest認証に関しては標準のモジュールがなかったので、このコードは疑似コードになります。
ですので、挙動はイメージであり、どんなユーザー名、パスワードでも認証に成功します。

ちゃんとした内部のロジックをもう少し詳しく見たい方は、このStackOverFlowの回答などが参考になるかと思います。

チャレンジする

--digestオプションを付けてcurlコマンドを実行し、リクエストを送ります。

$ curl -v --digest -u user:pass http://localhost:18888/digest

結果は以下です。

行頭が「>」になっている行がリクエストで、「<」になっている部分がレスポンスなので、往復しているのがわかるかと思います

レスポンスの1行目のステータスラインを見てみると、401が返っているのがわかります。
2度あるうちの、1度目のレスポンスの「WWW-Authenticateヘッダ」を見てみます。WWW-Authenticate: Digest realm="my private area", nonce="RMH1usDrAwA=6dc290ea3304de42a7347e0a94089ff5912ce0de", algorithm=MD5, qop="auth"

いくつかのkeyがありますね。
一つずつ見ていきます。
realmは前回の記事で見たので、nonceから見てみます。

nonce

「number used once」の略で、一度のみ使われる数字という意味です。
リクエストごとに変化するランダムデータです。

algorithm

ダイジェスト文字列を生成するためのアルゴリズムです。
「MD5」とした場合、MD5ハッシュ値を求めます。

qop

「quality of protection」の略で、保護レベルを表し、「auth」か「auth-init」のどちらかの値を取ります。

 

  • auth→メソッドとURIからダイジェストを作成
  • auth-init→メソッドとURIとメッセージボディからダイジェストを作成する

つまり、「auth-init」を指定すればボディも含めて暗号化するので、POSTやPUTで見られる第三者の表現の改竄を防ぐことができます。

これらを用いた計算を行い、再度送信することで認証をします。
このように、認証のために必要となる情報を一度目のレスポンスで受け取ることを「チャレンジ」と呼びます。

ダイジェストを作成する

「Digest認証」の「Digest」とは、「メッセージダイジェスト」の略です。
つまり、これはあるメッセージに対してハッシュ関数を適用して得られたハッシュ値のことです。

先ほどの出力の二度目のリク得られた「Authorizationヘッダ」の中身を見てみます。
Authorization: Digest username="user", realm="my private area", nonce="RMH1usDrAwA=6dc290ea3304de42a7347e0a94089ff5912ce0de", uri="/digest", cnonce="Y2IwZWQ2NTVkYjQxYmNlNGI0YWFkNzAxY2RhNDVkMmI=", nc=00000001, qop=auth, response="160409b25959dae85b4bb218ec4a83a1", algorithm="MD5"

nonceとqopとalgorithmに関しては、先ほど確認しました。
このnonceは先ほどのものと全く同じものです。

では、残りの「cnonce」、「nc」、「response」は一体何なのでしょうか。

cnonce

クライアント側で作成したnonceです。
この値は平文でネットワーク上を流れるので、ランダムにする意味がなさそうですが、これは選択平文攻撃を防ぐためです。

nc

「nonce-count」の略で、特定のnonce値を使ってクライアントが送ったリクエスト数です。
16進数8桁の数値で、00000001から始まり、カウントアップされていきます。
qopがないときは省略されます。

response

以上のデータによって計算されたダイジェストです。
以下に計算式を示します。

ダイジェスト生成のアルゴリズム

  • A1 = usernamme:realm:pass
  • A2 = HTTPのメソッド:コンテンツのURI

とすると、これらを使って、responseは以下のように算出できます。

response = MD5(MD5(A1):nonce:nc:cnonce:qop:MD5(A2))

こうして求まったresponseをAuthorizationヘッダに入れて再びリクエストを送ります。

サーバー側の処理

そして、サーバー側ではサーバー内に保存されているユーザー名とパスワードを用いて同じ計算をし、一致するかどうかを確認します。

不一致の場合は以上の処理を繰り返します。

また、別の場所にアクセスする場合も、リクエストのたびに一度401を返され、そのたびにダイジェストを作成します。
これは、サーバーからのnonceがなければクライアント側でダイジェストの計算ができないからです。

以上がDigest認証の流れです。
Basic認証よりもセキュリティ的にマシになりました。

しかし、Digest認証を使っても不便なところはいくつかあり、それを埋めるためにもCookieを使ったセッション管理などがありますが、それは今度調べてみます。

参考