-
-
Save dahoba/6670a0d38e6d31c26503ffc484e5651e to your computer and use it in GitHub Desktop.
Thai National ID Card reader in GO
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
// Require pcscd, libpcsclite | |
import ( | |
"bufio" | |
"bytes" | |
"fmt" | |
"io/ioutil" | |
"os" | |
"strconv" | |
"github.com/ebfe/scard" | |
"golang.org/x/text/encoding/charmap" | |
) | |
var ( | |
cmdReq = []byte{0x00, 0xc0, 0x00, 0x00} | |
cmdSelectThaiCard = []byte{0x00, 0xA4, 0x04, 0x00, 0x08, 0xA0, 0x00, 0x00, 0x00, 0x54, 0x48, 0x00, 0x01} | |
cmdCID = []byte{0x80, 0xb0, 0x00, 0x04, 0x02, 0x00, 0x0d} | |
cmdTHFullname = []byte{0x80, 0xb0, 0x00, 0x11, 0x02, 0x00, 0x64} | |
cmdENFullname = []byte{0x80, 0xb0, 0x00, 0x75, 0x02, 0x00, 0x64} | |
cmdBirth = []byte{0x80, 0xb0, 0x00, 0xD9, 0x02, 0x00, 0x08} | |
cmdGender = []byte{0x80, 0xb0, 0x00, 0xE1, 0x02, 0x00, 0x01} | |
cmdIssuer = []byte{0x80, 0xb0, 0x00, 0xF6, 0x02, 0x00, 0x64} | |
cmdIssueDate = []byte{0x80, 0xb0, 0x01, 0x67, 0x02, 0x00, 0x08} | |
cmdExpireDate = []byte{0x80, 0xb0, 0x01, 0x6F, 0x02, 0x00, 0x08} | |
cmdAddress = []byte{0x80, 0xb0, 0x15, 0x79, 0x02, 0x00, 0x64} | |
cmdPhoto = [][]byte{ | |
{0x80, 0xb0, 0x01, 0x7B, 0x02, 0x00, 0xFF}, | |
{0x80, 0xb0, 0x02, 0x7A, 0x02, 0x00, 0xFF}, | |
{0x80, 0xb0, 0x03, 0x79, 0x02, 0x00, 0xFF}, | |
{0x80, 0xb0, 0x04, 0x78, 0x02, 0x00, 0xFF}, | |
{0x80, 0xb0, 0x05, 0x77, 0x02, 0x00, 0xFF}, | |
{0x80, 0xb0, 0x06, 0x76, 0x02, 0x00, 0xFF}, | |
{0x80, 0xb0, 0x07, 0x75, 0x02, 0x00, 0xFF}, | |
{0x80, 0xb0, 0x08, 0x74, 0x02, 0x00, 0xFF}, | |
{0x80, 0xb0, 0x09, 0x73, 0x02, 0x00, 0xFF}, | |
{0x80, 0xb0, 0x0A, 0x72, 0x02, 0x00, 0xFF}, | |
{0x80, 0xb0, 0x0B, 0x71, 0x02, 0x00, 0xFF}, | |
{0x80, 0xb0, 0x0C, 0x70, 0x02, 0x00, 0xFF}, | |
{0x80, 0xb0, 0x0D, 0x6F, 0x02, 0x00, 0xFF}, | |
{0x80, 0xb0, 0x0E, 0x6E, 0x02, 0x00, 0xFF}, | |
{0x80, 0xb0, 0x0F, 0x6D, 0x02, 0x00, 0xFF}, | |
{0x80, 0xb0, 0x10, 0x6C, 0x02, 0x00, 0xFF}, | |
{0x80, 0xb0, 0x11, 0x6B, 0x02, 0x00, 0xFF}, | |
{0x80, 0xb0, 0x12, 0x6A, 0x02, 0x00, 0xFF}, | |
{0x80, 0xb0, 0x13, 0x69, 0x02, 0x00, 0xFF}, | |
{0x80, 0xb0, 0x14, 0x68, 0x02, 0x00, 0xFF}, | |
} | |
) | |
func main() { | |
// Create context with pcscd | |
context, err := scard.EstablishContext() | |
if err != nil { | |
fmt.Println("Error EstablishContext:", err) | |
return | |
} | |
// Release context after exit | |
defer context.Release() | |
// Get all reader | |
readers, err := context.ListReaders() | |
if err != nil { | |
fmt.Println("Error ListReaders:", err) | |
return | |
} | |
if len(readers) == 0 { | |
fmt.Println("Error card readers not found.") | |
return | |
} | |
fmt.Println("Readers: ") | |
for rIdx, rItem := range readers { | |
fmt.Println("Card reader ID: ", rIdx, "Item: ", rItem) | |
} | |
// Select reader | |
readerIDx := 0 | |
if len(readers) > 1 { | |
buf := bufio.NewReader(os.Stdin) | |
fmt.Print("Select card reader ID[0]: ") | |
readerIDxInput, err := buf.ReadString('\n') | |
if err != nil { | |
fmt.Println("Error select reader:", err) | |
return | |
} | |
fmt.Println("Selected: ", readerIDxInput) | |
readerIDx, err := strconv.Atoi(readerIDxInput) | |
if readerIDx < 0 || readerIDx > len(readers)-1 { | |
fmt.Println("Error select reader: index out of bound") | |
return | |
} | |
} | |
reader := readers[readerIDx] | |
fmt.Println("Using reader:", reader) | |
// Connect to card | |
card, err := context.Connect(reader, scard.ShareExclusive, scard.ProtocolAny) | |
if err != nil { | |
fmt.Println("Error Connect:", err) | |
return | |
} | |
// Disconnect from card after exit | |
defer card.Disconnect(scard.ResetCard) | |
atr, err := card.GetAttrib(scard.AttrAtrString) | |
if err != nil { | |
fmt.Println("Error GetAttrib:", err) | |
return | |
} | |
// Get card attribute | |
fmt.Println("Card ATR: ", string(atr)) | |
if atr[0] == 0x3B && atr[1] == 0x67 { | |
cmdReq = []byte{0x00, 0xc0, 0x00, 0x01} | |
} else { | |
cmdReq = []byte{0x00, 0xc0, 0x00, 0x00} | |
} | |
fmt.Println("Req: ", cmdReq) | |
// Select thai national ID card | |
card.Transmit(cmdSelectThaiCard) | |
cid, _ := getString(card, cmdCID, cmdReq) | |
fmt.Println("cid: ", cid) | |
thFullname, _ := getString(card, cmdTHFullname, cmdReq) | |
fmt.Println("thFullname: ", thFullname) | |
enFullname, _ := getString(card, cmdENFullname, cmdReq) | |
fmt.Println("enFullname: ", enFullname) | |
dateOfBirth, _ := getString(card, cmdBirth, cmdReq) | |
fmt.Println("dateOfBirth: ", dateOfBirth) | |
gender, _ := getString(card, cmdGender, cmdReq) | |
fmt.Println("gender: ", gender) | |
issuer, _ := getString(card, cmdIssuer, cmdReq) | |
fmt.Println("issuer: ", issuer) | |
issueDate, _ := getString(card, cmdIssueDate, cmdReq) | |
fmt.Println("issueDate: ", issueDate) | |
expireDate, _ := getString(card, cmdExpireDate, cmdReq) | |
fmt.Println("expireDate: ", expireDate) | |
address, _ := getString(card, cmdAddress, cmdReq) | |
fmt.Println("address: ", address) | |
photo, _ := getPhoto(card, cmdReq) | |
err = ioutil.WriteFile(cid+".jpg", photo, 0664) | |
if err != nil { | |
fmt.Printf("Error write photo: %+v", err) | |
return | |
} | |
} | |
func getString(card *scard.Card, cmd, req []byte) (resp string, err error) { | |
rawResp, err := getData(card, cmd, cmdReq) | |
if err != nil { | |
fmt.Printf("getString: %+v", err) | |
return resp, err | |
} | |
thResp, err := thaiToUnicode(rawResp) | |
if err != nil { | |
fmt.Printf("getString: %+v", err) | |
return resp, err | |
} | |
// Remove unused bytes | |
thResp = bytes.Trim(thResp, " ") | |
return string(thResp), err | |
} | |
func thaiToUnicode(data []byte) (out []byte, err error) { | |
decoder := charmap.Windows874.NewDecoder() | |
out, err = decoder.Bytes(data) | |
return out, err | |
} | |
func getData(card *scard.Card, cmd, req []byte) (resp []byte, err error) { | |
// Send cmd | |
_, err = card.Transmit(cmd) | |
if err != nil { | |
fmt.Printf("getData: %+v", err) | |
return resp, err | |
} | |
// Send select cmd | |
req = append(req, cmd[len(cmd)-1]) | |
resp, err = card.Transmit(req) | |
if err != nil { | |
fmt.Printf("getData: %+v", err) | |
return resp, err | |
} | |
// Remove unused bytes | |
resp = resp[:len(resp)-2] | |
return resp, err | |
} | |
func getPhoto(card *scard.Card, req []byte) (resp []byte, err error) { | |
for _, itemCmd := range cmdPhoto { | |
tmpArray, err := getData(card, itemCmd, req) | |
if err != nil { | |
fmt.Printf("getPhoto: %+v", err) | |
} | |
for _, tmpItem := range tmpArray { | |
resp = append(resp, tmpItem) | |
} | |
} | |
return resp, err | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment