PaSoRiとDelphiでNFCカード(NXP NTAG215)を読み書き ~Windowsサンプルコード集
本記事はNXP製NTAG215カード専用の解説です。
PaSoRi(RC-S380)を用いてWindows環境でNFCカード(NTAG215)を読み書きする方法を、Delphiのサンプルソースコードとともに紹介します。
winscard.dllやScardTransmitを利用した具体的な処理例を掲載しており、UIDは他のICカードでも取得可能ですが、本記事で扱う読み書き処理はNTAG215に特化しています。
パソコンでICカードを読み書きするためには非接触ICカードリーダー/ライターが必要になります。
私はアマゾンで中古のPaSoRi(RC-S380)を購入して使用しました。
PaSoRi(RC-S380)をパソコンにUSB接続する前に、
https://www.sony.co.jp/Products/felica/consumer/support/download/nfcportsoftware.html
から「NFCポートソフトウェア」をダウンロードしてダブルクリックしてインストールし、Windowsを再起動します。
その後、PaSoRi(RC-S380)をパソコンに接続しましょう。
ICタグ(NFCカード)は、Amazonで購入しました。
「NFCタグ」などで検索して2種類購入しました(タグの種類の説明はないので買わないとわからなかったです)が、2種類とも「NXP NTAG215」のNFCタグのICカードでした。
NFCは13.56MHzで動作するRFIDだそうです。
NXP NTAG215は504バイト(126ページ×4)の書き込みエリアがある
ページサイズ: 4バイト
メモリ構成
| ページ | 0バイト目 | 1バイト目 | 2バイト目 | 3バイト目 |
|---|---|---|---|---|
| 0 | UID | チェックサム | ||
| 1 | UID | |||
| 2 | チェックサム | Internal | lock bytes | lock bytes |
| 3 | Capability Container(CC) E1(NDEFが存在するマジックナンバー)、バージョン、NDEFメッセージのサイズ(×8)、00 |
|||
| 4 | User Memory | |||
| ... | ||||
| 129 | ||||
| 130 | Dynamic lock byte | RFUI | ||
| 131 | CFG0 | |||
| 132 | CFG1 | |||
| 133 | PWD | |||
| 134 | PACK | RFUI | ||
カードリーダー関連のWindows API 関数
- スマートカードリソースマネージャへの接続
-
Function SCardEstablishContext( dwScope : DWORD; pvReserved1 : Pointer; pvReserved2 : Pointer; phContext : PSCARDCONTEXT ):LONG; stdcall; external 'Winscard.dll';
- コンテキストの解放
-
Function SCardReleaseContext( hContext:SCARDCONTEXT ):LONG; stdcall; external 'Winscard.dll';
- カードリーダーのリスト取得
-
Function SCardListReadersW( hContext : SCARDCONTEXT; mszGroups:PWideChar; szReaders:PWideChar; pcchReaders:PDWORD ):LONG; stdcall; external 'Winscard.dll';
- カードの接続
-
Function SCardConnectW( hContext : SCARDCONTEXT; szReaders:PWideChar; dwShareMode : DWORD; dwPreferredProtocols : DWORD; phCard : PSCARDHANDLE; pdwActiveProtocols:PDWORD ):LONG; stdcall; external 'Winscard.dll';
- カードの切断
-
Function SCardDisconnect( hCard : SCARDHANDLE; dwDisposition :DWORD ):LONG; stdcall; external 'Winscard.dll';
- カードのステータス取得
-
Function SCardStatusW( hCard : SCARDHANDLE; szReaderNames :PWideChar; pcchReaderLen : PDWORD; pdwState :PDWORD; pdwProtocol :PDWORD; pbATR :PBYTE; pcbAtrLen :PDWORD ):LONG; stdcall; external 'Winscard.dll';
- サービス要求を送信し、カードからデータを受信する
-
Function SCardTransmit( hCard : SCARDHANDLE; pioSendPci :PSCARD_IO_REQUEST; pbSendBuffer :PByte; cbSendLength :DWORD; pioRecvPci :PSCARD_IO_REQUEST; pbRecvBuffer :PByte; pcbRecvLength:PDWORD ):LONG; stdcall; external 'Winscard.dll';
SCardTransmit()の命令
RFIDカードからUIDやデータを読んだり書いたりするには「SCardTransmit」API関数を使います。
「SCardTransmit」API関数の第3引数のバイト配列のポインタで命令を送ります。
-
0バイト目 CLA $FF 1バイト目 INS $CA 2バイト目 P1 $00 3バイト目 P2 $00 4バイト目 Le $00
- UIDを取得
- データを読み取り
| 0バイト目 | CLA | $FF |
| 1バイト目 | INS | $B0 |
| 2バイト目 | P1 | $00 |
| 3バイト目 | P2 | ページ(ブロック)番号 |
| 4バイト目 | Le | 読み取るバイト数 |
- データを更新(書き込み)
| 0バイト目 | CLA | $FF |
| 1バイト目 | INS | $D6 |
| 2バイト目 | P1 | $00 |
| 3バイト目 | P2 | ページ(ブロック)番号 |
| 4バイト目 | Lc | 更新バイト数 (NTAG215の場合は$04) |
| 5バイト目 | DATA0 | 更新データ |
| 6バイト目 | DATA1 | 更新データ |
| 7バイト目 | DATA2 | 更新データ |
| 8バイト目 | DATA3 | 更新データ |
Mifare Classic 1Kなどの場合は4セクター毎に認証が必要で、以下のような命令を使う必要がある
- 認証鍵の読み込み
| 0バイト目 | CLA | $FF |
| 1バイト目 | INS | $82 |
| 2バイト目 | P1 | $00 |
| 3バイト目 | P2 | キーナンバー($00など) |
| 4バイト目 | Lc | データ(キー)の長さ($06) |
| 5バイト目 | DATA0 | (例)$FF |
| 6バイト目 | DATA1 | (例)$FF |
| 7バイト目 | DATA2 | (例)$FF |
| 8バイト目 | DATA3 | (例)$FF |
| 9バイト目 | DATA4 | (例)$FF |
| 10バイト目 | DATA5 | (例)$FF |
- 認証
| 0バイト目 | CLA | $FF |
| 1バイト目 | INS | $86 |
| 2バイト目 | P1 | $00 |
| 3バイト目 | P2 | キーナンバー($00など) |
| 4バイト目 | Lc | データの長さ($05) |
| 5バイト目 | DATA0 | $01 |
| 6バイト目 | DATA1 | $00 |
| 7バイト目 | DATA2 | ブロック番号 |
| 8バイト目 | DATA3 | $60(KeyA) 又は $61(KeyB) |
| 9バイト目 | DATA4 | $00 |
フォーマット
RFIDカードに書き込む場合はNDEF(NFC Data Exchange Format)というデータフォーマットで書き込む必要があります。
| $03 | TLV開始のデータ | ||||||||||||||||
| $xx | NDEF内のバイト数 | ||||||||||||||||
| (例)$D1 |
|
||||||||||||||||
| (例)$01 | Type Length(タイプ フィールドの長さ) | ||||||||||||||||
| (例)$04 | ペイロードの長さ | ||||||||||||||||
| ID長さ(ILが0の場合は省略) | |||||||||||||||||
| (例)$54 | タイプ(Type Lengthの長さ分) ILが0の時は $54:テキスト $55:URI ・・・ | ||||||||||||||||
| ID(ILが0の場合は省略) | |||||||||||||||||
|
|||||||||||||||||
| $FE | TLV終了のデータ | ||||||||||||||||
プロジェクトを作成する
Delphiを起動し、メニューから「ファイル」⇒「新規作成」⇒「Windows VCLアプリケーション -Delphi(W)」 をクリックする。
TEdit×1個、TButton×5個、TMemo1×1個を配置します。
[ファイル]⇒[すべて保存]からプロジェクトとユニットの保存をします。
ユニットを追加する
[ファイル]⇒[新規作成]⇒[ユニット]をクリックして新規ユニットを追加します。
以下のソースコードをコピペして、「winscard.pas」として保存します。
unit winscard;
interface
uses Winapi.Windows,System.SysUtils;
type
SCARDCONTEXT=ULONG;
PSCARDCONTEXT=^SCARDCONTEXT;
SCARDHANDLE = ULONG;
PSCARDHANDLE = ^SCARDHANDLE;
SCARD_IO_REQUEST = Record
dwProtocol :DWORD;
cbPciLength :DWORD;
end;
PSCARD_IO_REQUEST=^SCARD_IO_REQUEST;
TSCard=class(TObject)
private
fReader:String;
fContext:SCARDCONTEXT;
fErr:LONG;
fErrStr:String;
fHCard:SCARDHandle;
fProtocol:DWORD;
fSendPci:SCARD_IO_REQUEST;
fATR:TBytes;
fCardName:String;
fReadBlockData:TBytes;
procedure GetCardName();
//「MIFARE Ultralight」や「NXP NTAG215」は1ブロック(4バイト)ずつしか更新できない
function UpdateBuffer4Byte( BlockNum:Byte;Data:TBytes ):Boolean;
//指定したDataを指定したBlockNumから更新する
function UpdateBuffer(Data:TBytes;BlockNum:Byte=4):Boolean;
public
constructor Create;
destructor Destroy();override;
//リーダーに接続
function ConnectReader():Boolean;
//ConnectReaderを呼んだ後、カードに接続
function ConnectCard():Boolean;
//ATRの取得
function GetATR():String;
//UIDの取得
function GetUID():String;
//指定したブロックの出データを読む
function ReadBlock(BlockNum:Byte):String;
//データを初期化する
function ClearAll():Boolean;
//文字を書き込む(更新する)
function UpdateString(WriteStr:String):Boolean;
property Err:LONG read fErr;
property ErrStr:String read fErrStr;
property Reader:String read fReader;
property CardName:String read fCardName;
property ATR:TBytes read fATR;
end;
//スマートカードリソースマネージャへの接続
Function SCardEstablishContext(
dwScope:DWORD;
pvReserved1:Pointer;
pvReserved2: Pointer;
phContext :PSCARDCONTEXT
):LONG; stdcall; external 'Winscard.dll';
//コンテキストの解放
Function SCardReleaseContext(
hContext:SCARDCONTEXT
):LONG; stdcall; external 'Winscard.dll';
////カードリーダーのリスト取得
Function SCardListReadersA(
hContext : SCARDCONTEXT;
mszGroups:PAnsiChar;
szReaders:PAnsiChar;
pcchReaders:PDWORD
):LONG; stdcall; external 'Winscard.dll';
////カードリーダーのリスト取得
Function SCardListReadersW(
hContext : SCARDCONTEXT;
mszGroups:PWideChar;
szReaders:PWideChar;
pcchReaders:PDWORD
):LONG; stdcall; external 'Winscard.dll';
//カードの接続
Function SCardConnectA(
hContext : SCARDCONTEXT;
szReaders:PAnsiChar;
dwShareMode : DWORD;
dwPreferredProtocols : DWORD;
phCard : PSCARDHANDLE;
pdwActiveProtocols:PDWORD
):LONG; stdcall; external 'Winscard.dll';
//カードの接続
Function SCardConnectW(
hContext : SCARDCONTEXT;
szReaders:PWideChar;
dwShareMode : DWORD;
dwPreferredProtocols : DWORD;
phCard : PSCARDHANDLE;
pdwActiveProtocols:PDWORD
):LONG; stdcall; external 'Winscard.dll';
//カードの切断
Function SCardDisconnect(
hCard : SCARDHANDLE;
dwDisposition :DWORD
):LONG; stdcall; external 'Winscard.dll';
//カードのステータス取得
Function SCardStatusA(
hCard : SCARDHANDLE;
szReaderNames :PAnsiChar;
pcchReaderLen : PDWORD;
pdwState :PDWORD;
pdwProtocol :PDWORD;
pbATR :PBYTE;
pcbAtrLen :PDWORD
):LONG; stdcall; external 'Winscard.dll';
Function SCardStatusW(
hCard : SCARDHANDLE;
szReaderNames :PWideChar;
pcchReaderLen : PDWORD;
pdwState :PDWORD;
pdwProtocol :PDWORD;
pbATR :PBYTE;
pcbAtrLen :PDWORD
):LONG; stdcall; external 'Winscard.dll';
//サービス要求を送信し、カードからデータを受信する
Function SCardTransmit(
hCard : SCARDHANDLE;
pioSendPci :PSCARD_IO_REQUEST;
pbSendBuffer :PByte;
cbSendLength :DWORD;
pioRecvPci :PSCARD_IO_REQUEST;
pbRecvBuffer :PByte;
pcbRecvLength:PDWORD
):LONG; stdcall; external 'Winscard.dll';
//指定したブロック(4Byte)のデータを読む
function ReadBlock(
hCard:SCARDHANDLE; SendPci:SCARD_IO_REQUEST;
BlockNum:Byte
):TBytes;
//指定したブロック(Blocknum=0が1ブロック目)にdataの先頭4バイトのみ書き込む
//1~4ブロック目は書き込めないようです
function UpdateBuffer4Byte(
hCard:SCARDHANDLE; SendPci:SCARD_IO_REQUEST; BlockNum:Byte;Data:TBytes
):Boolean;
//指定したブロックにdata(4の倍数バイト分)を書き込む
function UpdateBuffer(
hCard:SCARDHANDLE; SendPci:SCARD_IO_REQUEST; Data:TBytes; BlockNum:Byte=4
):Boolean;
//4ブロック目から文字を書く
function WreiteString(
hCard:SCARDHANDLE; SendPci:SCARD_IO_REQUEST;st:String
):Boolean;
const
//スマートカードの戻り値
ERROR_BROKEN_PIPE =$00000109;
SCARD_E_BAD_SEEK =$80100029;
SCARD_E_CANCELLED =$80100002;
SCARD_E_CANT_DISPOSE =$8010000E;
SCARD_E_CARD_UNSUPPORTED=$8010001C;
SCARD_E_CERTIFICATE_UNAVAILABLE=$8010002D;
SCARD_E_COMM_DATA_LOST =$8010002;
SCARD_E_DIR_NOT_FOUND =$80100023;
SCARD_E_DUPLICATE_READER=$8010001B;
SCARD_E_FILE_NOT_FOUND =$80100024;
SCARD_E_ICC_CREATEORDER =$80100021;
SCARD_E_ICC_INSTALLATION=$80100020;
SCARD_E_INSUFFICIENT_BUFFER=$80100008;
SCARD_E_INVALID_ATR =$80100015;
SCARD_E_INVALID_CHV =$8010002A;
SCARD_E_INVALID_HANDLE =$80100003;
SCARD_E_INVALID_PARAMETER=$80100004;
SCARD_E_INVALID_TARGET =$80100005;
SCARD_E_INVALID_VALUE =$80100011;
SCARD_E_NO_ACCESS =$80100027;
SCARD_E_NO_DIR =$80100025;
SCARD_E_NO_FILE =$80100026;
SCARD_E_NO_KEY_CONTAINER=$80100030;
SCARD_E_NO_MEMORY =$80100006;
SCARD_E_NO_PIN_CACHE =$80100033;
SCARD_E_NO_READERS_AVAILABLE=$8010002E;
SCARD_E_NO_SERVICE =$8010001D;
SCARD_E_NO_SMARTCARD =$8010000C;
SCARD_E_NO_SUCH_CERTIFICATE=$8010002C;
SCARD_E_NOT_READY =$80100010;
SCARD_E_NOT_TRANSACTED =$80100016;
SCARD_E_PCI_TOO_SMALL =$80100019;
SCARD_E_PIN_CACHE_EXPIRED=$80100032;
SCARD_E_PROTO_MISMATCH =$8010000F;
SCARD_E_READ_ONLY_CARD =$80100034;
SCARD_E_READER_UNAVAILABLE=$80100017;
SCARD_E_READER_UNSUPPORTED=$8010001A;
SCARD_E_SERVER_TOO_BUSY =$80100031;
SCARD_E_SERVICE_STOPPED =$8010001E;
SCARD_E_SHARING_VIOLATION=$8010000B;
SCARD_E_SYSTEM_CANCELLED=$80100012;
SCARD_E_TIMEOUT =$8010000A;
SCARD_E_UNEXPECTED =$8010001F;
SCARD_E_UNKNOWN_CARD =$8010000D;
SCARD_E_UNKNOWN_READER =$80100009;
SCARD_E_UNKNOWN_RES_MNG =$8010002B;
SCARD_E_UNSUPPORTED_FEATURE=$80100022;
SCARD_E_WRITE_TOO_MANY =$80100028;
SCARD_F_COMM_ERROR =$80100013;
SCARD_F_INTERNAL_ERROR =$80100001;
SCARD_F_UNKNOWN_ERROR =$80100014;
SCARD_F_WAITED_TOO_LONG =$80100007;
SCARD_P_SHUTDOWN =$80100018;
SCARD_S_SUCCESS =$00000000;
SCARD_W_CANCELLED_BY_USER=$8010006E;
SCARD_W_CACHE_ITEM_NOT_FOUND=$80100070;
SCARD_W_CACHE_ITEM_STALE=$80100071;
SCARD_W_CACHE_ITEM_TOO_BIG=$80100072;
SCARD_W_CARD_NOT_AUTHENTICATED=$8010006F;
SCARD_W_CHV_BLOCKED =$8010006C;
SCARD_W_EOF =$8010006D;
SCARD_W_REMOVED_CARD =$80100069;
SCARD_W_RESET_CARD =$80100068;
SCARD_W_SECURITY_VIOLATION=$8010006A;
SCARD_W_UNPOWERED_CARD =$80100067;
SCARD_W_UNRESPONSIVE_CARD=$80100066;
SCARD_W_UNSUPPORTED_CARD=$80100065;
SCARD_W_WRONG_CHV =$8010006B;
//SCardEstablishContext の dwScope
//データベース操作は、ユーザーのドメイン内で実行されます
SCARD_SCOPE_USER=0;
//コンテキストは現在の端末のコンテキストであり、
//データベース操作はすべてその端末のドメイン内で実行されます
//(呼び出し側アプリケーションには、データベース アクションに対する
// 適切なアクセス許可が必要です。)
SCARD_SCOPE_TERMINAL=1;
//コンテキストはシステム コンテキストであり、
//データベース操作はすべてシステムのドメイン内で実行されます
//(呼び出し側アプリケーションには、データベース アクションに対する
//適切なアクセス許可が必要です。)
SCARD_SCOPE_SYSTEM=2;
//SCardConnectA,SCardConnectW の dwShareMode
SCARD_SHARE_EXCLUSIVE =1;
SCARD_SHARE_SHARED =2;
SCARD_SHARE_DIRECT =3;
//SCardConnectA,SCardConnectW の dwPreferredProtocols
SCARD_PROTOCOL_UNDEFINED =$00000000;
SCARD_PROTOCOL_T0 =$00000001;
SCARD_PROTOCOL_T1 =$00000002;
SCARD_PROTOCOL_RAW =$00010000;
SCARD_PROTOCOL_DEFAULT =$80000000;
//SCardDisconnect の dwDisposition
SCARD_LEAVE_CARD =$00000000;
SCARD_RESET_CARD =$00000001;
SCARD_UNPOWER_CARD =$00000002;
SCARD_EJECT_CARD =$00000003;
//SCardTransmit の pioSendPci
SCARD_PCI_T0 : SCARD_IO_REQUEST = (dwProtocol:1; cbPciLength:8);
SCARD_PCI_T1 : SCARD_IO_REQUEST = (dwProtocol:2; cbPciLength:8);
implementation
function ReadBlock(
hCard:SCARDHANDLE; SendPci:SCARD_IO_REQUEST;
BlockNum:Byte
):TBytes;
var res:DWORD;
SendLen,RecvLen:DWORD;
SendBuf,RecvBuf:TBytes;
begin
SetLength(Result,4);
ZeroMemory(Result,4);
SendLen:=255;
SetLength(SendBuf,SendLen);
ZeroMemory(@SendBuf[0],SendLen);
SendBuf[0]:=$FF; //CLA
SendBuf[1]:=$B0; //INS FFB0:ReadBinary
SendBuf[2]:=$00; //P1
SendBuf[3]:=BlockNum;//P2
SendBuf[4]:=$04; //Le 読み取るバイト数
RecvLen:=255;
SetLength(RecvBuf,RecvLen);
ZeroMemory(@RecvBuf[0],RecvLen);
Res:=SCardTransmit(
hCard,@SendPci,
@SendBuf[0],SendLen,
nil,
@RecvBuf[0],@RecvLen
);
if Res=SCARD_S_SUCCESS then
begin
SetLength(RecvBuf,RecvLen);
if (RecvBuf[RecvLen-2]=$90) and (RecvBuf[RecvLen-1]=$00) then
begin
//16バイト(4Block)読み取ってるけど1ブロックのみ返す(4Byte)
move(Recvbuf[0],Result[0],4);
end;
end;
end;
function UpdateBuffer4Byte(
hCard:SCARDHANDLE; SendPci:SCARD_IO_REQUEST; BlockNum:Byte;Data:TBytes
):Boolean;
var len:Integer;
res:DWORD;
SendLen,RecvLen:DWORD;
SendBuf,RecvBuf:TBytes;
d:TBytes;
begin
result:=False;
len:=Length(Data);
if len=0 then exit;
if len>4 then len:=4;
SetLength(d,4);
ZeroMemory(d,4);
//CopyMemory(@d[0],@Data[0],len);
Move(Data[0],d[0],len);
SendLen:=9;
SetLength(SendBuf,SendLen);
SendBuf[0]:=$FF;//CLA
SendBuf[1]:=$D6;//INS FFD6:Update Binary
SendBuf[2]:=$00;//P1
SendBuf[3]:=BlockNum;//P2 ブロック番号4以上
SendBuf[4]:=$04;//Lc 書き込みバイト数4固定
SendBuf[5]:=d[0];//書き込みデータ
SendBuf[6]:=d[1];//書き込みデータ
SendBuf[7]:=d[2];//書き込みデータ
SendBuf[8]:=d[3];//書き込みデータ
RecvLen:=256;
SetLength(RecvBuf,RecvLen);
res:=SCardTransmit(
hCard,@SendPci,
@SendBuf[0],SendLen,
nil,
@RecvBuf[0],@RecvLen
);
if res=SCARD_S_SUCCESS then
begin
SetLength(RecvBuf,RecvLen);
if (RecvBuf[RecvLen-2]=$90) and (RecvBuf[RecvLen-1]=$00) then
result:=True;
end;
end;
function UpdateBuffer(
hCard:SCARDHANDLE; SendPci:SCARD_IO_REQUEST; Data:TBytes; BlockNum:Byte=4
):Boolean;
var i:Integer;
d:TBytes;
bn:Byte;
len:Integer;
begin
result:=False;
if Length(Data)=0 then exit;
SetLength(d,4);
for i := 0 to (Length(Data) div 4) do
begin
ZeroMemory(@d[0],4);
len:=Length(Data)-i*4;
if len>4 then len:=4;
Move(Data[i*4],d[0],len);
bn:=BlockNum+i;
result:=UpdateBuffer4Byte(hCard,SendPci,bn,d);
if result=false then break;
end;
end;
//4ブロック目から文字を書く
function WreiteString(
hCard:SCARDHANDLE; SendPci:SCARD_IO_REQUEST;st:String
):Boolean;
var arr,data:TBytes;
paylen:Byte;
i:Integer;
BlockNum:Byte;
begin
BlockNum:=4;
arr:=TEncoding.UTF8.GetBytes(st);
paylen:=Length(arr)+3;
SetLength(data,paylen+7);
data[0]:=$03;//NDEF識別子 TVL開始
data[1]:=paylen+4;
data[2]:=$D1;//MB ME SR TNF
data[3]:=$01;//レコードタイプ $00:Empty $01:Well-Known・・・
data[4]:=paylen;//ペイロードの長さ
data[5]:=Ord('T');//Text:$54
data[6]:=$02; //言語コードの長さ2
data[7]:=Ord('j');// j
data[8]:=Ord('a');// a
for i := Low(arr) to High(arr) do
data[9+i]:=arr[i];
data[9+Length(arr)]:=$FE;//TVLの終わり
result:=UpdateBuffer(
hCard,SendPci,
data,BlockNum
);
end;
{ TSCard }
function TSCard.ConnectCard: Boolean;
var ActiveProtocols:DWORD;
begin
result:=False;
if ConnectReader() then
begin
fErr:=SCardConnectW(
fContext,PChar(fReader),SCARD_SHARE_SHARED,
SCARD_PROTOCOL_T0 or SCARD_PROTOCOL_T1,@fHCard,@ActiveProtocols
);
if fErr=SCARD_S_SUCCESS then
begin
if (ActiveProtocols and SCARD_PROTOCOL_T1)>0 then
begin
fProtocol:=SCARD_PROTOCOL_T1;
fSendPci:=SCARD_PCI_T1;
end
else
begin
fProtocol:=SCARD_PROTOCOL_T0;
fSendPci:=SCARD_PCI_T0;
end;
result:=True;
end
else
begin
fErrStr:='カードが見つかりません';
//コンテキストの解放
SCardReleaseContext(fContext);
end;
end
else
begin
//コンテキストの解放
SCardReleaseContext(fContext);
end;
end;
function TSCard.ConnectReader: Boolean;
var Readers:String;
ReadersLen:DWORD;
begin
result:=False;
fReader:='';//リーダーの名前
fErr:=$00;
fErrStr:='';
//スマートカードリソースマネージャへの接続
fErr:=SCardEstablishContext(SCARD_SCOPE_USER,nil,nil,@fContext);
if fErr=SCARD_S_SUCCESS then
begin
ReadersLen:=255;
SetLength(Readers,ReadersLen);
//リーダーの一覧を取得する
fErr:=SCardListReadersW(fContext,nil,PChar(Readers),@ReadersLen);
if fErr=SCARD_S_SUCCESS then
begin
SetLength(Readers,ReadersLen);
//一覧の最初の1つのリーダーの名前を取得する
fReader:=Readers;
result:=True;
end;
end
else
begin
fErrStr:='カードリーダーが見つかりません';
//コンテキストの解放
SCardReleaseContext(fContext);
end;
end;
constructor TSCard.Create;
begin
fErr:=0;
fErrStr:='';
fReader:='';//リーダーの名前
end;
destructor TSCard.Destroy;
begin
inherited;
end;
function TSCard.GetATR: String;
var i:Integer;
AtrLen:DWORD;
ReadersLen,State:DWORD;
begin
Result:='';
if ConnectCard() then
begin
AtrLen:=32;
SetLength(fATR,AtrLen);
fErr:=SCardStatusW(
fHCard,PChar(fReader),@ReadersLen,@State,
@fProtocol,
@fATR[0],@AtrLen
);
if fErr=SCARD_S_SUCCESS then
begin
SetLength(fATR,AtrLen);
self.GetCardName();
for i := 0 to AtrLen-1 do
Result:=Result+IntToHex(fAtr[i],2);
Result:=Result+'('+fCardName+')';
end
else
begin
SetLength(fATR,0);
fErrStr:='ATR取得失敗';
end;
//カードの切断
SCardDisconnect( fHCard, SCARD_LEAVE_CARD );
//コンテキストの解放
SCardReleaseContext(fContext);
end;
end;
procedure TSCard.GetCardName();
begin
if Length(fATR)<15 then
fCardName:='UnKnown'
else if (fATR[13]=$00) and (fATR[14]=$01) then
fCardName:='MIFARE Classic 1K'
else if (fATR[13]=$00) and (fATR[14]=$02) then
fCardName:='MIFARE Classic 4K'
else if (fATR[13]=$00) and (fATR[14]=$03) then
fCardName:='MIFARE Ultralight'
else if (fATR[13]=$00) and (fATR[14]=$26) then
fCardName:='MIFARE Mini'
else if (fATR[13]=$00) and (fATR[14]=$3A) then
fCardName:='MIFARE Ultralight C'
else if (fATR[13]=$00) and (fATR[14]=$36) then
fCardName:='MIFARE Plus SL1 2k'
else if (fATR[13]=$00) and (fATR[14]=$37) then
fCardName:='MIFARE Plus SL1 4k'
else if (fATR[13]=$00) and (fATR[14]=$38) then
fCardName:='MIFARE Plus SL2 2k'
else if (fATR[13]=$00) and (fATR[14]=$39) then
fCardName:='MIFARE Plus SL2 4k'
else if (fATR[13]=$00) and (fATR[14]=$30) then
fCardName:='Topaz and Jewel'
else if (fATR[13]=$00) and (fATR[14]=$3B) then
fCardName:='FeliCa'
else if (fATR[13]=$FF) and (fATR[14]=$28) then
fCardName:='JCOP 30'
else if (fATR[13]=$00) and (fATR[14]=$07) then
fCardName:='SRIX'
else
fCardName:='UnKnown';
end;
function TSCard.GetUID: String;
var SendLen,RecvLen:DWORD;
SendBuf,RecvBuf:TBytes;
i:Integer;
begin
Result:='';
if ConnectCard() then
begin
SendLen:=5;
SetLength(SendBuf,SendLen);
SendBuf[0]:=$FF;//CLA
SendBuf[1]:=$CA;//INS
SendBuf[2]:=$00;//P1 $00:UIDを取得 $01:ATSを取得
SendBuf[3]:=$00;//P2
SendBuf[4]:=$00;//Le
RecvLen:=256;
SetLength(RecvBuf,RecvLen);
ZeroMemory(RecvBuf,RecvLen);
fErr:=SCardTransmit(
fHCard,@fSendPci,@SendBuf[0],SendLen,nil,@RecvBuf[0],@RecvLen
);
if fErr=SCARD_S_SUCCESS then
begin
SetLength(RecvBuf,RecvLen);
if (RecvBuf[RecvLen-2]=$90) and (RecvBuf[RecvLen-1]=$00) then
begin
for i := Low(RecvBuf) to High(RecvBuf)-2 do
begin
Result:=Result+IntToHex(RecvBuf[i],2);
end;
end
else
begin
fErrStr:=Format(
'SW1:%2.2x SW2:%2.2x',
[RecvBuf[RecvLen-2],RecvBuf[RecvLen-1]]
);
end;
end
else
begin
fErrStr:='UID取得失敗';
end;
//カードの切断
SCardDisconnect( fHCard, SCARD_LEAVE_CARD );
//コンテキストの解放
SCardReleaseContext(fContext);
end;
end;
function TSCard.ReadBlock(BlockNum: Byte): String;
var SendLen,RecvLen:DWORD;
SendBuf,RecvBuf:TBytes;
i:Integer;
begin
Result:='';
if ConnectCard() then
begin
SendLen:=5;
SetLength(SendBuf,SendLen);
ZeroMemory(SendBuf,SendLen);
SendBuf[0]:=$FF; //CLA
SendBuf[1]:=$B0; //INS FFB0:ReadBinary
SendBuf[2]:=$00; //P1
SendBuf[3]:=BlockNum;//P2
SendBuf[4]:=$10; //Le 読み取るバイト数
RecvLen:=256;
SetLength(RecvBuf,RecvLen);
ZeroMemory(RecvBuf,RecvLen);
fErr:=SCardTransmit(
fHCard,@fSendPci,@SendBuf[0],SendLen,nil,@RecvBuf[0],@RecvLen
);
if fErr=SCARD_S_SUCCESS then
begin
SetLength(RecvBuf,RecvLen);
if (RecvBuf[RecvLen-2]=$90) and (RecvBuf[RecvLen-1]=$00) then
begin
//「MIFARE Ultralight」や「NXP NTAG215」は必ず16Byte(4ブロック)読める
//4Byte(1ブロック分)のみ返す
SetLength(fReadBlockData,4);
for i := 0 to 3 do
begin
fReadBlockData[i]:=RecvBuf[i];
Result:=Result+IntToHex(RecvBuf[i],2);
end;
end
else
fErrStr:=Format(
'SW1:%2.2x SW2:%2.2x',
[RecvBuf[RecvLen-2],RecvBuf[RecvLen-1]]
);
end
else
fErrStr:='読み込み失敗';
//カードの切断
SCardDisconnect( fHCard, SCARD_LEAVE_CARD );
//コンテキストの解放
SCardReleaseContext(fContext);
end;
end;
function TSCard.UpdateBuffer(Data: TBytes; BlockNum: Byte): Boolean;
var i:Integer;
d:TBytes;
WriteBlockNum:Byte;
len:Integer;
begin
result:=False;
if Length(Data)=0 then exit;
SetLength(d,4);
for i := 0 to (Length(Data) div 4) do
begin
ZeroMemory(@d[0],4);
len:=Length(Data)-i*4;
if len>4 then len:=4;
Move(Data[i*4],d[0],len);
WriteBlockNum:=BlockNum+i;
result:=UpdateBuffer4Byte(WriteBlockNum,d);
if result=false then break;
end;
end;
function TSCard.UpdateBuffer4Byte(BlockNum: Byte; Data: TBytes): Boolean;
var len:Integer;
d:TBytes;
SendLen,RecvLen:DWORD;
SendBuf,RecvBuf:TBytes;
begin
Result:=false;
if ConnectCard() then
begin
len:=Length(Data);
if len=0 then exit;
if len>4 then len:=4;
SetLength(d,4);
ZeroMemory(d,4);
Move(Data[0],d[0],len);
SendLen:=9;
SetLength(SendBuf,SendLen);
SendBuf[0]:=$FF;//CLA
SendBuf[1]:=$D6;//INS FFD6:Update Binary
SendBuf[2]:=$00;//P1
SendBuf[3]:=BlockNum;//P2 ブロック番号4以上
SendBuf[4]:=$04;//Lc 書き込みバイト数4固定
SendBuf[5]:=d[0];//書き込みデータ
SendBuf[6]:=d[1];//書き込みデータ
SendBuf[7]:=d[2];//書き込みデータ
SendBuf[8]:=d[3];//書き込みデータ
RecvLen:=256;
SetLength(RecvBuf,RecvLen);
fErr:=SCardTransmit(
fHCard,@fSendPci,
@SendBuf[0],SendLen,
nil,
@RecvBuf[0],@RecvLen
);
if fErr=SCARD_S_SUCCESS then
begin
SetLength(RecvBuf,RecvLen);
if (RecvBuf[RecvLen-2]=$90) and (RecvBuf[RecvLen-1]=$00) then
result:=True;
end
else
begin
fErrStr:=Format(
'SW1:%2.2x SW2:%2.2x',
[RecvBuf[RecvLen-2],RecvBuf[RecvLen-1]]
);
end;
//カードの切断
SCardDisconnect( fHCard, SCARD_LEAVE_CARD );
//コンテキストの解放
SCardReleaseContext(fContext);
end;
end;
function TSCard.UpdateString(WriteStr: String): Boolean;
var arr,data:TBytes;
paylen:Byte;
i:Integer;
begin
arr:=TEncoding.UTF8.GetBytes(WriteStr);
paylen:=Length(arr)+3;
SetLength(data,paylen+7);
data[0]:=$03;//NDEF識別子 TVL開始
data[1]:=paylen+4;
data[2]:=$D1;//MB ME SR TNF
data[3]:=$01;//レコードタイプ $00:Empty $01:Well-Known・・・
data[4]:=paylen;//ペイロードの長さ
data[5]:=Ord('T');//Text:$54 Uri:$55
data[6]:=$02; //言語コードの長さ2
data[7]:=Ord('j');// j
data[8]:=Ord('a');// a
for i := Low(arr) to High(arr) do
data[9+i]:=arr[i];
data[9+Length(arr)]:=$FE;//TVLの終わり
result:=UpdateBuffer(data);
end;
function TSCard.ClearAll: Boolean;
var i:Integer;
Data:TBytes;
begin
//0~3ブロック ロック
//4~129ブロック 利用可能
//130~134ブロック ロック
SetLength(Data,4);
Data[0]:=$03;
Data[1]:=$00;
Data[2]:=$FE;
Data[3]:=$00;
Result:=Self.UpdateBuffer4Byte(4,Data);
if Result then
begin
Data[0]:=$00;
Data[1]:=$00;
Data[2]:=$00;
Data[3]:=$00;
for i := 5 to 129 do
begin
Result:=Self.UpdateBuffer4Byte(i,Data);
if Result=False then Break;
end;
end;
end;
end.
フォームのソースコードの入力
Form1のOnCreate、OnDestroy、Button1のOnClickなどに以下のソースコードを記述します。
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls ,winscard;
type
TForm1 = class(TForm)
Button1: TButton;
Memo1: TMemo;
Button2: TButton;
Button3: TButton;
Button4: TButton;
Edit1: TEdit;
Button5: TButton;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure Button4Click(Sender: TObject);
procedure Button5Click(Sender: TObject);
private
{ Private 宣言 }
SCard:TSCard;
public
{ Public 宣言 }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
//ATRを読む
Memo1.Lines.Add('ATR:'+SCard.GetATR);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
//UIDを読む
Memo1.Lines.Add( 'UID:'+SCard.GetUID() );
end;
procedure TForm1.Button3Click(Sender: TObject);
var i:Integer;
begin
//「NXP NTAG215」の容量は540バイト(4Byte × 135ブロック)
//全ブロックのメモリを読む
for i := 0 to 134 do
begin
Memo1.Lines.Add(
Format('%2.2x:',[i])+SCard.ReadBlock(i)
);
end;
end;
procedure TForm1.Button4Click(Sender: TObject);
begin
if SCard.ClearAll() then
Memo1.Lines.Add('データのクリア成功')
else
Memo1.Lines.Add('データのクリア失敗');
end;
procedure TForm1.Button5Click(Sender: TObject);
begin
if SCard.UpdateString(Edit1.Text) then
Memo1.Lines.Add('文字列の書き込み成功')
else
Memo1.Lines.Add('文字列の書き込み失敗')
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
SCard:=TSCard.Create;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
SCard.Free;
end;
end.
実行
PaSoRi(RC-S3800)をパソコンに接続し、ICタグを載せます。
Delphi IDEの「実行」ボタンを押して実行します。
Button1をクリックすると、カードのATRとカードの種類が表示されます。
Button2をクリックすると、カードのUIDが表示されます。
Button3をクリックすると、ICカードの0ブロック目から134ブロック目(全135ブロック、520Byte)を読み込んで4Byteずつ表示します。
Button4をクリックすると4ブロック目~129ブロック目のデータをクリアします。(数秒時間がかかります)
Edit1に「こんにちは」などICカードに入れたい文字列を設定し、 Button5をクリックするとICカードに文字列を書き込みます。
「こんにちは」と書き込んだカードをスマホの背面にかざします。
