Carpe Diem

備忘録

SCRAM (Salted Challenge Response Authentication Mechanism) 認証

概要

MongoDBやPostgreSQLでは認証時にSCRAM (Salted Challenge Response Authentication Mechanism) と呼ばれる認証方式を採用しています。
これは従来のチャレンジレスポンス型の認証を改善したものであり、パスワード(ハッシュ値含む)といった機密情報をサーバ側で保持せずとも認証できる仕組みです。

今回はそのSCRAMについて図を交えて説明します。

シーケンス

SCRAMのシーケンスは以下です。

従来のチャレンジレスポンス認証のようにnonceを用いてパスワードのような機密情報が認証時に経路に流れないようになっています。
チャレンジレスポンス認証はこれによりパスワードの盗聴やリプレイ攻撃に対する耐性を保証します。

しかし従来だと検証のため共通鍵(パスワード or そのハッシュ値)がクライアント・サーバ両者に必要であり、

  • データベース情報の漏洩
    • チャレンジレスポンスでは共通の鍵が必要なためハッシュ化して保存していてもクライアントで同様のハッシュ化が必要になるので深刻度は変わらない
  • 悪意あるサーバ管理者のパスワード流用

といった課題がありました。

SCRAMでは検証に必要な機密情報をサーバ側が持つ必要がないため、上記課題を解決したゼロ知識証明な認証方式と言えます。

要素

SCRAM認証で出てくる要素とその生成方法は以下です。

要素 生成方法
SaltedPassword PBKDF2(HMAC, password, salt, iteration-count)
ClientKey HMAC(SaltedPassword, "Client Key")
StoredKey H(ClientKey)
AuthMessage client-first-message-bare + "," +
server-first-message + "," +
client-final-message-without-proof
ClientSignature HMAC(StoredKey, AuthMessage)
ClientProof ClientKey XOR ClientSignature
ServerKey HMAC(SaltedPassword, "Server Key")
ServerSignature HMAC(ServerKey, AuthMessage)

生成のために使う関数は以下です。

関数 説明
PBKDF2(HMAC, password, salt, i) 鍵導出関数
HMAC(key, str) メッセージ認証コード関数
H(str) ハッシュ関数

AuthMessageの中身の詳細は以下です。

要素 中身
client-first-message-bare username,
client-nonce
server-first-message combined-nonce(client-nonce + server-nonce),
salt,
iteration-count
client-final-message-without-proof base64(channel‑flag,channel‑binding),
combined-nonce

※詳細な定義及びオプション値はRFC5802を参照してください

これらを,区切りで以下のようにくっつけています。
nonceは何度も登場するので何度もくっつけているのが印象的です。

r=c‑nonce,r=combined‑nonce,s=salt,i=iteration‑count,c=base64(channel‑flag,channel‑binding),r=combined‑nonce

図解

各要素やシーケンスを図解します。

コンポーネント

全体図

先程挙げた要素の関係図を表すと以下になります。

サーバに登録する情報

まず事前に登録する際にsalt, iteration-countをユーザ毎に固定し、クライアントのパスワードを使ってStoredKeyServerKeyを発行してもらいます。 そしてこの4つをサーバ側でデータとして保持します。

MongoDBだと以下のようになります。

> db.system.users.findOne({user: "testUser"})
{
    "_id" : "admin.testUser",
    "user" : "testUser",
    "db" : "testdb",
    "credentials" : {
        "SCRAM-SHA-1" : {
            "iterationCount" : 10000,
            "salt" : "+seF99VS0sZFe30VPBHA7A==",
            "storedKey" : "DYPbk/QJVowCNDPe2O2uWMmGq8U=",
            "serverKey" : "q4KAi4pVZNOLCgWcxcBr7jkM3m8="
        }
    },
    "roles" : [
        {
            "role" : "readWrite",
            "db" : "testDb"
        }
    ]
}

ポイントはパスワードといったクライアントの機密情報をサーバが保持しない点です。

クライアント側はサーバに保持してもらったsalt, iteration-countをサーバから渡してもらえれば常にSaltedPasswordを生成できるので、そこから認証に必要なClientKeyServerKeyをいつでも用意できます。

シーケンス詳細

シーケンス図

冒頭でもシーケンスを見せましたが、詳細なシーケンスは以下です。

詳細説明

  1. usernameとclientNonceをサーバへ送る
  2. サーバは登録済のusernameからsaltとiterationCountを検索する
  3. serverNonceを生成してclientNonceにくっつける(combinedNonce)
  4. クライアントへsalt, iterationCount, combinedNonceを返す
  5. クライアントは受け取ったsalt, iterationCountと、自身のパスワードで認証に必要なClientKey, ServerKeyを計算する

  6. クライアントは受け取った値とheader値からAuthMessageを用意し、それを元にClientProofを生成する

  7. クライアントはサーバにClientProofを送る

  8. サーバはAuthMessageを同様に用意できるので、ClientSignatureを計算する

  9. 受け取ったClientProofと計算したClientSignatureのXORでClientKeyを生成する。それをハッシュ化してStoredKeyと一致するか検証する

  10. 検証が成功したらサーバはServerSignatureを生成する

  11. サーバはクライアントへServerSignatureを送る

  12. クライアントはServerKeyを計算できるので、そこから同様にServerSignatureを計算する

  13. ServerSignatureが一致するか検証し、サーバ側がなりすましでないことを確認できる

まとめ

従来のチャレンジレスポンス認証の改善版であるSCRAMについて説明しました。

参考