[JAVA] Lesen Sie den IC-Kontostand Ihres Studentenausweises (Felica) auf Android

Einführung

Dieser Artikel ist der 8. Tagesartikel von SLP KBIT Adventskalender 2019.

Kürzlich habe ich erfahren, dass Sie die Daten auf der IC-Karte mit der NFC-Funktion von Android lesen können. Also dachte ich: "Kann ich meinen Studentenausweis lesen?", Also versuchte ich es.

Über die zu lesenden Daten

Dieses Mal möchte ich den IC-Kontostand der Universitätsgenossenschaft lesen, der im Studentenausweis gespeichert ist.

Da es sich bei Ihrem Studentenausweis um einen mit Felica kompatiblen IC-Ausweis handelt, können Sie ihn mit einem mit NFC Felica kompatiblen Terminal lesen. Der Felica-Standard ist in Auszug aus dem FeliCa Card-Benutzerhandbuch zusammengefasst.

In Bezug auf die Datenstruktur des Studentenausweises habe ich darauf verwiesen, wie sie in Spezifikationen der Universitätsgenossenschaft Felica zusammengefasst wurde.

Die Daten, die ich dieses Mal erhalten möchte, sind

Es wird als ** Purse Service ** in gespeichert. Gemäß der Purse Service-Spezifikation wird der tatsächliche Kontostand in den oberen 4 Bytes im Little-toilen-Format gespeichert. Daher müssen nach dem Erfassen der Daten 4 Bytes extrahiert und in das Big-Endian-Format konvertiert werden.

Grob zusammengefasst hat es die folgende Struktur.

- System 0:
    - ...
- System 1: (0xFE00)
    - IDm
    - Area
    - ...
    - Area
        - Service
        - ...
        - Purse Service: (0x50D7) <-Hier

Felica auf Android lesen

Der Gesamtprozess ist MainActivity.java, Das Abrufen von Daten von Felica erfolgt mit NfcReader.java.

Informationen zur von MainActivity.java durchgeführten Verarbeitung finden Sie auf der folgenden Seite. [Android] Lesen Sie NFC: Crafting and Horse Racing --Livedoor

Der eindeutige Wert der Karte wird konstant gemacht, falls Sie eine andere Karte oder Daten lesen möchten.

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 {

    //Konstante
    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;

    //Variablen für den Umgang mit Adaptern
    private NfcAdapter mNfcAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //Holen Sie sich eine Instanz des Adapters
        mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
    }

    @Override
    protected void onResume() {

        super.onResume();

        //Einstellungen, wenn der NFC gehalten wird
        Intent intent = new Intent(this, this.getClass());
        intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);

        //Vermeiden Sie das Öffnen anderer Apps
        PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(), 0, intent, 0);
        mNfcAdapter.enableForegroundDispatch(this, pendingIntent, null, null);

    }

    @Override
    protected void onPause() {
        super.onPause();

        //Nicht empfangen, wenn die Aktivität im Hintergrund ist
        mNfcAdapter.disableForegroundDispatch(this);
    }

    @Override
    protected void onNewIntent(Intent intent) {
        NfcReader nfcReader = new NfcReader();
        super.onNewIntent(intent);

        //Holen Sie sich NFC TAG
        Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);

        //Bilanzdaten lesen
        byte[][] data = nfcReader.readTag(tag, TARGET_SYSTEM_CODE, TARGET_SERVICE_CODE, TARGET_SIZE);

        //Wenn es nicht gelesen werden kann, endet es
        if (data == null) {
            Toast.makeText(this, "error", Toast.LENGTH_SHORT).show();
            return;
        }

        //Umwandlung
        byte[] balanceData = new byte[]{data[0][3], data[0][2], data[0][1], data[0][0]};
        int balance = ByteBuffer.wrap(balanceData).getInt();

        //Anzeige
        Toast.makeText(this, balance + "Kreis", Toast.LENGTH_LONG).show();
    }
}

Erstellen Sie als Nächstes NfcReader.java. NfcReader.java basiert auf dem Code auf der folgenden Seite. Felica (NFC) -Blockdaten auf Android abrufen

Der Verarbeitungsinhalt ist nahezu identisch, die readTag-Methode wurde jedoch geändert, sodass der eindeutige Wert der Karte als Argument übergeben werden kann.

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();

            //Abrufbefehl erstellen
            byte[] polling = polling(targetSystemCode);

            //Senden Sie einen Befehl und erhalten Sie das Ergebnis
            byte[] pollingRes = nfc.transceive(polling);

            //Holen Sie sich System 0 IDm(Das erste Byte ist die Datengröße, das zweite Byte ist der Antwortcode und die IDm-Größe beträgt 8 Bytes.)
            byte[] targetIDm = Arrays.copyOfRange(pollingRes, 2, 10);

            //Befehl "Lesen ohne Verschlüsselung" erstellen
            byte[] req = readWithoutEncryption(targetIDm, size, targetServiceCode);

            //Senden Sie einen Befehl und erhalten Sie das Ergebnis
            byte[] res = nfc.transceive(req);

            nfc.close();

            //Analysieren Sie das Ergebnis und erhalten Sie nur die Daten
            return parse(res);
        } catch (Exception e) {
            Log.e(TAG, e.getMessage() , e);
        }
        return null;
    }

    /*
     *Abrufbefehl abrufen.
     * @param systemCode byte[]Systemcode anzugeben
     * @Befehl Polling zurückgeben
     * @throws IOException
     */
    private byte[] polling(byte[] systemCode) {
        ByteArrayOutputStream bout = new ByteArrayOutputStream(100);

        bout.write(0x00);           //Datenlängen-Byte-Dummy
        bout.write(0x00);           //Befehlscode
        bout.write(systemCode[0]);  // systemCode
        bout.write(systemCode[1]);  // systemCode
        bout.write(0x01);           //Anfrage Code
        bout.write(0x0f);           //Zeitfenster

        byte[] msg = bout.toByteArray();
        msg[0] = (byte) msg.length; //Das erste Byte ist die Datenlänge
        return msg;
    }

    /*
     *Rufen Sie den Befehl Lesen ohne Verschlüsselung ab.
     * @param IDm ID des angegebenen Systems
     * @Parametergröße Anzahl der abzurufenden Daten
     * @Befehl Read Without Encryption zurückgeben
     * @throws IOException
     */
    private byte[] readWithoutEncryption(byte[] idm, int size, byte[] serviceCode) throws IOException {
        ByteArrayOutputStream bout = new ByteArrayOutputStream(100);

        bout.write(0);              //Datenlängen-Byte-Dummy
        bout.write(0x06);           //Befehlscode
        bout.write(idm);            // IDm 8byte
        bout.write(1);              //Länge der Dienstnummer(Die folgenden 2 Bytes wiederholen sich für diese Anzahl von Minuten)

        //Da der Servicecode als Little Endian angegeben ist, geben Sie ihn im unteren Byte an.
        bout.write(serviceCode[1]); //Service Code Low Byte
        bout.write(serviceCode[0]); //Service Code High Byte
        bout.write(size);           //Anzahl der Blöcke

        //Angabe der Blocknummer
        for (int i = 0; i < size; i++) {
            bout.write(0x80);       //Blockelement oberes Byte "Felica Benutzerhandbuch Auszug" 4.Siehe Punkt 3
            bout.write(i);          //Blocknummer
        }

        byte[] msg = bout.toByteArray();
        msg[0] = (byte) msg.length; //Das erste Byte ist die Datenlänge
        return msg;
    }

    /*
     *Analyse ohne Verschlüsselungsantwort lesen.
     * @param res byte[]
     * @Zeichenfolgendarstellung zurückgeben
     * @throws Exception
     */
    private byte[][] parse(byte[] res) {
        // res[10]Fehlercode. 0x00 ist normal
        if (res[10] != 0x00) {
            throw new RuntimeException("Read Without Encryption Command Error");
        }

        // res[12]Anzahl der Antwortblöcke
        // res[13 + n * 16]Aktuelle Daten 16(byte/Block)Wiederholen
        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;
    }
}

Ich habe es versucht

Als ich meinen Studentenausweis über mein Smartphone hielt, wurde der Kontostand genau angezeigt. Es stimmte sogar im Vergleich zur Quittung überein. War gut.

abschließend

Es war meine erste Entwicklung auf Android, aber es war interessanter als ich erwartet hatte. Android hat verschiedene Funktionen, deshalb wollte ich verschiedene Dinge ausprobieren, wenn ich Zeit hatte.

Recommended Posts

Lesen Sie den IC-Kontostand Ihres Studentenausweises (Felica) auf Android
Lesen und sehen Sie die Informationen auf der FeliCa (WAON) -Karte
Informationen zum Abschneiden von Zeichenfolgen um die Anzahl der Bytes unter Android
(Ruby on Rails6) Anzeige der Datenbank, die die ID der Datenbank erhalten hat
Holen Sie sich die Beschleunigung und Ausrichtung des Weltkoordinatensystems auf Android