Android 보안 확인

사용자가 결제와 같은 민감한 트랜잭션을 시작할 때 사용자의 의도를 확인할 수 있도록 Android 9(API 수준 28) 이상을 실행하는 지원 기기에서는 Android 보안 확인을 사용할 수 있습니다. 이 워크플로를 사용하면 앱에서 사용자에게 민감한 트랜잭션을 완료할 의도를 재확인하는 간단한 문장을 승인할지 묻는 메시지를 표시합니다.

사용자가 이 문장을 수락하면 앱에서는 Android 키 저장소의 키를 사용하여 대화상자에 표시된 메시지에 서명할 수 있습니다. 이 서명은 강한 확신을 가지고 사용자가 문장을 보고 그 내용에 동의했음을 나타냅니다.

주의: Android 보안 확인은 사용자를 위한 보안 정보 채널을 제공하지 않습니다. 앱은 Android 플랫폼이 제공하는 수준을 넘어서는 그 어떤 기밀성 보장도 가정할 수 없습니다. 특히, 통상적으로 사용자 기기에 표시하지 않을 민감한 정보를 표시하려는 목적으로 이 워크플로를 사용해서는 안 됩니다.

사용자가 메시지를 확인하면 메시지의 무결성이 보장되지만, 앱은 여전히 전송 중 데이터 암호화를 사용하여, 서명된 메시지의 기밀성을 보호해야 합니다.

앱에서 높은 수준의 사용자 확인 지원을 제공하려면 다음 단계를 완료하세요.

  1. KeyGenParameterSpec.Builder 클래스를 사용하여 비대칭 서명 키를 생성합니다. 키를 생성할 때 truesetUserConfirmationRequired()로 전달합니다. 또한, setAttestationChallenge()를 호출하여 신뢰 당사자가 제공하는 적절한 챌린지 값을 전달합니다.

  2. 적절한 신뢰 당사자에 새로 생성된 키와 키의 증명 인증서를 등록합니다.

  3. 트랜잭션 세부정보를 서버로 전송하여 서버에서 추가 데이터의 바이너리 대형 객체(BLOB)를 생성하여 반환하도록 합니다. 추가 데이터에는 확인해야 할 데이터나 파싱 힌트(예: 메시지 문자열의 언어)가 포함될 수 있습니다.

    좀 더 안전하게 구현하려면 재생 공격으로부터 보호하고 트랜잭션의 모호성을 없애기 위해 암호화 nonce를 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은 다른 세부정보 중에서도 특히 사용자가 확인한 메시지 텍스트와 개발자가 ConfirmationPrompt 빌더로 전달한 추가 데이터가 포함되어 있는 CBOR 데이터 구조입니다. 이전에 만든 키를 사용하여 dataThatWasConfirmed BLOB에 서명한 후 이 BLOB을 서명 및 트랜잭션 세부정보와 함께 신뢰 당사자에게 다시 전달합니다.

    Android 보안 확인에서 제공하는 보안 보증을 최대한 활용하려면 신뢰 당사자는 서명된 메시지를 받으면 다음 단계를 실행해야 합니다.

    1. 메시지의 서명 및 서명 키의 증명 인증서 체인을 확인합니다.
    2. 증명 인증서에 TRUSTED_CONFIRMATION_REQUIRED 플래그가 설정되어 있는지 확인합니다. 이는 서명 키에 신뢰할 수 있는 사용자 확인이 필요함을 나타냅니다. 서명 키가 RSA 키인 경우 PURPOSE_ENCRYPT 또는 PURPOSE_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 보안 확인에 관한 자세한 내용은 다음 리소스를 참고하세요.

블로그