建立密碼金鑰

使用者必須先為帳戶註冊或建立密碼金鑰,應用程式才能驗證密碼金鑰。

如要建立密碼金鑰,請從應用程式伺服器取得建立密碼金鑰所需的詳細資料,然後呼叫 Credential Manager API,該 API 會傳回公開和私密金鑰配對。系統會將傳回的私密金鑰儲存在憑證供應商 (例如 Google 密碼管理工具) 中,做為密碼金鑰。公開金鑰會儲存在應用程式伺服器上。

密碼金鑰會儲存在憑證供應商中,公開金鑰則會儲存在應用程式伺服器中
圖 1: 建立密碼金鑰

必要條件

請確認您已設定數位資產連結,並指定搭載 Android 9 (API 級別 28) 以上版本的裝置。

總覽

本指南著重於在憑證方用戶端應用程式中建立密碼金鑰所需的變更,並簡要概述憑證方應用程式伺服器的實作方式。如要進一步瞭解伺服器端整合,請參閱「伺服器端密碼金鑰註冊」。

  1. 為應用程式新增依附元件:新增必要的 Credential Manager 程式庫。
  2. 例項化 Credential Manager:建立 Credential Manager 例項。
  3. 從應用程式伺服器取得憑證建立選項:從應用程式伺服器將建立密碼金鑰所需的詳細資料傳送至用戶端應用程式,例如應用程式、使用者資訊,以及 challenge 和其他欄位。
  4. 要求密碼金鑰:在應用程式中,使用從應用程式伺服器收到的詳細資料建立 GetPublicKeyCredentialOption 物件,並使用這個物件叫用 credentialManager.getCredential() 方法來建立密碼金鑰。
  5. 處理密碼金鑰建立回應:在用戶端應用程式收到憑證時,您必須編碼、序列化,然後將公開金鑰傳送至應用程式伺服器。您也必須處理密碼金鑰建立時可能發生的每個例外狀況。
  6. 在伺服器上驗證並儲存公開金鑰:完成伺服器端步驟,驗證憑證來源,然後儲存公開金鑰。
  7. 通知使用者:通知使用者密碼金鑰已建立。

1. 為應用程式新增依附元件

在應用程式模組的 build.gradle 檔案中新增下列依附元件:

Kotlin

dependencies {
    implementation("androidx.credentials:credentials:1.6.0-beta03")
    implementation("androidx.credentials:credentials-play-services-auth:1.6.0-beta03")
}

Groovy

dependencies {
    implementation "androidx.credentials:credentials:1.6.0-beta03"
    implementation "androidx.credentials:credentials-play-services-auth:1.6.0-beta03"
}

2. 例項化 Credential Manager

使用應用程式或活動情境建立 CredentialManager 物件。

// Use your app or activity context to instantiate a client instance of
// CredentialManager.
private val credentialManager = CredentialManager.create(context)

3. 從應用程式伺服器取得憑證建立選項

當使用者點選「建立密碼金鑰」按鈕或註冊新帳戶時,請從應用程式向應用程式伺服器發出要求,取得啟動密碼金鑰註冊程序所需的資訊。

在應用程式伺服器中使用符合 FIDO 標準的程式庫,將建立密碼金鑰所需的資訊傳送給用戶端應用程式,例如使用者、應用程式和額外設定屬性的相關資訊。詳情請參閱伺服器端密碼金鑰註冊

在用戶端應用程式中,解碼應用程式伺服器傳送的公開金鑰建立選項。這些通常以 JSON 格式表示。如要進一步瞭解如何為網頁用戶端執行這項解碼作業,請參閱「編碼和解碼」。如果是 Android 用戶端應用程式,您必須另外處理解碼作業。

以下程式碼片段顯示應用程式伺服器傳送的公開金鑰建立選項結構:

{
  "challenge": "<base64url-encoded challenge>",
  "rp": {
    "name": "<relying party name>",
    "id": "<relying party host name>"
  },
  "user": {
    "id": "<base64url-encoded user ID>",
    "name": "<user name>",
    "displayName": "<user display name>"
  },
  "pubKeyCredParams": [
    {
      "type": "public-key",
      "alg": -7
    }
  ],
  "attestation": "none",
  "excludeCredentials": [
    {
        "id": "<base64url-encoded credential ID to exclude>", 
        "type": "public-key"
    }
  ],
  "authenticatorSelection": {
    "requireResidentKey": true,
    "residentKey": "required",
    "userVerification": "required"
  }
}

公開金鑰建立選項中的重要欄位包括:

  • challenge:伺服器產生的隨機字串,用於防止重播攻擊。
  • rp:應用程式的詳細資料。
    • rp.name:應用程式名稱。
    • rp.id:應用程式的網域或子網域。
  • user:使用者詳細資料。
    • id:使用者的專屬 ID。這個值不得包含個人識別資訊,例如電子郵件地址或使用者名稱。你可以使用隨機 16 位元組值。
    • name:使用者能夠認得的帳戶專屬 ID,例如電子郵件地址或使用者名稱。這會顯示在帳戶選取器中 如要指定使用者名稱,請使用密碼驗證中的值。
    • displayName:帳戶的選用名稱,方便使用者在帳戶選取器中辨識。
  • authenticatorSelection:用於驗證的裝置詳細資料。

    • authenticatorAttachment:指出偏好的驗證器。可能的值如下: - platform:這個值用於使用者裝置內建的驗證器,例如指紋感應器。 - cross-platform:這個值用於漫遊裝置,例如安全金鑰。通常不會在密碼金鑰情境中使用。 - 未指定 (建議):如果未指定這個值,使用者就能在慣用裝置上建立密碼金鑰。在大多數情況下,不指定參數是最佳選擇。
      • requireResidentKey:如要建立密碼金鑰,請將這個 Boolean 欄位的值設為 true
      • residentKey:如要建立密碼金鑰,請將值設為 required
      • userVerification:用於指定密碼金鑰註冊期間的使用者驗證規定。可能的值如下: - preferred:如果使用者體驗比保護措施更重要,請使用這個值,例如在使用者驗證造成的摩擦比保護措施還多的環境中。 - required:如需叫用裝置上可用的使用者驗證方法,請使用這個值。 - discouraged:如果建議不要使用使用者驗證方法,請使用這個值。
        如要進一步瞭解 userVerification,請參閱 userVerification 深入說明
  • excludeCredentials:在陣列中列出憑證 ID,防止建立重複的密碼金鑰 (如果已存在相同憑證提供者的密碼金鑰)。

4. 要求建立密碼金鑰

剖析伺服器端公開金鑰建立選項後,請將這些選項包裝在 CreatePublicKeyCredentialRequest 物件中,然後呼叫 createCredential(),藉此建立密碼金鑰。

createPublicKeyCredentialRequest 包含下列項目:

  • requestJson:應用程式伺服器傳送的憑證建立選項。
  • preferImmediatelyAvailableCredentials:這是選用的布林值欄位,可定義是否只使用本機可用的憑證或憑證供應商同步處理的憑證執行要求,而不使用來自安全金鑰或混合金鑰流程的憑證。可能的用途如下:
    • false (預設):如果對 Credential Manager 的呼叫是由明確的使用者動作觸發,請使用這個值。
    • true:如果 Credential Manager 是在適當時機呼叫 (例如首次開啟應用程式時),請使用這個值。
      如果將值設為 true,且當前沒有即時可用的憑證,Credential Manager 就不會顯示任何 UI,要求也將立即失敗,針對 get 要求傳回 NoCredentialException,針對 create 要求傳回 CreateCredentialNoCreateOptionException
  • origin:這個欄位會自動為 Android 應用程式設定。如需為瀏覽器和類似的特殊權限應用程式設定 origin,請參閱「代表其他方為具有特殊權限的應用程式呼叫 Credential Manager」。
  • isConditional:這是選填欄位,預設為 false。如果將這項設定設為 true,且使用者沒有密碼金鑰,系統會在使用者下次以已儲存的密碼登入時,自動為他們建立密碼金鑰。密碼金鑰會儲存在使用者的憑證提供者服務中。如要使用條件式建立功能,必須安裝androidx.credentials最新版本

呼叫 createCredential() 函式會啟動 Credential Manager 的內建底部功能表 UI,提示使用者使用密碼金鑰,並選取憑證供應商和帳戶來儲存密碼金鑰。不過,如果 isConditional 設為 true,系統就不會顯示底部功能表 UI,而是自動建立密碼金鑰。

5. 處理回應

使用裝置的螢幕鎖定驗證使用者後,系統會建立密碼金鑰,並儲存在使用者選取的憑證提供者中。

成功呼叫 createCredential() 後,回應會是 PublicKeyCredential 物件。

PublicKeyCredential 如下所示:

{
  "id": "<identifier>",
  "type": "public-key",
  "rawId": "<identifier>",
  "response": {
    "clientDataJSON": "<ArrayBuffer encoded object with the origin and signed challenge>",
    "attestationObject": "<ArrayBuffer encoded object with the public key and other information.>"
  },
  "authenticatorAttachment": "platform"
}

在用戶端應用程式中,序列化物件並傳送至應用程式伺服器。

新增程式碼來處理失敗情形,如下列程式碼片段所示:

fun handleFailure(e: CreateCredentialException) {
    when (e) {
        is CreatePublicKeyCredentialDomException -> {
            // Handle the passkey DOM errors thrown according to the
            // WebAuthn spec.
        }
        is CreateCredentialCancellationException -> {
            // The user intentionally canceled the operation and chose not
            // to register the credential.
        }
        is CreateCredentialInterruptedException -> {
            // Retry-able error. Consider retrying the call.
        }
        is CreateCredentialProviderConfigurationException -> {
            // Your app is missing the provider configuration dependency.
            // Most likely, you're missing the
            // "credentials-play-services-auth" module.
        }
        is CreateCredentialCustomException -> {
            // You have encountered an error from a 3rd-party SDK. If you
            // make the API call with a request object that's a subclass of
            // CreateCustomCredentialRequest using a 3rd-party SDK, then you
            // should check for any custom exception type constants within
            // that SDK to match with e.type. Otherwise, drop or log the
            // exception.
        }
        else -> Log.w(TAG, "Unexpected exception type ${e::class.java.name}")
    }
}

6. 在應用程式伺服器上驗證並儲存公開金鑰

在應用程式伺服器上,您必須驗證公開金鑰憑證,然後儲存公開金鑰

如要驗證公開金鑰憑證的來源,請與核准的應用程式許可清單進行比較。如果金鑰的來源不明,請拒絕。

如要取得應用程式的 SHA 256 指紋,請按照下列步驟操作:

  1. 在終端機中執行下列指令,列印發布應用程式的簽署憑證:

    keytool -list -keystore <path-to-apk-signing-keystore>
    

    在回應中找出簽署憑證的 SHA 256 指紋,也就是 Certificate fingerprints block : SHA256

  2. 使用 base64url 編碼方式編碼 SHA256 指紋。以下 Python 範例說明如何正確編碼指紋:

    import binascii
    import base64
    fingerprint = '<SHA256 finerprint>' # your app's SHA256 fingerprint
    print(base64.urlsafe_b64encode(binascii.a2b_hex(fingerprint.replace(':', ''))).decode('utf8').replace('=', ''))
    
  3. 在先前步驟的輸出內容開頭附加 android:apk-key-hash:,讓輸出內容類似以下內容:

    android:apk-key-hash:<encoded SHA 256 fingerprint>
    

    結果應與應用程式伺服器上的許可來源相符。如果有多個簽署憑證 (例如用於偵錯和發布的憑證),或有多個應用程式,請重複上述程序,接受所有這些來源做為應用程式伺服器上的有效來源。

7. 通知使用者

成功建立密碼金鑰後,請通知使用者,並告知他們可以透過憑證提供者應用程式或應用程式設定管理密碼金鑰。使用自訂對話方塊、通知或 Snackbar 通知使用者。由於惡意實體建立密碼金鑰時會觸發立即安全警報,建議您搭配使用電子郵件等外部通訊方式,輔助這些應用程式內建方法。

加強使用者體驗

如要提升使用者體驗,同時實作透過 Credential Manager 註冊的功能,建議您新增還原憑證的功能,並禁止顯示自動填入對話方塊。

新增在新的裝置上還原憑證的功能

如要讓使用者在新裝置上順暢登入帳戶,請實作「還原憑證」功能。新增還原憑證後,使用者在新裝置上開啟還原的應用程式時,系統會自動登入帳戶,讓他們立即使用應用程式。BackupAgent

禁止在憑證欄位中自動填入資料 (選用)

如果應用程式畫面預期使用者會使用憑證管理工具的底部功能表 UI 進行驗證,請在使用者名稱和密碼欄位中新增 isCredential 屬性。這樣可避免自動填入對話方塊 (FillDialogSaveDialog) 與 Credential Manager 的底部功能表 UI 重疊。

Android 14 以上版本支援 isCredential 屬性。

以下範例說明如何將 isCredential 屬性新增至應用程式相關檢視區塊中的使用者名稱和密碼欄位:

<TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:isCredential="true" />

後續步驟