In diesem Artikel implementieren wir die sogenannte
Host-basierte Kartenemulation (HCE, Bankkartenemulation am Telefon). Das Netzwerk enthält viele detaillierte Beschreibungen dieser Technologie. Hier habe ich mich darauf konzentriert, funktionierende Emulator- und Reader-Anwendungen zu erhalten und eine Reihe praktischer Probleme zu lösen. Ja, Sie benötigen 2 Geräte mit NFC.
Es gibt viele Nutzungsszenarien:
Passsystem , Kundenkarten, Transportkarten, zusätzliche Informationen zu Exponaten im Museum,
Passwort-Manager .
In diesem Fall wird die Anwendung auf dem Telefon, die die Karte emuliert, möglicherweise gestartet oder nicht, und der Bildschirm Ihres Telefons ist möglicherweise gesperrt.
Für Xamarin Android gibt es vorgefertigte Beispiele für
einen Kartenemulator und
-leser .
Versuchen wir, anhand dieser Beispiele zwei Xamarin Forms-Anwendungen, einen Emulator und einen Reader zu erstellen und die folgenden Probleme darin zu lösen:
- Daten vom Emulator auf dem Lesebildschirm anzeigen
- Zeigen Sie die Daten des Lesegeräts auf dem Emulatorbildschirm an
- Der Emulator sollte mit einer unveröffentlichten Anwendung und einem gesperrten Bildschirm arbeiten
- Emulatoreinstellungen steuern
- Starten Sie die Emulatoranwendung, wenn ein Lesegerät erkannt wird
- Überprüfen Sie den Status des NFC-Adapters und wechseln Sie zu den NFC-Einstellungen
In diesem Artikel geht es um Android. Wenn Sie also eine Anwendung auch für iOS haben, sollte es eine separate Implementierung geben.
Das Minimum der Theorie.
Wie in der
Android-Dokumentation beschrieben , wurde ab Version 4.4 (Kitkat) die Möglichkeit hinzugefügt, ISO-DEP-Karten zu emulieren und APDU-Befehle zu verarbeiten.
Die Kartenemulation basiert auf Android-Diensten, die als „HCE-Dienste“ bezeichnet werden.
Wenn ein Benutzer ein Gerät an einen NFC-Leser anschließt, muss der Android verstehen, mit welchem HCE-Dienst der Leser eine Verbindung herstellen möchte. ISO / IEC 7816-4 beschreibt eine Anwendungsauswahlmethode basierend auf der Anwendungs-ID (AID).
Wenn es interessant ist, in die schöne Welt der Byte-Arrays einzutauchen, finden Sie
hier und
hier detailliertere Informationen zu APDU-Befehlen. In diesem Artikel werden nur einige der zum Datenaustausch erforderlichen Befehle verwendet.
Leseranwendung
Beginnen wir mit dem Leser, weil es ist einfacher.
Wir erstellen in Visual Studio ein neues Projekt wie "Mobile App (Xamarin.Forms)", wählen dann die Vorlage "Leer" aus und lassen nur das Häkchen "Android" im Abschnitt "Plattformen".
Im Android-Projekt müssen Sie Folgendes tun:
- Die CardReader-Klasse - enthält mehrere Konstanten und die OnTagDiscovered-Methode
- MainActivity - Initialisierung der CardReader-Klasse sowie der OnPause- und OnResume-Methoden zum Ein- und Ausschalten des Readers beim Minimieren der Anwendung
- AndroidManifest.xml - Berechtigungen für nfc
Und im plattformübergreifenden Projekt in der Datei App.xaml.cs:
- Methode zum Anzeigen einer Nachricht für den Benutzer
CardReader-Klasse
using Android.Nfc; using Android.Nfc.Tech; using System; using System.Linq; using System.Text; namespace ApduServiceReaderApp.Droid.Services { public class CardReader : Java.Lang.Object, NfcAdapter.IReaderCallback {
Im Lesemodus des NFC-Adapters wird die OnTagDiscovered-Methode aufgerufen, wenn die Karte erkannt wird. Darin ist IsoDep ein Objekt, mit dem wir Befehle mit der Karte austauschen (isoDep.Transceive (Befehl)). Befehle sind Byte-Arrays.
Der Code zeigt, dass wir dem Emulator eine Sequenz senden, die aus dem SELECT_APDU_HEADER-Header, der Länge unserer AID in Bytes und der AID selbst besteht:
0 164 4 0 // SELECT_APDU_HEADER 5 // AID 241 35 69 103 137 // SAMPLE_LOYALTY_CARD_AID (F1 23 45 67 89)
MainActivity Reader
Hier müssen Sie das Leserfeld deklarieren:
public CardReader cardReader;
und zwei Hilfsmethoden:
private void EnableReaderMode() { var nfc = NfcAdapter.GetDefaultAdapter(this); if (nfc != null) nfc.EnableReaderMode(this, cardReader, READER_FLAGS, null); } private void DisableReaderMode() { var nfc = NfcAdapter.GetDefaultAdapter(this); if (nfc != null) nfc.DisableReaderMode(this); }
Initialisieren Sie in der OnCreate () -Methode den Reader und aktivieren Sie den Lesemodus:
protected override void OnCreate(Bundle savedInstanceState) { ... cardReader = new CardReader(); EnableReaderMode(); LoadApplication(new App()); }
Aktivieren / Deaktivieren Sie auch den Lesemodus, wenn Sie die Anwendung minimieren / öffnen:
protected override void OnPause() { base.OnPause(); DisableReaderMode(); } protected override void OnResume() { base.OnResume(); EnableReaderMode(); }
App.xaml.cs
Statische Methode zum Anzeigen einer Nachricht:
public static async Task DisplayAlertAsync(string msg) => await Device.InvokeOnMainThreadAsync(async () => await Current.MainPage.DisplayAlert("message from service", msg, "ok"));
AndroidManifest.xml
Die
Android-Dokumentation besagt, dass Sie diese Elemente in AndroidManifest.xml deklarieren müssen, um nfc in Ihrer Anwendung zu verwenden und korrekt damit zu arbeiten:
<uses-permission android:name="android.permission.NFC" /> <uses-sdk android:minSdkVersion="10"/> <uses-sdk android:minSdkVersion="14"/> <uses-feature android:name="android.hardware.nfc" android:required="true" />
Wenn Ihre Anwendung nfc verwenden kann, dies jedoch keine erforderliche Funktion ist, können Sie das Verwendungsfeature-Element überspringen und die Verfügbarkeit von nfc während des Betriebs überprüfen.
Das ist alles für den Leser.
Emulator-Anwendung
Erstellen Sie erneut ein neues Projekt in Visual Studio wie "Mobile App (Xamarin.Forms)", wählen Sie dann die Vorlage "Leer" aus und lassen Sie nur das Häkchen "Android" im Abschnitt "Plattformen".
Gehen Sie in einem Android-Projekt wie folgt vor:
- CardService-Klasse - hier benötigen Sie Konstanten und die ProcessCommandApdu () -Methode sowie die SendMessageToActivity () -Methode
- Beschreibung des Dienstes in aid_list.xml
- Mechanismus zum Senden von Nachrichten in MainActivity
- Anwendungsstart (falls erforderlich)
- AndroidManifest.xml - Berechtigungen für nfc
Und im plattformübergreifenden Projekt in der Datei App.xaml.cs:
- Methode zum Anzeigen einer Nachricht für den Benutzer
CardService-Klasse
using Android.App; using Android.Content; using Android.Nfc.CardEmulators; using Android.OS; using System; using System.Linq; using System.Text; namespace ApduServiceCardApp.Droid.Services { [Service(Exported = true, Enabled = true, Permission = "android.permission.BIND_NFC_SERVICE"), IntentFilter(new[] { "android.nfc.cardemulation.action.HOST_APDU_SERVICE" }, Categories = new[] { "android.intent.category.DEFAULT" }), MetaData("android.nfc.cardemulation.host_apdu_service", Resource = "@xml/aid_list")] public class CardService : HostApduService {
Nach Erhalt des APDU-Befehls vom Reader wird die ProcessCommandApdu-Methode aufgerufen und der Befehl als Array von Bytes an ihn übertragen.
Zuerst überprüfen wir, ob die Nachricht mit SELECT_APDU_HEADER beginnt, und verfassen in diesem Fall eine Antwort an den Leser. In der Realität kann ein Austausch in mehreren Schritten erfolgen, Frage-Antwort-Frage-Antwort usw.
Vor der Klasse beschreibt das Service-Attribut die Parameter des Android-Dienstes. Beim Erstellen konvertiert xamarin diese Beschreibung in ein solches Element in AndroidManifest.xml:
<service name='md51c8b1c564e9c74403ac6103c28fa46ff.CardService' permission='android.permission.BIND_NFC_SERVICE' enabled='true' exported='true'> <meta-data name='android.nfc.cardemulation.host_apdu_service' resource='@res/0x7F100000'> </meta-data> <intent-filter> <action name='android.nfc.cardemulation.action.HOST_APDU_SERVICE'> </action> <category name='android.intent.category.DEFAULT'> </category> </intent-filter> </service>
Beschreibung des Dienstes in aid_list.xml
Erstellen Sie im XML-Ordner die Datei aid_list.xml:
<?xml version="1.0" encoding="utf-8"?> <host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android" android:description="@string/service_name" android:requireDeviceUnlock="false"> <aid-group android:description="@string/card_title" android:category="other"> <aid-filter android:name="F123456789"/> </aid-group> </host-apdu-service>
Ein Verweis darauf befindet sich im Service-Attribut in der CardService-Klasse - Resource = "@ xml / aid_list".
Hier legen wir die AID unserer Anwendung fest, nach der der Leser darauf zugreifen wird, und das Attribut requireDeviceUnlock = "false", damit die Karte mit einem entsperrten Bildschirm gelesen wird.
Der Code enthält zwei Konstanten:
@string/service_name
und
@string/card_title
. Sie werden in der Datei values / strings.xml deklariert:
<resources> <string name="card_title">My Loyalty Card</string> <string name="service_name">My Company</string> </resources>
Mechanismus zum Senden von Nachrichten:
Der Dienst hat keine Links zu MainActivity, die zum Zeitpunkt des Empfangs des APDU-Befehls möglicherweise nicht einmal gestartet werden. Daher senden wir Nachrichten von CardService mit BroadcastReceiver wie folgt an MainActivity:
Methode zum Senden einer Nachricht von CardService:
private void SendMessageToActivity(string msg) { Intent intent = new Intent("MSG_NAME"); intent.PutExtra("MSG_DATA", msg); SendBroadcast(intent); }
Eine Nachricht erhalten:
Erstellen Sie die MessageReceiver-Klasse:
using Android.Content; namespace ApduServiceCardApp.Droid.Services { public class MessageReceiver : BroadcastReceiver { public override async void OnReceive(Context context, Intent intent) { var message = intent.GetStringExtra("MSG_DATA"); await App.DisplayAlertAsync(message); } } }
Registrieren Sie MessageReceiver in MainActivity:
protected override void OnCreate(Bundle savedInstanceState) { ... var receiver = new MessageReceiver(); RegisterReceiver(receiver, new IntentFilter("MSG_NAME")); LoadApplication(new App()); }
App.xaml.cs
Das gleiche wie bei der Lesemethode zum Anzeigen einer Nachricht:
public static async Task DisplayAlertAsync(string msg) => await Device.InvokeOnMainThreadAsync(async () => await Current.MainPage.DisplayAlert("message from service", msg, "ok"));
AndroidManifest.xml
<uses-feature android:name="android.hardware.nfc.hce" android:required="true" /> <uses-feature android:name="FEATURE_NFC_HOST_CARD_EMULATION"/> <uses-permission android:name="android.permission.NFC" /> <uses-permission android:name="android.permission.BIND_NFC_SERVICE" /> <uses-sdk android:minSdkVersion="10"/> 14
Im Moment haben wir bereits folgende Funktionen:
- Daten vom Emulator auf dem Lesebildschirm anzeigen
- Zeigen Sie die Daten des Lesegeräts auf dem Emulatorbildschirm an
- Der Emulator sollte mit nicht laufender Anwendung und ausgeschaltetem Bildschirm funktionieren.
Weiter.
Emulatorsteuerung
Ich werde die Einstellungen mit Xamarin.Essentials speichern.
Gehen wir folgendermaßen vor: Wenn wir die Emulatoranwendung neu starten, aktualisieren wir die Einstellung:
Xamarin.Essentials.Preferences.Set("key1", Guid.NewGuid().ToString());
und in der ProcessCommandApdu-Methode nehmen wir diesen Wert jedes Mal wieder an:
var messageToReader = $"Hello Reader! - {Xamarin.Essentials.Preferences.Get("key1", "key1 not found")}";
Jedes Mal, wenn Sie die Emulatoranwendung neu starten (nicht minimieren), sehen wir eine neue Anleitung, zum Beispiel:
Hello Reader! - 76324a99-b5c3-46bc-8678-5650dab0529d
Schalten Sie den Emulator über die Einstellungen ein / aus:
Xamarin.Essentials.Preferences.Set("IsEnabled", false);
und am Anfang der ProcessCommandApdu-Methode hinzufügen:
var IsEnabled = Xamarin.Essentials.Preferences.Get("IsEnabled", false); if (!IsEnabled) return UNKNOWN_CMD_SW;
Dies ist ein einfacher Weg, aber es gibt noch
andere .
Ausführen der Emulatoranwendung, wenn ein Lesegerät erkannt wird
Wenn Sie nur die Emulatoranwendung öffnen müssen, fügen Sie die Zeile in die ProcessCommandApdu-Methode ein:
StartActivity(typeof(MainActivity));
Wenn Sie Parameter an die Anwendung übergeben müssen, gehen Sie wie folgt vor:
var activity = new Intent(this, typeof(MainActivity)); intent.PutExtra("MSG_DATA", "data for application"); this.StartActivity(activity);
Sie können die übergebenen Parameter in der MainActivity-Klasse in der OnCreate-Methode lesen:
... LoadApplication(new App()); if (Intent.Extras != null) { var message = Intent.Extras.GetString("MSG_DATA"); await App.DisplayAlertAsync(message); }
Überprüfen des Status des NFC-Adapters und Umschalten auf die NFC-Einstellungen
Dieser Abschnitt gilt sowohl für den Leser als auch für den Emulator.
Erstellen Sie NfcHelper im Android-Projekt und verwenden Sie DependencyService, um über den MainPage-Seitencode darauf zuzugreifen.
using Android.App; using Android.Content; using Android.Nfc; using ApduServiceCardApp.Services; using Xamarin.Forms; [assembly: Dependency(typeof(ApduServiceCardApp.Droid.Services.NfcHelper))] namespace ApduServiceCardApp.Droid.Services { public class NfcHelper : INfcHelper { public NfcAdapterStatus GetNfcAdapterStatus() { var adapter = NfcAdapter.GetDefaultAdapter(Forms.Context as Activity); return adapter == null ? NfcAdapterStatus.NoAdapter : adapter.IsEnabled ? NfcAdapterStatus.Enabled : NfcAdapterStatus.Disabled; } public void GoToNFCSettings() { var intent = new Intent(Android.Provider.Settings.ActionNfcSettings); intent.AddFlags(ActivityFlags.NewTask); Android.App.Application.Context.StartActivity(intent); } } }
Fügen Sie nun im plattformübergreifenden Projekt die INfcHelper-Schnittstelle hinzu:
namespace ApduServiceCardApp.Services { public interface INfcHelper { NfcAdapterStatus GetNfcAdapterStatus(); void GoToNFCSettings(); } public enum NfcAdapterStatus { Enabled, Disabled, NoAdapter } }
und verwenden Sie all dies im Code von MainPage.xaml.cs:
protected override async void OnAppearing() { base.OnAppearing(); await CheckNfc(); } private async Task CheckNfc() { var nfcHelper = DependencyService.Get<INfcHelper>(); var status = nfcHelper.GetNfcAdapterStatus(); switch (status) { case NfcAdapterStatus.Enabled: default: await App.DisplayAlertAsync("nfc enabled!"); break; case NfcAdapterStatus.Disabled: nfcHelper.GoToNFCSettings(); break; case NfcAdapterStatus.NoAdapter: await App.DisplayAlertAsync("no nfc adapter found!"); break; } }
GitHub Links
EmulatorLeser