אישור מאובטח של Android

כדי לעזור לכם לאשר את כוונות המשתמשים כשהם יוזמים עסקה רגישה, כמו ביצוע תשלום, אפשר להשתמש ב-Android Protected Verification באמצעות מכשירים נתמכים עם Android 9 (רמת API 28) ואילך. כשמשתמשים בתהליך העבודה הזה, באפליקציה מוצגת בקשה למשתמש לאשר הצהרה קצרה שמאשרת מחדש את כוונתו להשלים את העסקה הרגישה.

אם המשתמש יאשר את ההצהרה, האפליקציה תוכל להשתמש במפתח מ-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.
      }
    }

    אם המשתמש יאשר את תיבת הדו-שיח, תתבצע קריאה חוזרת (callback) של onConfirmed(). ה-BLOB של dataThatWasConfirmed הוא מבנה נתונים מסוג CBOR שמכיל, בין היתר, את הטקסט של ההנחיה שהמשתמש ראה ואת הנתונים הנוספים שהעברתם ל-builder של ConfirmationPrompt. משתמשים במפתח שנוצר קודם כדי לחתום על ה-BLOB של dataThatWasConfirmed, ואז מעבירים את ה-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, אפשר לעיין במקורות המידע הבאים.

בלוגים