Android 受保护确认

为了帮助您在用户发起敏感交易(例如付款)时确认用户的意图,运行 Android 9(API 级别 28)或更高版本的受支持设备允许您使用 Android 受保护确认。使用此工作流程时,您的应用会向用户显示一个提示,要求他们批准一个简短的声明,以重申他们完成敏感交易的意图。

如果用户接受该声明,您的应用可以使用来自 Android 密钥库的密钥对显示在对话框中的消息进行签名。签名以非常高的置信度表明用户已看到该声明并同意了该声明。

注意:Android 受保护确认不为用户提供安全的信息通道。您的应用不能假设超出 Android 平台提供的任何机密性保证。特别是,不要使用此工作流程来显示您通常不会在用户设备上显示的敏感信息。

用户确认消息后,消息的完整性将得到保证,但您的应用仍必须使用数据传输加密来保护签名消息的机密性。

要为您的应用提供对高保证用户确认的支持,请完成以下步骤

  1. 使用KeyGenParameterSpec.Builder生成非对称签名密钥。创建密钥时,将true传递到setUserConfirmationRequired()。此外,调用setAttestationChallenge(),并传递依赖方提供的合适的挑战值。

  2. 将新生成的密钥和密钥的证明证书注册到相应的依赖方。

  3. 将交易详细信息发送到您的服务器,并使其生成并返回额外数据的二进制大对象 (BLOB)。额外数据可能包括要确认的数据或解析提示,例如提示字符串的语言环境。

    为了实现更安全,BLOB 必须包含一个加密随机数,以防止重放攻击并消除交易的歧义。

  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 受保护确认的更多信息,请查阅以下资源。

博客