Confirmación de protección de Android

Para ayudarte a confirmar las intenciones de los usuarios cuando inician una transacción sensible, como realizar un pago, los dispositivos compatibles que ejecutan Android 9 (nivel de API 28) o versiones posteriores te permiten usar la Confirmación de protección de Android. Cuando se usa este flujo de trabajo, tu app muestra un mensaje al usuario en el que se le solicita que apruebe una breve declaración que reafirme su intención de completar la transacción sensible.

Si el usuario acepta la declaración, tu app podrá usar la clave de Android Keystore para firmar el mensaje que se mostró en el diálogo. La firma indica, con un alto grado de confianza, que el usuario vio la declaración y la aceptó.

Precaución: La Confirmación de protección de Android no proporciona un canal de información seguro para el usuario. Tu app no puede asumir garantías de confidencialidad más allá de las que ofrece la plataforma de Android. En especial, no uses este flujo de trabajo para mostrar información sensible que no mostrarías normalmente en el dispositivo del usuario.

Una vez que el usuario confirma el mensaje, se garantiza la integridad del mensaje, pero tu app debe seguir usando la encriptación de datos en tránsito para proteger la confidencialidad del mensaje firmado.

Para admitir la confirmación de usuarios altamente confiables en tu app, completa estos pasos:

  1. Genera una clave de firma asimétrica con la clase KeyGenParameterSpec.Builder. Cuando crees la clave, pasa true a setUserConfirmationRequired(). Además, llama a setAttestationChallenge() y pasa un valor de comprobación adecuado proporcionado por el usuario de confianza.

  2. Inscribe la nueva clave generada y la certificación de tu clave con el usuario de confianza adecuado.

  3. Envía los detalles de la transacción a tu servidor y haz que genere y muestre un objeto binario grande (BLOB) de datos adicionales. Estos datos pueden incluir información que aún se debe confirmar o sugerencias de análisis, como la configuración regional de la string de solicitud.

    Para una implementación más segura, el BLOB debe contener un nonce criptográfico para tener protección contra ataques de reproducción y desambiguar transacciones.

  4. Configura el objeto ConfirmationCallback que informa a tu app una vez que el usuario acepte la solicitud mostrada en un diálogo de confirmación.

    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.
      }
    }

    Si el usuario acepta el diálogo, se llama a la devolución de llamada onConfirmed(). El BLOB dataThatWasConfirmed es una estructura de datos CBOR que contiene, entre otros detalles, el texto del mensaje que el usuario vio, así como los datos adicionales que pasó al compilador ConfirmationPrompt. Usa la clave creada anteriormente para firmar el BLOB dataThatWasConfirmed y, luego, pásalo al usuario de confianza junto con los detalles de la firma y la transacción.

    Para aprovechar por completo la garantía de seguridad que ofrece la Confirmación de protección de Android, el usuario de confianza debe llevar a cabo los siguientes pasos cuando reciba un mensaje firmado:

    1. Controla la firma en el mensaje, así como la cadena del certificado de certificación de la clave de firma.
    2. Controla que el certificado de atestación tenga configurado el indicador TRUSTED_CONFIRMATION_REQUIRED, que indique que la clave de firma requiere la confirmación de un usuario de confianza. Si la clave de firma es una clave RSA, comprueba que no tenga la propiedad PURPOSE_ENCRYPT o PURPOSE_DECRYPT.
    3. Controla extraData para asegurarte de que este mensaje de confirmación pertenezca a una solicitud nueva y que no se haya procesado aún. Este paso protege contra ataques de reproducción.
    4. Analiza promptText con el objetivo de obtener información sobre la acción o solicitud confirmada. Recuerda que promptText es la única parte del mensaje que el usuario realmente confirmó. El usuario de confianza jamás debe suponer que los datos por confirmar que se incluyen en extraData corresponden a promptText.
  5. Agrega una lógica similar a la que se muestra en el siguiente fragmento de código para mostrar el diálogo mismo:

    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);

Recursos adicionales

Para obtener más información sobre la Confirmación de protección de Android, consulta los siguientes recursos.

Blogs