Android 保護確認

為協助確認使用者在進行機密交易 (例如付款) 時的意圖,您可以在搭載 Android 9 (API 級別 28) 以上版本的支援裝置上,使用 Android 保護確認機制。使用這項工作流程時,應用程式會對使用者顯示提示,請他們核准一則簡短聲明,再次確認使用者完成機密交易的意圖。

如果使用者接受聲明,應用程式就能使用 Android KeyStore 中的金鑰,簽署對話方塊中顯示的訊息。該簽章有極高可信度,表示使用者已看過且同意該聲明。

注意:Android 保護確認機制不會為使用者提供安全資訊管道。除了 Android 平台提供的保證外,應用程式不得假設其他機密保證。請特別注意,勿利用此工作流程,顯示通常不會出現在使用者裝置上的機密資訊。

使用者確認訊息後,我們會確保訊息的完整性,但應用程式仍須使用傳輸中資料加密功能來保護已簽署訊息的機密性。

如要在應用程式中,為高度可信的使用者確認機制提供支援,請完成下列步驟:

  1. 使用 KeyGenParameterSpec.Builder 類別產生非對稱式簽署金鑰。建立金鑰時,請將 true 傳遞至 setUserConfirmationRequired()。此外,也請呼叫 setAttestationChallenge(),並由依賴方提供適當驗證值。

  2. 透過適合的依賴方註冊新產生的金鑰,以及您金鑰的認證憑證。

  3. 將交易明細傳送至伺服器,讓其產生並傳回「額外資料」的二進位大型物件 (BLOB)。額外資料可能包含待確認的資料或剖析提示,例如提示字串的語言代碼。

    為提升實作的安全性,BLOB 必須包含密碼編譯 Nonce,藉此防範重送攻擊並區分交易。

  4. 設定 ConfirmationCallback 物件,在使用者接受確認對話方塊中顯示的提示時,通知應用程式:

    Kotlin

    class MyConfirmationCallback : ConfirmationCallback() {
    
          override fun onConfirmed(dataThatWasConfirmed: ByteArray?) {
              super.onConfirmed(dataThatWasConfirmed)
              // Sign dataThatWasConfirmed using your generated signing key.
              // By completing this process, you generate a signed statement.
          }
    
          override fun onDismissed() {
              super.onDismissed()
              // Handle case where user declined the prompt in the
              // confirmation dialog.
          }
    
          override fun onCanceled() {
              super.onCanceled()
              // Handle case where your app closed the dialog before the user
              // responded to the prompt.
          }
    
          override fun onError(e: Exception?) {
              super.onError(e)
              // Handle the exception that the callback captured.
          }
      }

    Java

    public class MyConfirmationCallback extends ConfirmationCallback {
    
      @Override
      public void onConfirmed(@NonNull byte[] dataThatWasConfirmed) {
          super.onConfirmed(dataThatWasConfirmed);
          // Sign dataThatWasConfirmed using your generated signing key.
          // By completing this process, you generate a signed statement.
      }
    
      @Override
      public void onDismissed() {
          super.onDismissed();
          // Handle case where user declined the prompt in the
          // confirmation dialog.
      }
    
      @Override
      public void onCanceled() {
          super.onCanceled();
          // Handle case where your app closed the dialog before the user
          // responded to the prompt.
      }
    
      @Override
      public void onError(Throwable e) {
          super.onError(e);
          // Handle the exception that the callback captured.
      }
    }

    如果使用者核准對話方塊,系統會呼叫 onConfirmed() 回呼。dataThatWasConfirmed BLOB 是一種 CBOR 資料結構,內含其他詳細資料、使用者看到的提示文字,以及您傳遞至 ConfirmationPrompt 建構工具的額外資料。請使用先前建立的金鑰簽署 dataThatWasConfirmed BLOB,然後將這個 BLOB、簽章和交易詳細資料傳回給依賴方。

    為了充分運用 Android 保護確認機制提供的安全保證,依賴方必須在收到已簽署訊息後執行下列步驟:

    1. 檢查訊息的簽章及簽署金鑰的認證憑證鏈。
    2. 檢查認證憑證是否已設定 TRUSTED_CONFIRMATION_REQUIRED 標記,這表示簽署金鑰需要受信任的使用者確認。如果簽署金鑰是 RSA 金鑰,請確認該金鑰沒有 PURPOSE_ENCRYPTPURPOSE_DECRYPT 屬性。
    3. 檢查 extraData,確定此確認訊息屬於新的要求,且尚未處理。這個步驟可以防範重送攻擊。
    4. 剖析 promptText,取得已確認動作或要求的相關資訊。請注意,promptText 是使用者實際確認訊息的唯一部分。依賴方不得假設 extraData 中包含的已確認資料與 promptText 相對應。
  5. 新增類似下列程式碼片段中的邏輯,以顯示對話方塊本身:

    Kotlin

    // This data structure varies by app type. This is an example.
      data class ConfirmationPromptData(val sender: String,
              val receiver: String, val amount: String)
    
      val myExtraData: ByteArray = byteArrayOf()
      val myDialogData = ConfirmationPromptData("Ashlyn", "Jordan", "$500")
      val threadReceivingCallback = Executor { runnable -> runnable.run() }
      val callback = MyConfirmationCallback()
    
      val dialog = ConfirmationPrompt.Builder(context)
              .setPromptText("${myDialogData.sender}, send
                              ${myDialogData.amount} to
                              ${myDialogData.receiver}?")
              .setExtraData(myExtraData)
              .build()
      dialog.presentPrompt(threadReceivingCallback, callback)

    Java

      // This data structure varies by app type. This is an example.
      class ConfirmationPromptData {
          String sender, receiver, amount;
          ConfirmationPromptData(String sender, String receiver, String amount) {
              this.sender = sender;
              this.receiver = receiver;
              this.amount = amount;
          }
      };
      final int MY_EXTRA_DATA_LENGTH = 100;
      byte[] myExtraData = new byte[MY_EXTRA_DATA_LENGTH];
      ConfirmationPromptData myDialogData = new ConfirmationPromptData("Ashlyn", "Jordan", "$500");
      Executor threadReceivingCallback = Runnable::run;
      MyConfirmationCallback callback = new MyConfirmationCallback();
      ConfirmationPrompt dialog = (new ConfirmationPrompt.Builder(getApplicationContext()))
              .setPromptText("${myDialogData.sender}, send ${myDialogData.amount} to ${myDialogData.receiver}?")
              .setExtraData(myExtraData)
              .build();
      dialog.presentPrompt(threadReceivingCallback, callback);

其他資源

如要進一步瞭解 Android 保護確認,請參閱下列資源。

網誌