2014年11月26日 星期三

[Java] Java讀取健保卡基本資料(晶片讀卡機)













健保卡內到底存了什麼

 在健保卡上所嵌的IC晶片內規劃有「個人基本資料」、「健保資料」、「醫療專區」及「衛生行政專區」等四種不同類別資料存放區段,各區段預定存放之內容說明如下:


基本資料
健保內容
醫療專區
衛生行政區
卡片號碼
保險人代碼
門診處方箋
預防接種資料
姓名
保險對像身份註記
長期處方箋
同意器官捐贈註記
身份證字號
卡片有效期限
重要處方項目
同意安寧緩和醫療註記
出生年月日
重大傷病註記
過敏藥物

性別
就醫可用次數


發卡日期
最近一次就醫序號


照片
新生兒依附註記


卡片註銷註記
就醫類別



新生兒就醫註記



就診日期時間



補卡註記



就醫序號



保險醫事服務機構代碼



主、次診斷碼



就醫醫療費用紀錄



就醫累計資料



醫療費用總累計



個人保險費



保健服務紀錄



緊急聯絡電話



孕婦產前檢查



其他就醫需要之註記


資料來源
 http://www.nhi.gov.tw/webdata/webdata.aspx?menu=23&menu_id=817&WD_ID=196&webdata_id=918


在上述表格中,橘色部份是可以用一般晶片讀卡機讀出來的部份。其他的部份必須由健保專用讀卡機以搭配醫事人員卡以及醫療專用網路認證讀卡機及醫事人員卡後才可讀取。

今天要寫的是用一般市售的晶片讀卡機把基本資料讀取出來的方法。

目前常見用途為診間報到。










































晶片讀卡機的部份,windows大多都可以支援,但Mac上網友推的是EZ-100PU ,或是選購符合CCID規格的晶片讀卡機。

CCID(USB Chip/Smart Card Interface Devices-USB芯片智能卡接口設備)標準是由幾大國際級IT企業共同製定的一個標準,它提供了一種智能卡讀寫設備與主機或其它嵌入式主機實現相互通訊的可能。

資料來源 http://baike.baidu.com/view/2808369.htm


背景知識


IC卡內的積體電路可包含微處理器(MCU)與記憶體,只有記憶體的稱為記憶卡(Memory Card),只能儲存資料,而具微處理器則擁有運算與資料處理能力,被稱為智慧卡(Smart Card)。

接觸式 IC卡的標準是 ISO 7816(規定了規格/電氣特性/通訊協議/部件等各方面);
非接觸式 IC卡的標準是 ISO 14443。

由Microsoft、Gemplus等業者提出了 PC/SC(Personal Computer/Smart Card)的驅動程式規範,遵循此規範才能讓智慧卡(Smart Card)透過讀卡機(Card Reader)跟 PC相連運作。PC/SC規模支援 ISO 7816-4的基本指令集(APDU指令),界定了 IC卡、讀卡機及作業系統的責任與分工,各家讀卡機廠商只要遵循 PC/SC所定義之介面與方法開發驅動程式,應用程式只要透過單一標準介面與作業系統溝通,就可輕易操控各種讀卡機讀寫 IC卡。

PC/SC實做在 Microsoft Windows 200x/XP及以上版本,但 Microsoft Windows NT/9x也可以執行,而 Open-source實作的成品叫作 PC/SC Lite,支援 Linux及 Mac OS X。

資料來源
 http://blog.xuite.net/sugopili/computerblog/31011868-%E8%AE%80%E5%8F%96+IC%E5%8D%A1(%E6%99%BA%E6%85%A7%E5%8D%A1)



APDU

APDU Command  Structure

CLA
INS
P1
P2
Lc
Data
Le


APDU Command Details

Type
Name
Length
Details
CLA
Class
1 Byte
Class of the command
(e.g:if a command uses secure messageing or not)
INS
Instruction
1 Byte
Command instruction
P1
Parameter 1
1 Byte
First parameter of the instruction
P2
Parameter 2
1 Byte
Second parameter of the instruction
Lc
Length Command
0-3 Byte
Length of the command data
Data
Data
Lc Byte
Command data(apdu request)
Le
Length Expeted
0-3 Byte
Length of the response data (apdu response)


APDU Respone Structure

Response
SW1
SW2




APDU Response Details

Type
Name
Length
Details
Data
Body
0-3 Bytes
Data of the response (Le) Can be Null
SW1
Status Word 1
1 Byte
Status Word 1
SW2
Status Word 2
1 Byte
Status Word 2



至於要怎麼下APDU Command ,請自行GOOGLE  ISO 7816-4  . 或參考這篇使用的方式,小弟在此只概略說明程式碼中會用到的ADPU Command以及Response。網路上常見己的有讀取健保卡、自然人憑證、金融卡等範例程式。



以下是程式中的APDU Command  以及欄位資料


CLA
INS
P1
P2
Lc
Data
Le
00
CA
11
00
02
00 00



Type
Value
Value 意思
CLA
00
No SM or no SM (Secure messaging) indication  
INS
CA
GET DATA
P1
11
只可以輸入 11 或 00 ,當輸入00 時只會讀出卡號 (自行嘗試)
P2
00
若P1 為00 時只能輸入 00 ,若P1 為11 則任意 0x00~0x15 (自行嘗試)
Lc
02
Length of the command data  ,如果輸入 03 則 Data 為 00 00 00 以此類推,輸入08 則Data 為 00 00 00 00 00 00 00 00
Data
00 00
Command data(apdu request)
Le

Length of the response data (apdu response)  


至於回傳的純資料欄位共有57個Byte(Debug mode 得知 ,SW1 及SW2不包含在此)














程式碼如下,由網路上爬到的,配合自己的理解,修改了一點點 。


package readhealthcardbasicdata;

import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import javax.smartcardio.Card;
import javax.smartcardio.CardException;
import javax.smartcardio.CommandAPDU;
import javax.smartcardio.ResponseAPDU;
import javax.smartcardio.TerminalFactory;
import javax.smartcardio.CardTerminal;
import javax.smartcardio.CardChannel;

public class ReadData {

    //Define APDU
    public static byte[] SelectAPDU = new byte[]{(byte) 0x00,
        (byte) 0xA4, (byte) 0x04, (byte) 0x00, (byte) 0x10,
        (byte) 0xD1, (byte) 0x58, (byte) 0x00, (byte) 0x00,
        (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
        (byte) 0x00, (byte) 0x00, (byte) 0x11, (byte) 0x00};
    public static byte[] ReadProfileAPDU = 
            new byte[]{(byte) 0x00, (byte) 0xca, (byte) 0x11, 
            (byte) 0x00,(byte) 0x02, (byte) 0x00, (byte) 0x00};

    public static void main(String args[]) {
        TerminalFactory terminalFactory
                = TerminalFactory.getDefault();

        try {
            //Get CardTermial List
            for (CardTerminal terminal : 
                    terminalFactory.terminals().list()) {
                try {
                    //連線讀卡機 * 表示不限制協定,
                    //這裡也可以輸入T=1,T=0 (健保卡T=1)
                    Card card = terminal.connect("T=1");
                    CardChannel channel = 
                            card.getBasicChannel();
                    //APDU Command
                    CommandAPDU command = 
                            new CommandAPDU(SelectAPDU);
                    //APDU Response
                    ResponseAPDU response = 
                            channel.transmit(command);
                    //APDU Command
                    command = 
                            new CommandAPDU(ReadProfileAPDU);
                    //APDU Response
                    response = channel.transmit(command);
                    byte[] a = response.getData();
                    //Display Data
                    System.out.println("卡號:"
                            + new String(Arrays.copyOfRange
                    (response.getData(), 0, 12)));// 卡號
                    System.out.println("姓名:"
                            + new String(Arrays.copyOfRange
                    (response.getData(),12, 32), 
                                    "Big5").trim());// 姓名
                    System.out.println("身份證字號:"
                            + new String(Arrays.copyOfRange
                    (response.getData(),32, 42))); // 身分證號
                    System.out.println("出生年月日:"
                            + new String(Arrays.copyOfRange
                    (response.getData(),42, 49))); // 出生年月日
                    System.out.println("性別:"
                            + new String(Arrays.copyOfRange
                    (response.getData(),49, 50)));  // 性別
                    System.out.println("發卡年月日:"
                            + new String(Arrays.copyOfRange
                    (response.getData(),50, 57))); // 發卡年月日

                } 
                catch (javax.smartcardio.CardNotPresentException e) 
                {
                    continue;
                } catch (CardException e) {
                    continue;
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }
            }
        } catch (NumberFormatException e2) {
            e2.printStackTrace();
        } catch (CardException e3) {
            e3.printStackTrace();
        }
    }
}



本篇文章,小弟對APDU了解尚淺,若有不足的地方,請不吝指教。





沒有留言:

張貼留言