This article is the 8th day article of SLP KBIT Advent Calendar 2019.
Recently, I learned that it is possible to read data on an IC card using the NFC function of Android. So I thought, "Can I read my student ID card?", So I tried it.
This time, I would like to read the IC balance of the University Cooperatives stored in the student ID card.
The student ID card you have is an IC card compatible with Felica, so you can read it with a terminal compatible with NFC Felica. The Felica standard is summarized in FeliCa Card User's Manual Excerpt.
Regarding the data structure of the student ID card, it was summarized in University Cooperative Felica Specifications, so I referred to it.
The data I want to get this time is
--System code ** 0xFE00 ** --Service code ** 0x50D7 **
It is stored as ** Purse Service ** in. According to the Purse Service specification, the actual balance is stored in the upper 4 bytes in little endian format. Therefore, after retrieving the data, it is necessary to extract 4 bytes and convert it to big endian format.
Roughly summarized, it has the following structure.
- System 0:
- ...
- System 1: (0xFE00)
- IDm
- Area
- ...
- Area
- Service
- ...
- Purse Service: (0x50D7) <-here
The overall process is MainActivity.java, The process of getting data from Felica is done by NfcReader.java.
For the processing performed by MainActivity.java, refer to the following page. [Android] Read NFC: Craft and Horse Racing --Livedoor
The eigenvalues of the cards are made constant in case you want to read another card or data.
MainActivity.java
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.app.PendingIntent;
import android.content.Intent;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.widget.Toast;
import java.nio.ByteBuffer;
public class MainActivity extends AppCompatActivity {
//constant
private final byte[] TARGET_SYSTEM_CODE = new byte[]{(byte) 0xFE, (byte) 0x00};
private final byte[] TARGET_SERVICE_CODE = new byte[]{(byte) 0x50, (byte) 0xD7};
private final int TARGET_SIZE = 1;
//Variables for handling adapters
private NfcAdapter mNfcAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Get an instance of the adapter
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
}
@Override
protected void onResume() {
super.onResume();
//Settings when NFC is held over
Intent intent = new Intent(this, this.getClass());
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
//Avoid opening other apps
PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(), 0, intent, 0);
mNfcAdapter.enableForegroundDispatch(this, pendingIntent, null, null);
}
@Override
protected void onPause() {
super.onPause();
//Do not receive when Activity is in the background
mNfcAdapter.disableForegroundDispatch(this);
}
@Override
protected void onNewIntent(Intent intent) {
NfcReader nfcReader = new NfcReader();
super.onNewIntent(intent);
//Get NFC TAG
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
//Read balance data
byte[][] data = nfcReader.readTag(tag, TARGET_SYSTEM_CODE, TARGET_SERVICE_CODE, TARGET_SIZE);
//If it cannot be read, it ends
if (data == null) {
Toast.makeText(this, "error", Toast.LENGTH_SHORT).show();
return;
}
//conversion
byte[] balanceData = new byte[]{data[0][3], data[0][2], data[0][1], data[0][0]};
int balance = ByteBuffer.wrap(balanceData).getInt();
//display
Toast.makeText(this, balance + "Circle", Toast.LENGTH_LONG).show();
}
}
Next, create NfcReader.java. NfcReader.java referred to the code on the following page. Getting Felica (NFC) block data on Android
The processing contents are almost the same, but the readTag method has been changed so that the unique value of the card can be passed as an argument.
NfcReader.java
import android.nfc.Tag;
import android.nfc.tech.NfcF;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
import static android.content.ContentValues.TAG;
class NfcReader {
public byte[][] readTag(Tag tag, byte[] targetSystemCode, byte[] targetServiceCode, int size) {
NfcF nfc = NfcF.get(tag);
if (nfc == null) {
return null;
}
try {
nfc.connect();
//Create polling command
byte[] polling = polling(targetSystemCode);
//Send a command and get the result
byte[] pollingRes = nfc.transceive(polling);
//Get System 0 IDm(The first byte is the data size, the second byte is the response code, and the IDm size is 8 bytes.)
byte[] targetIDm = Arrays.copyOfRange(pollingRes, 2, 10);
//Create a Read Without Encryption command
byte[] req = readWithoutEncryption(targetIDm, size, targetServiceCode);
//Send a command and get the result
byte[] res = nfc.transceive(req);
nfc.close();
//Parse the result and get only the data
return parse(res);
} catch (Exception e) {
Log.e(TAG, e.getMessage() , e);
}
return null;
}
/*
*Get Polling command.
* @param systemCode byte[]System code to specify
* @return Polling command
* @throws IOException
*/
private byte[] polling(byte[] systemCode) {
ByteArrayOutputStream bout = new ByteArrayOutputStream(100);
bout.write(0x00); //Data length byte dummy
bout.write(0x00); //Command code
bout.write(systemCode[0]); // systemCode
bout.write(systemCode[1]); // systemCode
bout.write(0x01); //Request code
bout.write(0x0f); //Time slot
byte[] msg = bout.toByteArray();
msg[0] = (byte) msg.length; //The first byte is the data length
return msg;
}
/*
*Get the Read Without Encryption command.
* @param IDm ID of the specified system
* @param size Number of data to retrieve
* @return Read Without Encryption command
* @throws IOException
*/
private byte[] readWithoutEncryption(byte[] idm, int size, byte[] serviceCode) throws IOException {
ByteArrayOutputStream bout = new ByteArrayOutputStream(100);
bout.write(0); //Data length byte dummy
bout.write(0x06); //Command code
bout.write(idm); // IDm 8byte
bout.write(1); //Length of services(The following 2 bytes repeat for this number of minutes)
//Since the service code is specified as little endian, specify it from the lower byte.
bout.write(serviceCode[1]); //Service code low byte
bout.write(serviceCode[0]); //Service code high byte
bout.write(size); //Number of blocks
//Specifying the block number
for (int i = 0; i < size; i++) {
bout.write(0x80); //Block element upper byte "Felica user manual excerpt" 4.See item 3
bout.write(i); //Block number
}
byte[] msg = bout.toByteArray();
msg[0] = (byte) msg.length; //The first byte is the data length
return msg;
}
/*
*Read Without Encryption Response analysis.
* @param res byte[]
* @return string representation
* @throws Exception
*/
private byte[][] parse(byte[] res) {
// res[10]Error code. 0x00 is normal
if (res[10] != 0x00) {
throw new RuntimeException("Read Without Encryption Command Error");
}
// res[12]Number of response blocks
// res[13 + n * 16]Actual data 16(byte/block)Repeat
int size = res[12];
byte[][] data = new byte[size][16];
for (int i = 0; i < size; i++) {
byte[] tmp = new byte[16];
int offset = 13 + i * 16;
for (int j = 0; j < 16; j++) {
tmp[j] = res[offset + j];
}
data[i] = tmp;
}
return data;
}
}
When I held my student ID card over my smartphone, the balance was displayed accurately. It matched even when compared with the receipt. Was good.
It was my first time developing on Android, but it was more interesting than I expected. Android has various functions, so I wanted to try various things if I had time.
Recommended Posts