概要
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
をユーザ毎に固定し、クライアントのパスワードを使ってStoredKey
、ServerKey
を発行してもらいます。
そしてこの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
を生成できるので、そこから認証に必要なClientKey
やServerKey
をいつでも用意できます。
シーケンス詳細
シーケンス図
冒頭でもシーケンスを見せましたが、詳細なシーケンスは以下です。
詳細説明
- usernameとclientNonceをサーバへ送る
- サーバは登録済のusernameからsaltとiterationCountを検索する
- serverNonceを生成してclientNonceにくっつける(combinedNonce)
- クライアントへsalt, iterationCount, combinedNonceを返す
クライアントは受け取ったsalt, iterationCountと、自身のパスワードで認証に必要なClientKey, ServerKeyを計算する
クライアントは受け取った値とheader値からAuthMessageを用意し、それを元にClientProofを生成する
クライアントはサーバにClientProofを送る
サーバはAuthMessageを同様に用意できるので、ClientSignatureを計算する
受け取ったClientProofと計算したClientSignatureのXORでClientKeyを生成する。それをハッシュ化してStoredKeyと一致するか検証する
検証が成功したらサーバはServerSignatureを生成する
サーバはクライアントへServerSignatureを送る
クライアントはServerKeyを計算できるので、そこから同様にServerSignatureを計算する
ServerSignatureが一致するか検証し、サーバ側がなりすましでないことを確認できる
まとめ
従来のチャレンジレスポンス認証の改善版であるSCRAMについて説明しました。
参考
- RFC 5802: Salted Challenge Response Authentication Mechanism (SCRAM) SASL and GSS-API Mechanisms
- Salted Challenge Response Authentication Mechanism - Wikipedia
- Safely Protect PostgreSQL Passwords - Tell Others to SCRAM
- Improved Password-Based Authentication in MongoDB 3.0: SCRAM Explained - Pt. 1 | MongoDB Blog
- Improved password-based authentication in MongoDB 3.0: SCRAM Explained (Part 2) | MongoDB Blog