Featured image of post パスキーの仕組み

パスキーの仕組み

目次

背景

  • 最近パスキーで認証するシステムを実装した
  • パスワードを使わない公開非公開鍵方式でCryptみたいに便利だった
  • その仕組みを整理した

パスキーとは

  • FIDO Allianceの定義によれば、パスキーとは「FIDO標準に基づく認証情報で、スマートフォンやPC、またはハードウェアセキュリティキーに保存できるもの」
  • 中身は公開鍵暗号の鍵ペア。パスワードのような「サーバーと共有する秘密の文字列」ではなく、デバイス側に秘密鍵を保持し、サーバー側には公開鍵だけを保存する方式
  • ログイン時は、デバイスのロック解除と同じ操作(生体認証・PIN・パターンなど)で承認するだけで済む

公開鍵暗号を使った認証の仕組み

  • WebAuthn(ブラウザが提供するAPI)の用語では、サービス側を「Relying Party(RP)」、ブラウザを「Client」、鍵ペアを生成・保管する側(スマートフォンやセキュリティキーなど)を「Authenticator」と呼ぶ

登録(Registration)

  • ユーザーがアカウント登録・新しいパスキーの追加を行うと、ブラウザはnavigator.credentials.create()を呼び出す
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
let credential = await navigator.credentials.create({
  publicKey: {
    challenge: new Uint8Array([117, 61, 252, 231, 191, 241 /* … */]),
    rp: { id: "acme.com", name: "ACME Corporation" },
    user: {
      id: new Uint8Array([79, 252, 83, 72, 214, 7, 89, 26]),
      name: "jamiedoe",
      displayName: "Jamie Doe",
    },
    pubKeyCredParams: [{ type: "public-key", alg: -7 }],
  },
});
  • Authenticatorが、そのRPに紐づく公開鍵・秘密鍵のペアを新しく生成する
  • 秘密鍵はAuthenticator内に残り、外に出ることはない。公開鍵だけがRPに送られ、ユーザーアカウントに紐づけて保存される
  • この際、Authenticatorが自分の正当性を証明する「Attestation(証明)」という情報も一緒に返されることがある

認証(Authentication)

  • ログイン時は、ブラウザがnavigator.credentials.get()を呼び出す
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
let credential = await navigator.credentials.get({
  publicKey: {
    challenge: new Uint8Array([139, 66, 181, 87, 7, 203 /* … */]),
    rpId: "acme.com",
    allowCredentials: [
      {
        type: "public-key",
        id: new Uint8Array([64, 66, 25, 78, 168, 226, 174 /* … */]),
      },
    ],
    userVerification: "required",
  },
});
  • RPがランダムな challenge(毎回変わる使い捨ての値)を発行する
  • Authenticatorが、デバイスのロック解除などでユーザー本人であることを確認した上で、保存していた秘密鍵でそのchallengeに署名する
  • RPは、登録時に保存しておいた公開鍵を使って、その署名を検証する。秘密鍵そのものはネットワーク上を一切流れない

なぜフィッシング耐性があるのか

  • 署名の対象には、challengeだけでなく、RP ID(アクセス先のドメイン)も含まれる
  • 例えば正規サイトがacme.comだとして、攻撃者がacme-fake.comのようなフィッシングサイトを用意しても、Authenticatorはそのドメイン向けの署名を作ってくれない。署名がオリジン(ドメイン)に紐づいているため、別ドメインでは再利用できない
  • パスワードのように「人間がどのサイトにいるか勘違いして入力してしまう」という失敗がそもそも起こり得ない設計になっている
  • Yubicoのデータでは、パスキーによってフィッシング・認証情報窃取へのリスクが99.99%減少するとされている

Discoverable Credential(Resident Key)

  • 通常のWebAuthn認証では、RPがallowCredentialsでcredential IDを指定する必要があるが、パスキーは「Discoverable Credential(かつてはResident Keyと呼ばれた)」という形式を使う
  • これは、RP IDさえ分かれば、Authenticator側に保存された資格情報を自分で見つけ出し、署名を生成できる方式
  • この「Client-side Discoverable Credential」のうち、複数デバイス間で同期されるものが、一般に「パスキー」と呼ばれている

WebAuthn・FIDO2・CTAPの関係

  • WebAuthnは、ブラウザが提供する、Webサイトから公開鍵認証を扱うためのJavaScript API(W3Cの標準)
  • CTAP(Client to Authenticator Protocol)は、ブラウザ(Client)と、USBキーやスマートフォンなどの外部Authenticatorとの間の通信プロトコル
  • WebAuthn + CTAPをあわせて「FIDO2」と呼ぶ。パスキーは、このFIDO2の標準をベースにした実装にあたる

デバイス間の同期

  • パスキーの保存・同期方法には、大きく2種類ある
種類内容
同期パスキー(Synced passkeys)iCloudキーチェーンやGoogleパスワードマネージャーなどのクラウド経由で、同じアカウントの他のデバイスにも自動的に複製される
デバイス固定パスキー(Device-bound passkeys)特定の1台のデバイスやハードウェアセキュリティキーの中に留まり、クラウドには同期されない
  • 自分のスマートフォンにパスキーがあり、PC側にはまだ登録していない場合でも、FIDOのCross-Device Authenticationという仕組みで、Bluetoothによる近接確認を行った上で、スマートフォン経由でPC側のログインを完了できる

まとめ

  • パスキーは、公開鍵暗号の鍵ペアをベースにした認証方式で、秘密鍵はデバイス(Authenticator)内に留まり、サーバー(RP)には公開鍵だけが保存される
  • 登録(navigator.credentials.create())でAuthenticatorが鍵ペアを生成して公開鍵をRPに渡し、認証(navigator.credentials.get())ではRPが発行したchallengeに秘密鍵で署名することで、本人確認が行われる
  • 署名がRP ID(ドメイン)に紐づいているため、フィッシングサイトでは署名が成立せず、パスワードと違って原理的にフィッシング耐性を持つ
  • パスキーは、RP IDだけで資格情報を特定できるDiscoverable Credentialという形式をベースにしており、WebAuthn・CTAPをあわせたFIDO2標準の実装にあたる
  • 同期パスキー(クラウド経由で複数デバイスに複製)とデバイス固定パスキー(1台に留まる)の2種類があり、Bluetoothによる近接確認を使ったデバイス間認証の仕組みもある

参考文献

Built with Hugo
テーマ Stack は Jimmy によって設計されています。