How to build a simple smart card emulator & reader for Android

Mohamed Hamdaoui
The Almanac
Published in
11 min readNov 20, 2017

--

Hi! I am a Senior Software Engineer @TWG. Previously, I worked at Gemalto — the world’s biggest Smart Cards manufacturer.

Introduction:

Smart Cards have a lot of useful functions - from ID cards, to repository cards - and they present a flexible, secure and portable tool to manage all sorts of data.

The goal of this article is to be able to use the Android phone’s NFC to emulate and read a Smart Card, but before that, it’s very important to understand how Smart Cards work and communicate with readers.

What you need:

  • Basic Android development knowledge
  • Basic Kotlin knowledge, as we will do the examples in Kotlin
  • An Android phone A with NFC that will act as a Card Emulator for our tests
  • An Android phone B with NFC that will act as a Card Reader
  • Optional: an ePassport that is ICAO compliant. you can verify if your passport is ICAO compliant and contains an electronic chip by looking at the front and seeing this sign:
Chip sign in the front of ePassports

Smart Cards:

Smart Cards are, for all intents and purposes, mini-computers, that contain a micro-processors and memory. Just like a regular computer, they can have an OS and applications running (they are called Applets) capable of doing complex operations as well as providing a secure access to data.

Types:

Smart Cards can be “Contact”, “Contactless”, or both (Dual Interface). Contact cards have to be in direct contact with the reader in order to be powered and ready for communication. Contactless cards however can be communicated with using 13.56-MHz Radio Waves from a maximum distance of 10 cm (4 in). they contain an antenna that enables this type of communications:

Dual Interface Smart Card

Every phone has at least a Contact Smart Card Reader, which is used to read the SIM card. Most Android phones have a Contactless Smart Card Reader in the form of the NFC Reader, which we will give examples on how to use.

Data Structure:

The ISO7816 regulates how the data in Smart Cards should be structured. It supports the following structure:

DF/Dedicated Files: You can think of them as directories containing files. It is mandatory to have at least one root DF which is called Master File (MF).

EF/Elementary Files: These are files containing binary data that can be read or written to by an external reader.

Data Structure as described in the ISO7816

Cards can sometimes contain more than one Applet, and the first thing you need to do is select the Applet you want to communicate with using its AID.

Communication Protocol:

The ISO7816 also defines the communication protocol with Smart Cards (contact and contactless). In order to communicate with the card, a reader has to send an “APDU Command” (Application Protocol Data Unit Command) to the card, which will respond with an “APDU Response”.

APDU commands are byte arrays containing the following:

Important: In all the following, we will refer to bytes in their Hexadecimal representations, like `A0`, `FE`, `00` …

CLA: the Class byte is used to indicate to what extent the command complies with the ISO7816, and if so, what kind of “Secure Messaging” will be used. To keep things simple, we will not be using this byte in our example and we will be passing `00` all the time.

INS: the Instruction byte is used to indicate what method we want to execute, this can be a variety of methods like: `A4` to Select a File or Applet, `B0` to Read a Binary, `D0` to Write a Binary … (see full list of Instructions here)

P1 & P2: these two Parameter bytes are used for further customization of the Instruction, they depend on what custom commands the card specifies. Click here for the list of the possible cases

Lc: is the length of the data to be sent

Data: is the actual data for the Instruction

Le: is the length of the expected response

And so an example to select a file or Applet with an `ID = A0000002471001` will be as follow: `00 A4 0400 07 A0000002471001 00`

Once the card receives the command, it will respond with an APDU response as follow:

Data Field: is the the body of the response

SW1 & SW2: are the status bytes, they are separated because sometimes the first byte can tell us the actual status, and the second byte can specify more information about that status. For example, if we use a command to “Verify a PIN” with the wrong PIN, the card will return a status of `63 CX` where X is the number of attempts left, that way, the reader app can easily check the first byte for the status and second for the number of attempts left.

When the command has been successfully executed, we usually get a `90 00` status (See the full list of possible responses in here)

Using the information above, we are now ready to create a Smart Card Emulator and a Smart Card Reader in Android, let’s get right into it:

Make sure you are running the Android Studio 3.1 Canary or later to support Kotlin development

Host-based Card Emulation (HCE):

Starting from Android 4.4, we have the possibility to create a Card Emulation Service, that will act as a Smart Card by taking APDU commands and returning APDU responses. To do that, let’s create a new Android project: New → New Project

Make sure you are checking the `Include Kotlin support` checkbox, and click “Next”

Make sure you are selecting API 19 or higher, as the Card Emulation is only supported starting Android 4.4. Click “Next”, choose “Empty Activity” and “Finish”.
The only reason we are adding an activity is to make things simpler, our Card Emulator will run as a Service all the time in the background, so it doesn’t actually need an activity, but for the sake of simplicity, we will use one.

The first thing we will add is the Manifest permission declaration to use NFC, in your `AndroidManifest.xml` add the following inside the `manifest` tag before the `application tag`:

<uses-permission android:name=”android.permission.NFC” />

Next we will add the requirement for the HCE hardware so that the app only installs on phones that can run the HCE, right under the previous line, add this line:

<uses-feature android:name="android.hardware.nfc.hce"
android:required="true"
/>

Next, we will declare our HCE service inside the `application` tag:

<service
android:name=".HostCardEmulatorService"
android:exported="true"
android:permission="android.permission.BIND_NFC_SERVICE"
>
<intent-filter>
<action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE" />
</intent-filter>

<meta-data
android:name="android.nfc.cardemulation.host_apdu_service"
android:resource="@xml/apduservice"
/>
</service>

There is a lot going on in this declaration, so let’s go through it:

  • Name: is the name of the Class that will implement the Service callbacks (we will create that in a minute).
  • Exported: this has to be true in order for our service to be accessible by other applications. If this is false, then no outside application can interact with the service, which is not what we want.
  • Permission: the Service has to Bind to the NFC service in order to be able to use NFC.
  • Intent Filter: When the Android system detects that an external Card Reader is trying to read a card, it fires a `HOST_APDU_SERVICE` action, our service having registered to that action, will be called, and then we can do whatever we want once our service is called into action.
  • Metadata: in order for the system to know which services to call based on which AID the reader is trying to communicate with, we need to declare the `meta-data` tag and point to an XML resource.

Let’s create the `apduservice` XML file now: Right-click the `res` folder in your project and choose new → Directory, call it `xml`. Then create a new xml file inside this new directory called `apduservice` and write the following inside:

<host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/servicedesc"
android:requireDeviceUnlock="false"
>
<aid-group android:description="@string/aiddescription"
android:category="other"
>
<aid-filter android:name="A0000002471001"/>
</aid-group>
</host-apdu-service>

The most important part here is the AID filter, which registers our service to be fired if that AID is being selected by a Card Reader.

You have to create the @string values for the descriptions

Now we will create our Service, Right-click your package, select New → Kotlin File/Class

Select `Class` and the same name we put in the Manifest. Click Ok.

Now we will extend `HostApduService` Abstract Class and implement its abstract methods:

class HostCardEmulatorService: HostApduService() {
override fun onDeactivated(reason: Int) {
TODO("not implemented") //To change body of created
// functions use File | Settings | File Templates.
}

override fun processCommandApdu(commandApdu: ByteArray?,
extras: Bundle?): ByteArray {
TODO("not implemented") //To change body of created
// functions use File | Settings | File Templates.
}
}

The `onDeactiveted` method will be called when the a different AID has been selected or the NFC connection has been lost.

The `processCommandApdu` method will be called every time a card reader sends an APDU command that is filtered by our manifest filter.

Let’s define a few constants to work with, before the first method, add the following:

companion object {
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
}

Just go ahead and write this inside the `onDeactivated`:

Log.d(TAG, "Deactivated: " + reason)

Before implementing the `processCommandApdu` method, we will need a couple of helper methods. Create a new Kotlin Class named `Utils` and copy the two following (static in Java) methods:

companion object {
private val HEX_CHARS = "0123456789ABCDEF"
fun
hexStringToByteArray(data: String) : ByteArray {

val result = ByteArray(data.length / 2)

for (i in 0 until data.length step 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())
}

return result
}

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

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

return
result.toString()
}
}

These methods are here to convert between byte arrays and Hexadecimal Strings.

Now let’s write the following into the `processCommandApdu` method:

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

val hexCommandApdu = Utils.toHex(commandApdu)
if (hexCommandApdu.length < MIN_APDU_LENGTH) {
return Utils.hexStringToByteArray(STATUS_FAILED)
}

if (hexCommandApdu.substring(0, 2) != DEFAULT_CLA) {
return Utils.hexStringToByteArray(CLA_NOT_SUPPORTED)
}

if (hexCommandApdu.substring(2, 4) != SELECT_INS) {
return Utils.hexStringToByteArray(INS_NOT_SUPPORTED)
}

if (hexCommandApdu.substring(10, 24) == AID) {
return Utils.hexStringToByteArray(STATUS_SUCCESS)
} else {
return Utils.hexStringToByteArray(STATUS_FAILED)
}

This is obviously just a mockup to create our first emulation. You can customize this as you like, but what I created here is a simple check for length and CLA and INS that returns a successful APDU (9000) only when we select the predefined AID.

Android Card Reader with NFC Example:

Just like the previous project, create a new project with Android 4.4 as a minimum SDK, and with Kotlin support, with an Empty Activity.

Inside the `AndroidManifest.xml` declare the same NFC permission:

<uses-permission android:name=”android.permission.NFC” />

Also declare the NFC requirement:

<uses-feature android:name="android.hardware.nfc" 
android:required="true"
/>

In your activity_main.xml layout, just add a TextView to see the card responses with

<TextView
android:id="@+id/textView"
....
/>

Copy the `Utils` Class from the previous project because we will use the same methods here as well.

In your Activity, next to the `AppCompatActivity()` extension, add the following:

class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback

This will implement the `ReaderCallback` Interface and you will have to implement the `onTagDiscovered` method.

Inside your Activity, declare the following variable:

private var nfcAdapter: NfcAdapter? = null

In your onCreate method, add the following:

nfcAdapter = NfcAdapter.getDefaultAdapter(this);

This will get the default NfcAdapter for us to use.

Override the `onResume` method as follow:

public override fun onResume() {
super.onResume()
nfcAdapter?.enableReaderMode(this, this,
NfcAdapter.FLAG_READER_NFC_A or
NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK,
null)
}

This will enable reader mode while this activity is running. When dealing with a Smart Card, make sure you search the technology it is using, so you declare it, but mostly you can use NFC_A. The second flag is there so we skip the NDEF interfaces, what that means in our case, is we don’t want Android Beam to be called when on this activity, otherwise, it will interfere with our reader, because Android gives priority to NDEF type before TECH type (or Smart Cards in General)

Don’t forget to override the `onPause()` method and disable the NfcAdapter:

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

Now the last thing left is to start sending APDU commands once a card is detected, so in `onTagDiscovered` method, let’s write the following:

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

Now if you run the first app on Phone A, and the second app on Phone B, then put the phones back-to-back, so their NFCs are facing one another, here is what will happen as soon as Phone B detects :

Interaction diagam

Phone B will send the above APDU command to the HCE of Phone A, which will forward it to our Service and return a 9000 status. You can intentionally change the command to see one of the errors we put in the Service, like using another CLA or INS …

Now here is the good part. If you take Phone B with our Card Reader App, and put your ePassport back (where the Chip is) on the NFC reader of the phone, you will see the 9000 response from the Chip of the Passport.

Wait! What just happened there?

The APDU command we are sending to the Card is the same we saw on the first paragraph. It is trying to Select the AID `A0000002471001`, which is the ID of the Applet that contains the personal data of the holder inside every ICAO compliant ePassport.

Where to go from here:

You can actually use what we went through here to read the personal data inside the passport, but that’s a whole other subject, that I will cover in part two of this series. Basically, we would need to do Basic Access Control (BAC) where we prove to the chip that we know the keys (The keys are generated based on the Number of Document, Expiry Date, and Birthdate). If you are interested in creating a passport reader, you have to read more about BAC and the structure of data inside a Passport, starting Page 106 is where BAC is explained.

By knowing how to emulate a Smart Card, you can write your own mock-up for a Card that you don’t physically have, but have the specs for. Then, you can use the reader we created to read it, and it will work perfectly with the real Card once you have it.

You can also use your phone instead of an actual Card - if you know what the cards contains - by writing an Emulator that acts exactly like the card. This means you don’t have to carry your Card — you could just use the Emulator app to do what you would with the Card.

Download Sources for the Host-based Card Emulator.
Download Sources for the Sample Smart Card Reader.

Written by Mohamed Hamdaoui for TWG: Software makers to the world’s innovators. Browse our career page to learn more about our team.

--

--