2012/06/23

[felicalite]MAC生成の結果確認方法

MACって、ここ10年くらい食べてないなあ、のMACではない。

 

FeliCa Liteカードは、認証コマンドを持っていない。
その代わりに、MACレジスタというものを持っている。

機能は、1回のRead w/o Encriptionによって読み出したブロックのデータから値を計算するというものだ。
Read w/o Encription実行時には初期化されるので、注意が必要だ。
一般的な使い方としては、Read w/o Encriptionに読み出しブロックを指定するが、その最後にMACブロックにする、というものだ(Liteは同時に4ブロックまで指定できる)。

このMACブロックの値を、読み出した人が自分でも計算してみて、同じ値になったら「自分が発行したカードだ」と認識する、という片側認証するようになっているのだ。


よって、MACの計算アルゴリズムは公開されている。
いるのだが、なんだか難しい。。。
なんだかというか、私はけっこう時間がかかった。
それに、読み出した値と計算値が一致したとしても、それが偶然うまくいっているだけなのかどうかがよくわからなかった。

結局どうやって確認したかというと、ユーザーズマニュアル「6.4.8. MAC生成試験」というカードの動作確認項目があるので、これを試して一致したらOK、ということにした。
(実は、未だに計算には自信がない・・・)


私がAndroid向けに書いたのは、こんなコードだ。
Android向けといっても、携帯電話向けじゃない。
それに、私はJavaでのコーディングをほとんどしたことがないので、動けばいいや、といって作ったものなので、未だにきれいな実装方法がわかってない。

作ったのは昨年なので、確か動いていたはず…という記憶しかない。
間違ってたら、ごめんなさい、だ。

/**
* MAC計算
*
* @param mac MAC計算結果(先頭から8byte書く)。エラーになっても書き換える可能性あり。
* @param ck カード鍵(16byte)
* @param id ID(16byte)
* @param rc ランダムチャレンジブロック(16byte)
*
* @return true MAC計算成功
*/
private static boolean calcMac(byte[] mac, byte[] ck, byte[] id, byte[] rc) {
byte[] sk = new byte[16];
IvParameterSpec ips = null;

// 秘密鍵を準備
byte[] key = new byte[24];
for(int i=0; i<8; i++) {
key[i] = key[16+i] = ck[7-i];
key[8+i] = ck[15-i];
}

byte[] rc1 = new byte[8];
byte[] rc2 = new byte[8];
byte[] id1 = new byte[8];
byte[] id2 = new byte[8];
for(int i=0; i<8; i++) {
rc1[i] = rc[7-i];
rc2[i] = rc[15-i];
id1[i] = id[7-i];
id2[i] = id[15-i];
}

// RC[1]==(CK)==>SK[1]
ips = new IvParameterSpec(new byte[8]); //zero
int ret = enc83(sk, 0, key, rc1, 0, ips); //RC1-->SK1
if(ret != 8) {
Log.e(TAG, "calcMac: proc1");
return false;
}

// SK[1] =(iv)> RC[2] =(CK)=> SK[2]
ips = new IvParameterSpec(sk, 0, 8); //SK1
ret = enc83(sk, 8, key, rc2, 0, ips); //RC2-->SK2
if(ret != 8) {
Log.e(TAG, "calcMac: proc2");
return false;
}

/////////////////////////////////////////////////////////
for(int i=0; i<8; i++) {
key[i] = key[16+i] = sk[i];
key[8+i] = sk[8+i];
}

// RC[1] =(iv)=> ID[1] =(SK)=> tmp
ips = new IvParameterSpec(rc1, 0, 8); //RC1
ret = enc83(mac, 0, key, id1, 0, ips); //ID1-->tmp
if(ret != 8) {
Log.e(TAG, "calcMac: proc3");
return false;
}

// tmp =(iv)=> ID[2] =(SK)=> tmp
ips = new IvParameterSpec(mac); //tmp
ret = enc83(mac, 0, key, id2, 0, ips); //ID1-->tmp
if(ret != 8) {
Log.e(TAG, "calcMac: proc4");
return false;
}

for(int i=0; i<4; i++) {
byte swp = mac[i];
mac[i] = mac[7-i];
mac[7-i] = swp;
}

return true;
}


/**
* Triple-DES暗号化
*
* @param outBuf 暗号化出力バッファ(8byte以上)
* @param outOffset 暗号化出力バッファへの書き込み開始位置(ここから8byte書く)
* @param key 秘密鍵(24byte [0-7]KEY1, [8-15]KEY2, [16-23]KEY1)
* @param inBuf 平文バッファ(8byte以上)
* @param inOffset 平文バッファの読み込み開始位置(ここから8byte読む)
* @param ips 初期ベクタ(8byte)
*
* @return true 暗号化成功
*/
private static int enc83(byte[] outBuf, int outOffset, byte[] key, byte[] inBuf, int inOffset, IvParameterSpec ips) {
int sz = 0;
try {
// 秘密鍵を準備
SecretKeyFactory kf = SecretKeyFactory.getInstance("DESede");
DESedeKeySpec dk = new DESedeKeySpec(key);
SecretKey sk = kf.generateSecret(dk);
dk = null;
kf = null;

// 暗号
Cipher c = Cipher.getInstance("DESede/CBC/NoPadding");
c.init(Cipher.ENCRYPT_MODE, sk, ips);
sz = c.doFinal(inBuf, inOffset, 8, outBuf, outOffset);

} catch (Exception e) {
Log.e(TAG, "enc83 exception");
}

return sz;
}