
第3回 応用編!実際にデータを交換してみた
第2回では、Android Studioのサンプルアプリケーションを使用して、HCEの通信を動作させてみました。
さて、今回は前回使用したサンプルアプリケーションに手を加え、より複雑なデータのやり取りをしてみたいと思います。
準備するもの
- NFC R/Wに対応したAndroid端末(Android 4.4以降)
- NFC HCEに対応したAndroid端末
- Android StudioをインストールしたPC
通信データ
今回は、CardReader側でCardEmuration側から読み取ったデータが信頼できるものかどうかの照合をする、認証のシーケンスを実現したいと思います。
やり取りするデータは、「カード番号」と「暗号鍵」、そしてこれらから生成した「Hmac値(≒ハッシュ値)」です。詳細は後ほど説明していきます。
さらに、CardEmurationとCardReader間の通信は、ISO7816を意識した内容にしたいと思います。
ISO7816とは、CardとReader間の通信について定めた規格です。Cardからデータを読み取る際、ReaderからCardに要求コマンドを送信しますが、このCardに送るコマンドの形式に全カード共通のお約束事を設けたものが、ISO7816-4です。
今回は簡単にしか触れませんが、ISO7816-4についてはこちらに詳しい情報が記載されています。
以下がReaderからCardへの要求コマンドの形式(ヘッダ部分)です。

要求コマンドはヘッダ+ボディの形式から成り、ヘッダ内のINSの部分に要求の種別を現す値を指定します。INSに指定できる要求種別には、次のようなものがあります。

上記はほんの一部です。
今回は、3つの要求種別を使用して、CardReaderとCardEmuration間で以下のような通信を行いました。
(厳密にはISO7816の規則に反する部分があるかもしれません。あくまでそれっぽい雰囲気ということで、ご了承ください)

1-1/1-2で、CardEmuraion側からカード番号を取得します。(本当はここではカード番号を取り出すためのFileをセレクトするまでに留めたいところですが、今回はお試しという事で、カード番号も取り出してしまいます)
2-1/2-2で、CardReader側からCardEmuration側へ、暗号鍵を送信します。CardEmuration側はカード番号と、受信した暗号鍵からHmacを生成します。(ここでも本当は、1-1/1-2で指定したFileの情報を取り出すための認証を行ないたいところなのですが、今回は認証ではなく3-2での応答に使うHMAC生成キーワードの受け取りまでに留めてます。)
3-1/3-2で、CardReaderがCardEmurationからHmac値を受信、CardReader側で生成したHmac値と一致するかのデータ照合を行います。(本当は1-1/1-2のセレクト、2-1/2-2の認証を経て、ここで初めてカード番号が取れるのが理想的ですね。)
実際にやってみた様子がこちらです。
ソースコードの変更点&ポイント
今回のソースコードはGit Hubに登録していますので、具体的なコード内容を確認したい方は以下より取得してください。
- Card Emuration側
- Card Reader側
ここではソースコードの変更ポイントについて簡単に説明しておきます。
Card Emulation側のポイント
前回CardEmuration側のポイントとして説明した、processCommandApdu()に変更を加えました。
processCommandApdu()抜粋
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
@Override public byte[] processCommandApdu(byte[] commandApdu, Bundle extras) { //SELECT ADPU受信時 if(byteEquals(commandApdu, HexStringToByteArray(SELECT_APDU_HEADER), HexStringToByteArray(SELECT_APDU_HEADER).length)) { ・・・・・・ //カード番号応答処理を実装 } //INTERNAL AUTH受信時 else if(byteEquals(commandApdu, HexStringToByteArray(INT_AUTH_HEADER), HexStringToByteArray(INT_AUTH_HEADER).length)) { ・・・・・・ //暗号鍵を受信し応答を返す処理を実装 } //READ_BINARY受信時 else if(byteEquals(commandApdu, HexStringToByteArray(READ_BIN_HEADER), HexStringToByteArray(READ_BIN_HEADER).length)) { d binary"); ・・・・・・ //カード番号と暗号鍵から生成したHmac値を返す処理を実装 } |
processCommandApdu()はCardReader側と通信が確立してReader側から要求を受けたときにコールされるメソッドです。commandAdpuの中に、ADPUデータが一式詰まっています。このcommandAdpuのヘッダ(CLA/INS/P1/P2)の内容から要求種別を判断し、要求に応じた処理を実装します。
Card Reader側のポイント
CardReader側では、サンプルアプリケーションで既にISO7816-4に準拠したCardEmuration側への要求コマンド送信処理が存在するため、この部分を拡張し、CardEmuration側への要求を追加しました。
onTagDiscoverd()抜粋
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
private static final String INT_AUTH_HEADER = "00880000"; private static final String INT_AUTH_KEY ="7788"; @Override public void onTagDiscovered(Tag tag) { ・・・・・・ byte[] command_auth = BuildIntAuthApdu(INT_AUTH_KEY); Log.i(TAG, "Sending: " + ByteArrayToHexString(command_auth)); byte[] result_auth = isoDep.transceive(command_auth); Log.i(TAG, "INT Auth Seq Received:" +ByteArrayToHexString(result_auth)); int resultLength_auth = result_auth.length; byte[] statusWord_auth = {result_auth[resultLength_auth-2], result_auth[resultLength_auth-1]}; ・・・・・・ } public static byte[] BuildIntAuthApdu(String key) { // Format: [CLASS | INSTRUCTION | PARAMETER 1 | PARAMETER 2 | Lc field | DATA | Le field] return HexStringToByteArray(INT_AUTH_HEADER + String.format("%02X", key.length() / 2) + key + "05"); } |
上記は、シーケンス図の2-1部分を抜粋したものです。BuildIntAuthAdpu()メソッドでINTERNAL_AUTHENTICATIONのヘッダ情報と暗号鍵(INT_AUTH_KEY)を合わせ要求コマンドを生成し、tranceive()でCardEmuration側に送信しています。
データ照合
Hmac値の生成には、hmacSHA256メソッドを使用しました。CardReader側、CardEmuraion側両者に同じ暗号アルゴリズムを持たせ、カード番号と暗号鍵をもとにHmac値を生成しています。CardReader側で両者のHmac値が一致するかの照合を行い、一致した場合には、認証OKとなります。今回は認証OK時の効果音も鳴らしてみました。
まとめ
ISO7816を意識したため、少々こむずかしい内容となってしまいましたが、いかがだったでしょうか?認証シーケンスという決済っぽい雰囲気を味わうことができたのではないでしょうか。
次回は最終編として、カードの残高情報など、やりとりするデータをさらに拡張し、小さな決済システムを作ってみたいと思います!