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;
}
お時間を取っていただき、ありがとうございました。
返信削除ソースを拝見すると、確かに正順/逆順の扱いが、私のものと一致していました(排他的論理和の処理部分はついていけませんでした)。
これで一安心です。本当にありがとうございました。
あつかましいのですが、もう一点質問させてください。
<個別化カード鍵 標準生成アルゴリズム>についてです。
これは、テクニカルノートに書かれている
"カード内の処理"ではないので、素直にアルゴリズムに従って生成すればいいですよね?
今回、どうしても標準アルゴリズムを使用する必要があるのですが、正解がないため非常に不安に思っています。
個別化マスター鍵 (24 バイト)とID ブロックの値 (16 バイト)がこれこれであれば、生成結果はこうなる、
というようなデータをお持ちであれば、教えていただけないでしょうか。
どうかよろしくお願いいたします。
個別化カード鍵については、私も同じで、アルゴリズム通りに実装したつもりではあるのですが、答え合わせせずに放置しています。
返信削除業務でしたら、SONYさんに問い合わせるなどしたほうがよいかもしれません。
早々にお返事、ありがとうございます。
削除以前、SONYさんには問い合わせをしたのですが、
取引がないので、教えてもらえませんでした。
でも、今回、MACが正しく生成できたことで、
TDESの方法や、排他的論理和のやり方などは
正しくできているということがわかり、
カード鍵に関しても少し自信が持てました。
いろいろありがとうございました。
そうなんですか・・・私も残念です。
返信削除動作確認できていない私の実装はこちらに置いていますので、よろしければどうぞ(C#です)。
https://github.com/hirokuma/NfcStarterKitWrap/blob/master/NfcStarterKitWrap/NfcStarterKitWrap/FelicaLite.cs
かさねがさねご親切にありがとうございます。
削除C++で組んでいますので、実行させる方法はわかりませんが
ソースを拝見して、私のに間違いがないか検証させていただきたいと思います。
大変助かります。ありがとうございました。
先日はいろいろご指導ありがとうございました。
削除大変遅くなりましたが、ソース拝見させていただきました。
626行~631行
byte[] id1 = new byte[8]; //M1
byte[] id2 = new byte[8]; //M2
for(int i = 0; i < 8; i++) {
id1[i] = id[7 - i];
id2[i] = (byte)(id[15 - i] ^ enc1[i]);
}
ここで、IDブロックの値を逆順にされていますが、
これは必要なのでしょうか?
もしお時間ありましたら、ご教授のほど、よろしくお願いいたします。
この部分、まったく記憶にありません。
返信削除なぜ逆順にしたんでしょうね。。。。。
「FeliCa Liteに関するソフトウェア開発テクニカルノート」の「1.7. DES演算の入出力データの並びについて」を読んで、何が何でも逆順にしないと、と思って機械的に変更しただけかもしれません。
特に必要性があったという記憶はないです。
お返事、ありがとうございます。
削除実際の運用時にKEYの不一致が発覚した場合に
また考えることにします。
いろいろすみませんでした。