So erstellen Sie einen einfachen Smartcard-Emulator und -Leser für Android

Hallo! Ich bin Senior Software Engineer bei TWG. Zuvor arbeitete ich bei Gemalto, dem weltweit größten Hersteller von Smartcards.

Einführung:

Smartcards verfügen über eine Vielzahl nützlicher Funktionen - von ID-Karten bis zu Repository-Karten - und bieten ein flexibles, sicheres und portables Tool für die Verwaltung aller Arten von Daten.

Ziel dieses Artikels ist es, mit dem NFC des Android-Telefons eine Smartcard zu emulieren und zu lesen. Zuvor ist es jedoch sehr wichtig zu verstehen, wie Smartcards funktionieren und mit Lesern kommunizieren.

Was du brauchst:

  • Grundkenntnisse in der Android-Entwicklung
  • Grundlegendes Kotlin-Wissen, wie wir es anhand der Beispiele in Kotlin machen werden
  • Ein Android-Handy A mit NFC, das als Kartenemulator für unsere Tests fungiert
  • Ein Android-Telefon B mit NFC, das als Kartenleser fungiert
  • Optional: Ein ICAO-kompatibler ePass. Sie können überprüfen, ob Ihr Reisepass ICAO-konform ist und einen elektronischen Chip enthält, indem Sie auf die Vorderseite schauen und das folgende Zeichen sehen:
Chip-Zeichen auf der Vorderseite von ePassports

Chipkarten:

Smartcards sind in jeder Hinsicht Mini-Computer, die einen Mikroprozessor und einen Speicher enthalten. Genau wie bei einem normalen Computer können Betriebssysteme und Anwendungen (sogenannte Applets) ausgeführt werden, die sowohl komplexe Vorgänge ausführen als auch einen sicheren Zugriff auf Daten ermöglichen.

Typen:

Smartcards können "Kontakt", "Kontaktlos" oder beides sein (Dual Interface). Kontaktkarten müssen in direktem Kontakt mit dem Lesegerät stehen, um mit Strom versorgt und für die Kommunikation bereit zu sein. Kontaktlose Karten können jedoch mit 13,56-MHz-Funkwellen aus einer maximalen Entfernung von 10 cm (4 in) kommuniziert werden. Sie enthalten eine Antenne, die diese Art der Kommunikation ermöglicht:

Dual-Interface-Chipkarte

Jedes Telefon verfügt mindestens über einen Kontakt-Smartcard-Leser, mit dem die SIM-Karte gelesen werden kann. Die meisten Android-Handys verfügen über einen kontaktlosen Chipkartenleser in Form des NFC-Lesers, dessen Verwendung wir anhand von Beispielen erläutern.

Datenstruktur:

Die ISO7816 regelt, wie die Daten in Smartcards strukturiert werden sollen. Es unterstützt die folgende Struktur:

DF / Dedicated Files: Sie können sich diese als Verzeichnisse vorstellen, die Dateien enthalten. Es muss mindestens ein Root-DF vorhanden sein, der als Master File (MF) bezeichnet wird.

EF- / Elementardateien: Dies sind Dateien mit Binärdaten, die von einem externen Lesegerät gelesen oder beschrieben werden können.

Datenstruktur wie in ISO7816 beschrieben
Karten können manchmal mehr als ein Applet enthalten. Als Erstes müssen Sie das Applet auswählen, mit dem Sie über seine AID kommunizieren möchten.

Kommunikationsprotokoll:

Die ISO7816 definiert auch das Kommunikationsprotokoll mit Smartcards (kontaktbehaftet und kontaktlos). Um mit der Karte zu kommunizieren, muss ein Lesegerät einen „APDU-Befehl“ (Application Protocol Data Unit Command) an die Karte senden, der mit einer „APDU-Antwort“ antwortet.

APDU-Befehle sind Byte-Arrays, die Folgendes enthalten:

Wichtig: In den folgenden Abschnitten wird in hexadezimalen Darstellungen auf Bytes verwiesen, z. B. "A0", "FE", "00".

CLA: Das Class-Byte gibt an, inwieweit der Befehl der ISO7816 entspricht, und wenn ja, welche Art von „Secure Messaging“ verwendet wird. Zur Vereinfachung verwenden wir dieses Byte in unserem Beispiel nicht und übergeben die ganze Zeit '00'.

INS: Das Instruction-Byte wird verwendet, um anzugeben, welche Methode ausgeführt werden soll. Dies können verschiedene Methoden sein, z. B .: A4, um eine Datei oder ein Applet auszuwählen, B0, um eine Binärdatei zu lesen, D0, um eine Binärdatei zu schreiben … (Siehe vollständige Anleitung hier)

P1 & P2: Diese beiden Parameterbytes werden zur weiteren Anpassung des Befehls verwendet. Sie hängen davon ab, welche benutzerdefinierten Befehle auf der Karte angegeben sind. Klicken Sie hier für die Liste der möglichen Fälle

Lc: ist die Länge der zu sendenden Daten

Daten: sind die tatsächlichen Daten für die Anweisung

Le: ist die Länge der erwarteten Antwort

Ein Beispiel für die Auswahl einer Datei oder eines Applets mit der ID "A0000002471001" lautet wie folgt: "00 A4 0400 07 A0000002471001 00"

Sobald die Karte den Befehl erhalten hat, antwortet sie wie folgt mit einer APDU-Antwort:

Datenfeld: ist der Hauptteil der Antwort

SW1 & SW2: sind die Statusbytes, sie sind getrennt, da manchmal das erste Byte den aktuellen Status angibt und das zweite Byte weitere Informationen zu diesem Status enthält. Wenn wir zum Beispiel einen Befehl verwenden, um eine PIN mit der falschen PIN zu bestätigen, gibt die Karte den Status "63 CX" zurück, wobei X die Anzahl der verbleibenden Versuche ist. Auf diese Weise kann die Reader-App problemlos die erste überprüfen Byte für den Status und Sekunde für die Anzahl der verbleibenden Versuche.

Wenn der Befehl erfolgreich ausgeführt wurde, erhalten wir normalerweise den Status "90 00". (Die vollständige Liste der möglichen Antworten finden Sie hier.)

Mit den obigen Informationen können wir nun einen Smartcard-Emulator und einen Smartcard-Leser in Android erstellen.

Stellen Sie sicher, dass Sie Android Studio 3.1 Canary oder höher ausführen, um die Kotlin-Entwicklung zu unterstützen

Hostbasierte Kartenemulation (HCE):

Ab Android 4.4 haben wir die Möglichkeit, einen Kartenemulationsdienst zu erstellen, der als Smart Card fungiert, indem er APDU-Befehle entgegennimmt und APDU-Antworten zurückgibt. Dazu erstellen wir ein neues Android-Projekt: Neu → Neues Projekt

Stellen Sie sicher, dass Sie das Kontrollkästchen "Kotlin-Unterstützung einbeziehen" aktivieren und auf "Weiter" klicken.

Stellen Sie sicher, dass Sie API 19 oder höher auswählen, da die Kartenemulation nur ab Android 4.4 unterstützt wird. Klicken Sie auf "Weiter", wählen Sie "Leere Aktivität" und "Fertig stellen".
Der einzige Grund, warum wir eine Aktivität hinzufügen, ist, die Dinge zu vereinfachen. Unser Kartenemulator wird die ganze Zeit im Hintergrund als Dienst ausgeführt, sodass eigentlich keine Aktivität erforderlich ist. Der Einfachheit halber verwenden wir jedoch eine .

Das erste, was wir hinzufügen werden, ist die Manifest-Berechtigungserklärung zur Verwendung von NFC. Fügen Sie in Ihrer "AndroidManifest.xml" Folgendes in das "Manifest" -Tag vor das "Application-Tag" ein:

Als Nächstes fügen wir die Anforderung für die HCE-Hardware hinzu, sodass die App nur auf Telefonen installiert wird, auf denen HCE ausgeführt werden kann. Fügen Sie direkt unter der vorherigen Zeile die folgende Zeile hinzu:

Als nächstes deklarieren wir unseren HCE-Service innerhalb des "application" -Tags:


    
        
    

    

In dieser Erklärung ist viel los, also lasst uns das durchgehen:

  • Name: ist der Name der Klasse, die die Service-Rückrufe implementiert (wir werden das in einer Minute erstellen).
  • Exportiert: Dies muss zutreffen, damit andere Anwendungen auf unseren Service zugreifen können. Wenn dies falsch ist, kann keine externe Anwendung mit dem Dienst interagieren, was wir nicht wollen.
  • Berechtigung: Der Dienst muss an den NFC-Dienst gebunden sein, um NFC verwenden zu können.
  • Absichtsfilter: Wenn das Android-System feststellt, dass ein externer Kartenleser versucht, eine Karte zu lesen, wird eine Aktion `HOST_APDU_SERVICE` ausgelöst. Unser Dienst, der sich für diese Aktion registriert hat, wird aufgerufen, und dann können wir tun, was wir wollen Service wird in Aktion gerufen.
  • Metadaten: Damit das System weiß, mit welchen Diensten basierend auf welcher AID der Leser zu kommunizieren versucht, müssen wir das Metadaten-Tag deklarieren und auf eine XML-Ressource verweisen.

Erstellen wir jetzt die "apduservice" -XML-Datei: Klicken Sie mit der rechten Maustaste auf den "res" -Ordner in Ihrem Projekt und wählen Sie "new" → "Directory". Nennen Sie ihn "xml". Erstellen Sie dann eine neue XML-Datei in diesem neuen Verzeichnis mit dem Namen "apduservice" und schreiben Sie Folgendes hinein:


    
        
    

Der wichtigste Teil hier ist der AID-Filter, der registriert, dass unser Dienst ausgelöst wird, wenn diese AID von einem Kartenleser ausgewählt wird.

Sie müssen die @string-Werte für die Beschreibungen erstellen

Jetzt erstellen wir unseren Service. Klicken Sie mit der rechten Maustaste auf Ihr Paket und wählen Sie Neu → Kotlin-Datei / Klasse

Wählen Sie "Klasse" und denselben Namen, den wir in das Manifest eingetragen haben. OK klicken.

Jetzt werden wir die abstrakte Klasse "HostApduService" erweitern und ihre abstrakten Methoden implementieren:

Klasse HostCardEmulatorService: HostApduService () {
    überschreibe Spaß onDeactivated (Grund: Int) {
        TODO ("nicht implementiert") // Um ​​den erstellten Body zu ändern
        // Funktionen verwenden File | Einstellungen | Dateivorlagen.
    }

    Überschreibe fun processCommandApdu (commandApdu: ByteArray ?,
                                    Extras: Bundle?): ByteArray {
        TODO ("nicht implementiert") // Um ​​den erstellten Body zu ändern
        // Funktionen verwenden File | Einstellungen | Dateivorlagen.
    }
}

Die Methode "onDeactiveted" wird aufgerufen, wenn eine andere AID ausgewählt wurde oder die NFC-Verbindung unterbrochen wurde.

Die Methode "processCommandApdu" wird jedes Mal aufgerufen, wenn ein Kartenleser einen APDU-Befehl sendet, der von unserem Manifest-Filter gefiltert wird.

Definieren wir vor der ersten Methode einige Konstanten, mit denen gearbeitet werden soll, und fügen Sie Folgendes hinzu:

Begleitobjekt {
    val TAG = "Hostkarten-Emulator"
    val STATUS_SUCCESS = "9000"
    val STATUS_FAILED = "6F00"
    val CLA_NOT_SUPPORTED = "6E00"
    val INS_NOT_SUPPORTED = "6D00"
    val AID = "A0000002471001"
    val SELECT_INS = "A4"
    val DEFAULT_CLA = "00"
    val MIN_APDU_LENGTH = 12
}

Schreiben Sie dies einfach in das Feld "onDeactivated":

Log.d (TAG, "Deaktiviert:" + Grund)

Bevor wir die processCommandApdu-Methode implementieren, benötigen wir einige Hilfsmethoden. Erstellen Sie eine neue Kotlin-Klasse mit dem Namen "Utils" und kopieren Sie die beiden folgenden (in Java statischen) Methoden:

Begleitobjekt {
    private val HEX_CHARS = "0123456789ABCDEF"
    Spaß hexStringToByteArray (Daten: String): ByteArray {

        val result = ByteArray (data.length / 2)

        für (i in 0 bis Datenlänge Schritt 2) {
            val firstIndex = HEX_CHARS.indexOf (data [i]);
            val secondIndex = HEX_CHARS.indexOf (data [i + 1]);

            val octet = firstIndex.shl (4) .or (secondIndex)
            result.set (i.shr (1), octet.toByte ())
        }

        Ergebnis zurückgeben
    }

    private val HEX_CHARS_ARRAY = "0123456789ABCDEF" .toCharArray ()
    fun toHex (byteArray: ByteArray): String {
        val result = StringBuffer ()

        byteArray.forEach {
            val octet = it.toInt ()
            val firstIndex = (octet und 0xF0) .ushr (4)
            val secondIndex = octet und 0x0F
            result.append (HEX_CHARS_ARRAY [firstIndex])
            result.append (HEX_CHARS_ARRAY [secondIndex])
        }

        return result.toString ()
    }
}

Diese Methoden dienen zur Konvertierung zwischen Byte-Arrays und hexadezimalen Zeichenfolgen.

Schreiben wir nun Folgendes in die Methode "processCommandApdu":

if (commandApdu == null) {
    Utils.hexStringToByteArray (STATUS_FAILED) zurückgeben
}

val hexCommandApdu = Utils.toHex (commandApdu)
if (hexCommandApdu.length 

Dies ist offensichtlich nur ein Modell, um unsere erste Emulation zu erstellen. Sie können dies beliebig anpassen, aber was ich hier erstellt habe, ist eine einfache Überprüfung auf Länge und CLA und INS, die nur dann eine erfolgreiche APDU (9000) zurückgibt, wenn wir die vordefinierte AID auswählen.

Android Card Reader mit NFC Beispiel:

Erstellen Sie wie beim vorherigen Projekt ein neues Projekt mit mindestens Android 4.4 SDK und Kotlin-Unterstützung mit einer leeren Aktivität.

In der `AndroidManifest.xml` deklarieren Sie die gleiche NFC-Berechtigung:

Deklarieren Sie auch die NFC-Anforderung:

Fügen Sie in Ihrem activity_main.xml-Layout einfach eine Textansicht hinzu, um die Kartenantworten mit anzuzeigen

Kopieren Sie die Klasse "Utils" aus dem vorherigen Projekt, da wir auch hier die gleichen Methoden anwenden werden.

Fügen Sie in Ihrer Aktivität neben der Erweiterung "AppCompatActivity ()" Folgendes hinzu:

Klasse MainActivity: AppCompatActivity (), NfcAdapter.ReaderCallback

Dadurch wird die "ReaderCallback" -Schnittstelle implementiert, und Sie müssen die "onTagDiscovered" -Methode implementieren.

Deklarieren Sie in Ihrer Aktivität die folgende Variable:

private var nfcAdapter: NfcAdapter? = null

Fügen Sie Ihrer onCreate-Methode Folgendes hinzu:

nfcAdapter = NfcAdapter.getDefaultAdapter (this);

Damit erhalten wir den Standard-NfcAdapter, den wir verwenden können.

Überschreiben Sie die Methode "onResume" wie folgt:

Spaß beim Überschreiben durch die Öffentlichkeit onResume () {
    super.onResume ()
    nfcAdapter? .enableReaderMode (this, this,
            NfcAdapter.FLAG_READER_NFC_A oder
                    NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK,
            Null)
}

Dadurch wird der Lesemodus aktiviert, während diese Aktivität ausgeführt wird. Stellen Sie beim Umgang mit einer Smart Card sicher, dass Sie die verwendete Technologie durchsuchen, damit Sie sie deklarieren. Meistens können Sie jedoch NFC_A verwenden. Die zweite Markierung ist vorhanden, sodass wir die NDEF-Schnittstellen überspringen. Dies bedeutet in unserem Fall, dass Android Beam nicht aufgerufen werden soll, wenn diese Aktivität ausgeführt wird. Andernfalls stört dies unseren Leser, da Android der NDEF Priorität einräumt Typ vor TECH-Typ (oder Smartcards im Allgemeinen)

Vergessen Sie nicht, die Methode "onPause ()" zu überschreiben und den NfcAdapter zu deaktivieren:

public override fun onPause () {
    super.onPause ()
    nfcAdapter? .disableReaderMode (this)
}

Als letztes müssen Sie APDU-Befehle senden, sobald eine Karte erkannt wurde. Schreiben Sie also in der Methode "onTagDiscovered" Folgendes:

Überschreibe den Spaß onTagDiscovered (tag: Tag?) {
    val isoDep = IsoDep.get (tag)
    isoDep.connect ()
    val response = isoDep.transceive (Utils.hexStringToByteArray (
            "00A4040007A0000002471001"))
    runOnUiThread {textView.append ("\ nCard Response:"
            + Utils.toHex (Antwort))}
    isoDep.close ()
}

Wenn Sie nun die erste App auf Telefon A und die zweite App auf Telefon B ausführen, setzen Sie die Telefone aufeinander, sodass sich ihre NFCs gegenüberstehen. Sobald Telefon B dies erkennt, geschieht Folgendes:

Interaktionsdiagramm

Telefon B sendet den obigen APDU-Befehl an das HCE von Telefon A, das ihn an unseren Service weiterleitet und einen 9000-Status zurückgibt. Sie können den Befehl absichtlich ändern, um einen der Fehler anzuzeigen, die wir in den Dienst eingefügt haben, z. B. die Verwendung eines anderen CLA oder INS.

Hier ist der gute Teil. Wenn Sie Telefon B mit unserer Kartenleser-App nehmen und Ihren ePassport (wo sich der Chip befindet) wieder auf dem NFC-Leser des Telefons ablegen, wird die 9000-Antwort vom Chip des Passes angezeigt.

Warten! Was ist da gerade passiert?

Der APDU-Befehl, den wir an die Karte senden, ist derselbe, den wir im ersten Absatz gesehen haben. Es wird versucht, die AID "A0000002471001" auszuwählen. Dies ist die ID des Applets, das die persönlichen Daten des Inhabers in jedem ICAO-konformen ePass enthält.

Wohin von hier aus gehen:

Sie können das, was wir hier durchgemacht haben, tatsächlich verwenden, um die persönlichen Daten im Reisepass zu lesen, aber das ist ein ganz anderes Thema, das ich in Teil zwei dieser Serie behandeln werde. Grundsätzlich müssten wir Basic Access Control (BAC) durchführen, bei der wir dem Chip nachweisen, dass wir die Schlüssel kennen (Die Schlüssel werden basierend auf der Dokumentennummer, dem Ablaufdatum und dem Geburtsdatum generiert). Wenn Sie einen Passleser erstellen möchten, müssen Sie mehr über BAC und die Struktur der Daten in einem Pass lesen. Ab Seite 106 wird BAC erläutert.

Wenn Sie wissen, wie man eine Smart Card emuliert, können Sie ein eigenes Modell für eine Karte erstellen, die Sie physisch nicht haben, aber die Spezifikationen für haben. Dann können Sie das von uns erstellte Lesegerät zum Lesen verwenden. Sobald Sie es haben, funktioniert es perfekt mit der echten Karte.

Sie können Ihr Telefon auch anstelle einer tatsächlichen Karte verwenden - wenn Sie wissen, was die Karten enthalten -, indem Sie einen Emulator schreiben, der genau wie die Karte funktioniert. Dies bedeutet, dass Sie Ihre Karte nicht bei sich tragen müssen. Sie können einfach die Emulator-App verwenden, um das zu tun, was Sie mit der Karte tun würden.

Download-Quellen für den Host-basierten Kartenemulator.
Laden Sie Quellen für den Beispiel-Chipkartenleser herunter.

Geschrieben von Mohamed Hamdaoui für TWG: Softwarehersteller zu den Innovatoren der Welt. Durchsuchen Sie unsere Karriereseite, um mehr über unser Team zu erfahren.