Sådan bygger du en simpel smart card emulator og læser til Android

Hej! Jeg er senior softwareingeniør @TWG. Tidligere arbejdede jeg hos Gemalto - verdens største producent af Smart Cards.

Introduktion:

Smart Cards har en masse nyttige funktioner - fra ID-kort til depotkort - og de præsenterer et fleksibelt, sikkert og bærbart værktøj til at styre alle slags data.

Målet med denne artikel er at være i stand til at bruge Android-telefonens NFC til at efterligne og læse et Smart Card, men inden det er det meget vigtigt at forstå, hvordan Smart Cards fungerer og kommunikere med læsere.

Hvad du har brug for:

  • Grundlæggende viden om Android-udvikling
  • Grundlæggende Kotlin-viden, som vi vil gøre eksemplerne i Kotlin
  • En Android-telefon A med NFC, der fungerer som en kortemulator til vores test
  • En Android-telefon B med NFC, der fungerer som en kortlæser
  • Valgfrit: en ePassport, der er ICAO-kompatibel. kan du kontrollere, om dit pas er ICAO-kompatibelt og indeholder en elektronisk chip ved at se på fronten og se dette tegn:
Chip-skilt foran på ePassports

Smart Cards:

Smart Cards er til enhver anledning mini-computere, der indeholder en mikro-processor og hukommelse. Ligesom en almindelig computer kan de have et operativsystem og applikationer, der kører (de kaldes Applets), der er i stand til at udføre komplekse operationer samt give en sikker adgang til data.

typer:

Smartkort kan være "Kontakt", "Kontaktløs" eller begge dele (Dual Interface). Kontaktkort skal være i direkte kontakt med læseren for at være tændt og klar til kommunikation. Kontaktløse kort kan imidlertid kommunikeres med 13,56 MHz radiobølger fra en maksimal afstand på 10 cm. de indeholder en antenne, der muliggør denne type kommunikation:

Smartkort med dobbelt interface

Hver telefon har mindst en Kontakt Smart Card Reader, der bruges til at læse SIM-kortet. De fleste Android-telefoner har en kontaktløs smartcardlæser i form af NFC Reader, som vi vil give eksempler på, hvordan du bruger dem.

Datastruktur:

ISO7816 regulerer, hvordan dataene i Smart Cards skal struktureres. Det understøtter følgende struktur:

DF / dedikerede filer: Du kan tænke på dem som mapper, der indeholder filer. Det er obligatorisk at have mindst en root DF, der kaldes Master File (MF).

EF / elementære filer: Dette er filer, der indeholder binære data, der kan læses eller skrives til af en ekstern læser.

Datastruktur som beskrevet i ISO7816
Kort kan undertiden indeholde mere end en applet, og den første ting, du skal gøre, er at vælge den applet, du vil kommunikere med, ved hjælp af dens AID.

Kommunikationsprotokol:

ISO7816 definerer også kommunikationsprotokollen med Smart Cards (kontakt og kontaktløs). For at kommunikere med kortet skal en læser sende en “APDU-kommando” (Application Protocol Data Unit Command) til kortet, som vil svare med en “APDU-respons”.

APDU-kommandoer er byte-arrays, der indeholder følgende:

Vigtigt: I alt det følgende vil vi henvise til byte i deres hexadecimale repræsentationer, som 'A0', 'FE', '00' ...

CLA: Class-byte bruges til at indikere, i hvilken udstrækning kommandoen er i overensstemmelse med ISO7816, og i bekræftende fald hvilken type “Secure Messaging”, der vil blive brugt. For at holde tingene enkle bruger vi ikke denne byte i vores eksempel, og vi passerer hele tiden 00.

INS: Instruktionsbyten bruges til at indikere, hvilken metode vi vil udføre, dette kan være en række forskellige metoder som: 'A4' til at vælge en fil eller en applet, 'B0' for at læse en binær, 'D0' for at skrive en binær ... (se den fulde liste over instruktioner her)

P1 & P2: disse to parameterbyteer bruges til yderligere tilpasning af instruktionen, de afhænger af, hvilke brugerdefinerede kommandoer kortet angiver. Klik her for listen over mulige sager

Lc: er længden på de data, der skal sendes

Data: er de faktiske data for instruktionen

Le: er længden af ​​det forventede svar

Og et eksempel på at vælge en fil eller en applet med en `ID = A0000002471001` vil være som følger:` 00 A4 0400 07 A0000002471001 00`

Når kortet modtager kommandoen, vil det svare med et APDU-svar som følger:

Datafelt: er reaktionens krop

SW1 & SW2: er statusbyte, de adskilles, fordi nogle gange kan den første byte fortælle os den aktuelle status, og den anden byte kan specificere mere information om denne status. For eksempel, hvis vi bruger en kommando til "Bekræft en pinkode" med den forkerte pinkode, vil kortet returnere en status på "63 CX", hvor X er antallet af forsøg tilbage, på den måde kan læserappen let tjekke den første byte for status og nummer to for antallet af forsøg, der er tilbage.

Når kommandoen er udført med succes, får vi normalt en '90 00' status (Se den fulde liste over mulige svar her)

Ved hjælp af ovenstående oplysninger er vi nu klar til at oprette en Smart Card Emulator og en Smart Card Reader i Android, lad os komme lige ind i det:

Sørg for, at du kører Android Studio 3.1 Canary eller nyere for at understøtte Kotlin-udvikling

Værtsbaseret kortemulering (HCE):

Fra Android 4.4 har vi muligheden for at oprette en Card Emulation Service, der fungerer som et Smart Card ved at tage APDU-kommandoer og returnere APDU-svar. For at gøre det, lad os oprette et nyt Android-projekt: Nyt → nyt projekt

Sørg for, at du markerer afkrydsningsfeltet "Inkluder Kotlin support", og klik på "Næste"

Sørg for, at du vælger API 19 eller nyere, da kortemulering kun understøttes ved start af Android 4.4. Klik på "Næste", vælg "Tom aktivitet" og "Udfør".
Den eneste grund til, at vi tilføjer en aktivitet, er at gøre tingene enklere. Vores kortemulator kører som en tjeneste hele tiden i baggrunden, så den behøver faktisk ikke en aktivitet, men for enkelhedens skyld bruger vi en .

Den første ting, vi tilføjer, er manifest-tilladelseserklæringen til at bruge NFC, i din `AndroidManifest.xml` tilføje følgende inden i` manifest'-tagget før `applikationskoden ':

Dernæst tilføjer vi kravet til HCE-hardware, så appen kun installerer på telefoner, der kan køre HCE, lige under den forrige linje, tilføj denne linje:

Dernæst erklærer vi vores HCE-service inden for taggen 'applikation':


    
        
    

    

Der er meget, der sker i denne erklæring, så lad os gennemgå den:

  • Navn: er navnet på den klasse, der implementerer tilbagekald af tjenester (vi opretter det om et minut).
  • Eksporteret: dette skal være sandt for at vores service skal være tilgængelig for andre applikationer. Hvis dette er forkert, kan ingen ekstern applikation interagere med tjenesten, hvilket ikke er det, vi ønsker.
  • Tilladelse: Tjenesten skal binde sig til NFC-tjenesten for at kunne bruge NFC.
  • Intentfilter: Når Android-systemet registrerer, at en ekstern kortlæser forsøger at læse et kort, udløser det en `HOST_APDU_SERVICE`-handling, vores tjeneste, der har registreret sig til denne handling, vil blive kaldt, og så kan vi gøre, hvad vi vil, når vores service kaldes til handling.
  • Metadata: for at systemet skal vide, hvilke tjenester, der skal ringe, baseret på hvilken AID læseren forsøger at kommunikere med, er vi nødt til at erklære `metadata'-tagget og pege på en XML-ressource.

Lad os oprette XML-filen 'apduservice' nu: Højreklik på mappen 'res' i dit projekt og vælg ny → Directory, kalder den 'xml'. Opret derefter en ny xml-fil i denne nye mappe kaldet `apduservice` og skriv følgende indeni:


    
        
    

Den vigtigste del her er AID-filteret, der registrerer vores service, der skal affyres, hvis denne AID vælges af en kortlæser.

Du skal oprette @strengværdier til beskrivelserne

Nu opretter vi vores service, højreklik på din pakke, vælg Ny → Kotlin fil / klasse

Vælg "Klasse" og det samme navn, vi lægger i manifestet. Klik på Ok.

Nu udvider vi "HostApduService" Abstract Class og implementerer dets abstrakte metoder:

klasse HostCardEmulatorService: HostApduService () {
    tilsidesætte sjov onDeactivated (grund: Int) {
        TODO ("ikke implementeret") // For at ændre krop af oprettet
        // funktioner bruger File | Indstillinger | Filskabeloner.
    }

    tilsidesætte sjov procesCommandApdu (commandApdu: ByteArray ?,
                                    ekstramateriale: Bundle?): ByteArray {
        TODO ("ikke implementeret") // For at ændre krop af oprettet
        // funktioner bruger File | Indstillinger | Filskabeloner.
    }
}

Metoden 'onDeactiveted' kaldes, når en anden AID er valgt, eller NFC-forbindelsen er mistet.

Metoden `processCommandApdu` kaldes hver gang en kortlæser sender en APDU-kommando, der filtreres af vores manifestfilter.

Lad os definere et par konstanter at arbejde med, før den første metode tilføjes følgende:

ledsagerobjekt {
    val TAG = "Host Card 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
}

Bare gå videre og skriv dette inde i 'onDeactivated':

Log.d (TAG, "Deaktiveret:" + grund)

Før vi implementerer metoden `processCommandApdu`, har vi brug for et par hjælpermetoder. Opret en ny Kotlin-klasse med navnet 'Utils' og kopier de to følgende (statiske i Java) metoder:

ledsagerobjekt {
    privat val HEX_CHARS = "0123456789ABCDEF"
    sjov hexStringToByteArray (data: String): ByteArray {

        val resultat = ByteArray (data.length / 2)

        for (i i 0 indtil data.length trin 2) {
            val firstIndex = HEX_CHARS.indexOf (data [i]);
            val secondIndex = HEX_CHARS.indexOf (data [i + 1]);

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

        returresultat
    }

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

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

        returresultat.toString ()
    }
}

Disse metoder er her til at konvertere mellem byte-arrays og hexadecimale strenge.

Lad os nu skrive følgende i metoden `processCommandApdu`:

if (commandApdu == null) {
    returner Utils.hexStringToByteArray (STATUS_FAILED)
}

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

Dette er tydeligvis bare en mockup for at skabe vores første emulering. Du kan tilpasse dette, som du vil, men det, jeg oprettede her, er en enkel kontrol af længde og CLA og INS, der kun returnerer en vellykket APDU (9000), når vi vælger den foruddefinerede AID.

Android-kortlæser med NFC-eksempel:

Ligesom det foregående projekt, skal du oprette et nyt projekt med Android 4.4 som et minimum SDK og med Kotlin support med en tom aktivitet.

Inde i 'AndroidManifest.xml' erklærer den samme NFC-tilladelse:

Erklær også NFC-kravet:

I dit Activity_main.xml-layout skal du bare tilføje en TextView for at se kortets svar med

Kopier 'Utils'-klassen fra det forrige projekt, fordi vi også vil bruge de samme metoder her.

I din aktivitet skal du tilføje følgende ved siden af ​​udvidelsen 'AppCompatActivity ()':

klasse MainActivity: AppCompatActivity (), NfcAdapter.ReaderCallback

Dette implementerer 'ReaderCallback'-grænsefladen, og du bliver nødt til at implementere metoden' onTagDiscovered '.

Inden i din aktivitet skal du erklære følgende variabel:

privat var nfcAdapter: NfcAdapter? = null

I din onCreate-metode skal du tilføje følgende:

nfcAdapter = NfcAdapter.getDefaultAdapter (dette);

Dette får standard NfcAdapter, som vi kan bruge.

Overstyr metoden 'onResume' som følger:

offentlig tilsidesættelse af sjov onResume () {
    super.onResume ()
    nfcAdapter? .enableReaderMode (dette, dette,
            NfcAdapter.FLAG_READER_NFC_A eller
                    NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK,
            nul)
}

Dette aktiverer læsertilstand, mens denne aktivitet kører. Når du beskæftiger dig med et Smart Card, skal du sørge for at søge i den teknologi, det bruger, så du erklærer det, men for det meste kan du bruge NFC_A. Det andet flag er der, så vi springer over NDEF-grænsefladerne, hvad det betyder i vores tilfælde, er, at vi ikke ønsker, at Android Beam skal kaldes, når du er på denne aktivitet, ellers vil det forstyrre vores læser, fordi Android prioriterer NDEF type inden TECH type (eller Smart Cards generelt)

Glem ikke at tilsidesætte metoden 'onPause ()' og deaktivere NfcAdapter:

offentlig tilsidesættelse af sjov onPause () {
    super.onPause ()
    nfcAdapter? .disableReaderMode (dette)
}

Nu er det sidste, der er tilbage, at begynde at sende APDU-kommandoer, når et kort er fundet, så i `onTagDiscovered`-metoden, så lad os skrive følgende:

tilsidesætte sjov onTagDiscovered (tag: Tag?) {
    val isoDep = IsoDep.get (tag)
    isoDep.connect ()
    val response = isoDep.transceive (Utils.hexStringToByteArray (
            "00A4040007A0000002471001"))
    runOnUiThread {textView.append ("\ nCard Response:"
            + Utils.toHex (svar))}
    isoDep.close ()
}

Hvis du kører den første app på telefon A og den anden app på telefon B, skal du sætte telefonerne bagfra, så deres NFC'er vender mod hinanden, her er hvad der vil ske, så snart telefon B registrerer:

Interaktionsdiagam

Telefon B sender ovennævnte APDU-kommando til HCE for telefon A, der videresender den til vores service og returnerer en 9000-status. Du kan med vilje ændre kommandoen for at se en af ​​de fejl, vi har lagt i tjenesten, som at bruge en anden CLA eller INS ...

Nu er det den gode del. Hvis du tager telefon B med vores kortlæser-app og sætter dit ePassport tilbage (hvor chippen er) på NFC-læseren på telefonen, vil du se 9000-svaret fra chippen af ​​pas.

Vente! Hvad skete der lige der?

APDU-kommandoen, vi sender til kortet, er den samme, som vi så i første afsnit. Det forsøger at vælge AID `A0000002471001`, som er ID'et på appleten, der indeholder personlige data fra indehaveren i hver ICAO-kompatibel ePassport.

Hvor man skal hen herfra:

Du kan faktisk bruge det, vi gik igennem her, til at læse de personlige data inde i passet, men det er et helt andet emne, som jeg vil dække i del to af denne serie. Grundlæggende er vi nødt til at gøre Basic Access Control (BAC), hvor vi beviser for chippen, at vi kender nøglerne (Tasterne genereres baseret på antallet af dokumenter, udløbsdato og fødselsdato). Hvis du er interesseret i at oprette en paslæser, er du nødt til at læse mere om BAC og strukturen af ​​data i et pas, der starter fra side 106 hvor BAC forklares.

Ved at vide, hvordan man emulerer et Smart Card, kan du skrive din egen mock-up for et kort, som du ikke fysisk har, men har specifikationerne for. Derefter kan du bruge læseren, vi har oprettet, til at læse den, og den vil fungere perfekt med det rigtige kort, når du først har det.

Du kan også bruge din telefon i stedet for et faktisk kort - hvis du ved, hvad kortene indeholder - ved at skrive en emulator, der fungerer nøjagtigt som kortet. Dette betyder, at du ikke behøver at bære dit kort - du kan bare bruge Emulator-appen til at gøre, hvad du ville med kortet.

Download kilder til den værtsbaserede kortemulator.
Download kilder til prøven Smart Card Reader.

Skrevet af Mohamed Hamdaoui for TWG: Softwareproducenter til verdens innovatører. Gennemse vores karriereside for at lære mere om vores team.