🦭
SRP認証クライアントの実装と備忘メモ
ユーザー認証について検討した時に、暗号化されていない通信路を介してでもユーザ名とパスワードを使って認証可能なSRP(Secure Remote Password)プロトコルがあることを知りました。AWS Cognito等でも利用できるらしく良さそうだと思ったので何番煎じなのかはわかりませんが内容を理解すべく自分でTypeScriptのライブラリを作成しました。有名なSRPにはSRP-3とSRP-6aがあるようですが、SRP-6aを実装しています。実装を通じて理解したSRP認証の流れのポイントを備忘メモとして残しておきます。
成果物
概要
ユーザー登録時とログイン時で流れが大きく異なる。認証鍵session,keyはフロント・サーバーの両方で計算して導出する。正しいユーザー名とパスワードを使用すれば結果的に同じ値が導出されるが、フロント側とサーバー側で使用する計算式が異なり、使用する変数(引数)も異なる。計算式がそれぞれ異なるが、結果が等しくなるように設計されているのがSRP認証の重要なポイント。
式・変数・単語の定義
| 式 | 説明 |
|---|---|
| | | 結合演算子 |
| ^ | べき乗演算子 |
| H() | 一方向ハッシュ関数 |
| RAND() | 乱数バイト配列生成 |
| MP(n,k,m) | 剰余べき乗関数 (n^k % m) |
| PAD(d) | バイト配列ゼロ詰め固定長化関数 |
| 数式変数 | 説明 | 表記 |
|---|---|---|
| N | とても大きい素数 | prime |
| g | 数値を生成するための原始根 | generator |
| k | 複雑性向上のための値 | multiplier |
| s | ランダムなソルト | salt |
| U | ユーザー名 | username |
| p | 平文パスワード | password |
| I | ユーザー名+パスワードのハッシュ | identity |
| x | 身元証明値生成の鍵となる値 | secret |
| v | 身元証明値の検証の鍵となる値 | verifier |
| u | 中間者攻撃による改ざんを防止する値 | scrambling |
| a | ランダム生成されたフロント側秘密鍵 | pvtClient |
| A | ランダム生成されたフロント側公開鍵 | pubClient |
| b | ランダム生成されたサーバー側秘密鍵 | pvtServer |
| B | ランダム生成されたサーバー側公開鍵 | pubServer |
| S | 計算で導出される認証鍵 | session |
| K | 計算で導出された認証鍵のハッシュ | key |
| Mc | 認証鍵から生成した通信用の値 | evidence |
| Ms | 認証鍵から生成した通信用の値 | evidenceServer |
導出式一覧
| 数式変数 | 導出式 | メモ |
|---|---|---|
| N | <constant> | - |
| g | <constant> | - |
| U | <read from outside> | - |
| p | <read from outside> | - |
| I | H(U | ":" | p) | - |
| x | H(s | I) | - |
| s | RAND() | - |
| v | MP(g, x, N) | - |
| k | H(N | PAD(g)) | - |
| a | RAND() | - |
| A | MP(g, a, N) | - |
| b | RAND() | - |
| B | k * v + MP(g, b, N) | - |
| u | H(PAD(A) | PAD(B)) | - |
| Sc | MP(B - (k * MP(g, x, N)), a + (u * x), N) | Client side |
| Ss | MP(MP(v, u, N) * A, b, N) | Server side |
| Kc | H(Sc) | Client side |
| Ks | H(Ss) | Server side |
| Mc | H(H(N) xor H(g), H(U), s, A, B, Kc) | - |
| Mc' | H(H(N) xor H(g), H(U), s, A, B, Ks) | Verify Mc |
| Ms | H(A, Mc, Ks) | - |
| Ms' | H(A, Mc, Ks) | Expected Ms |
認証手順
ユーザー登録時
- フロント側:
username,passwordからsaltとverifierを生成 - フロント側:
username,salt,verifierをサーバーに送信 - サーバー側: 受け取ったデータをデータベース等に格納
ログイン時
- フロント側:
pvtClientとpubClientのランダム鍵ペアを生成 - フロント側:
usernameと生成したpubClient公開鍵をサーバーに送信 - サーバー側: データを受け取り、データベース等から
saltとverifierを取得 - サーバー側:
pvtServerとpubServerのランダム鍵ペアを生成 - サーバー側:
saltとpubServer公開鍵をフロントに送信 - フロント側:
session認証鍵を計算で導出 - フロント側:
session認証鍵から通信用の値evidenceを導出 - フロント側:
session認証鍵からサーバーから送信されるevidenceServerを予測導出 - フロント側: 導出した通信用の値
evidenceをサーバーに送信 - サーバー側: データを受け取り、
session認証鍵を異なる計算式で導出 - サーバー側:
session認証鍵からevidenceを導出する - サーバー側: 受け取った
evidenceと導出したevidenceが等しいことを確認 - サーバー側:
session認証鍵からevidenceServerを導出する - サーバー側: 認証結果と
evidenceServerをフロントに送信 - フロント側: 予測した
evidenceServerと受け取ったevidenceServerの一致を確認
雑記
SPR認証の実装について書いている記事がいくつかありますが、keyを通信する事になっている実装があったりして最初は混乱しました。自分で使うためにも作ったので、そこまで悪くないものができた気がします。
Discussion