内部認証の実装の記事に続けて、相互認証を実装していきます。相互認証は内部認証につづき外部認証を行うことですので、本記事では外部認証の実装を主に記載しています。(内部認証は以前の記事をご覧ください)
本記事では、Felica Lite-Sを主として扱っています。
前提
前回の内部認証の実装をベースとしています。
作成済みコードのみ記載しますが、詳細は記事をご覧ください。
from pyDes import triple_des,CBC
import secrets
from nfc.tag.tt3 import Type3Tag,BlockCode,ServiceCode
sc_read = ServiceCode(0, 0x0b) # データを読み込むときに使います
sc_write = ServiceCode(0, 0x09) # データを書き込むときに使います
bc80 = BlockCode(0x80, service=0)
bc82 = BlockCode(0x82, service=0)
bc86 = BlockCode(0x86, service=0)
bc90 = BlockCode(0x90, service=0)
bc91 = BlockCode(0x91, service=0)
bc92 = BlockCode(0x92, service=0)
class MyFelicaLiteS(Type3Tag):
def __init__(self, clf, target):
"""
```
clf = nfc.ContactlessFrontend("usb")
tag:FelicaLiteS = clf.connect(rdwr={'on-connect': lambda tag: False})
tag:MyFelicaLiteS = MyFelicaLiteS(clf,tag.target)
```
"""
super().__init__(clf, target)
self.master_key = None
def add_masterKey(self,master_key):
self.master_key = master_key
def internal_authenticate(self):
assert self.master_key is not None, "Master key must be set using add_masterKey before using this method."
# 乱数RC = RC1[1]-[7] + RC2[1]-[7]
self.RC = secrets.token_bytes(16)
# ランダムチャレンジブロックにRCを書き込む
self.write_without_encryption([sc_write], [bc80], self.RC)
# ID,CKV,MAC_Aを同時に読み出す
# MAC_Aの値は最後に読み出す
block_list = [bc82, bc86, bc91]
ret = self.read_without_encryption([sc_read], block_list)
ID = ret[:16]
MAC_A_card = ret[32:40]
# カード鍵をIDから生成
CK = self.generate_CK(id= ID)
# Rとカード鍵からセッション鍵SKを作成
_sk = triple_des(CK[7::-1]+CK[:7:-1],CBC,b'\x00'*8).encrypt(self.RC[7::-1]+self.RC[:7:-1])
self.SK1, self.SK2 = _sk[7::-1],_sk[:7:-1]
# MAC_Aの生成
plain = b'\x82\x00\x86\x00\x91\x00\xFF\xFF'
MAC_A = self._generate_mac(plain, self.SK1, self.SK2, ret[:-16])
return MAC_A_card == MAC_A
def generate_CK(self,id):
zero = b"\x00" * 8
M = id
K = self.master_key
L = int.from_bytes(triple_des(K,CBC,zero).encrypt(zero),"big")
# ドキュメントより:左シフトとは、値を2倍しその最上位bitを捨てること
K1 = L << 1
K1 = K1 & 0xFFFFFFFFFFFFFFFF # 左方シフトにより64bitを超えた範囲は取り除く
if L >> 63 == 1:
K1 = K1 ^ 0x1B
M1 = M[:8]
M2_ = M[8:]
M2 = (int.from_bytes(M2_, "big") ^ K1).to_bytes(8,"big")
# C1 -> T,C1' -> T'の生成は同時に行なっています
M = M1 + M2
T = triple_des(K,CBC,zero).encrypt(M)[8:] # 6),7)
T_ = triple_des(K,CBC,b"\x80\00\00\00\00\00\00\00").encrypt(M)[8:]
C = T + T_
return C
def _generate_mac(self,plain,key1,key2,block_data):
"""_summary_
Args:
plain (bytes): 平文
key1 (bytes): ENC(暗号化)キー
key2 (bytes): DEC(複合化)キー
block_data (bytes): 生成に必要なデータのみとすること
Returns:
bytes: MACの値
"""
data = b"".join([block_data[i:i+8][::-1] for i in range(0,len(block_data),8)])
return triple_des(key1[::-1]+key2[::-1],CBC,self.RC[7::-1]).encrypt(plain[::-1] + data)[:-9:-1]
from test_class import MyFelicaLiteS
import nfc
from nfc.tag.tt3 import Type3Tag
from key import MASTER_KEY
clf = nfc.ContactlessFrontend("usb")
tag:Type3Tag = clf.connect(rdwr={'on-connect': lambda tag: False})
tag = MyFelicaLiteS(clf,tag.target)
tag.add_masterKey(master_key=MASTER_KEY)
print(tag.internal_authenticate())
相互認証(外部認証)の実装
マニュアルを頼りに実装していきます
処理の流れ
下記画像を簡単に要約すると、
- 内部認証時に
WCNT
の値を読み出せ -
WCNT
の値を利用し、STATEブロックに対するMACつき書き込みを実装せよ
内部認証の実装
相互認証を実施するにあたって、内部認証で値を読み出す際にWCNT
の値を読み出しておく必要があります。
# 内部認証
# WCNTを読み出す以外はinternal_authenticationと同じ
R = self._write_random_challenge_block()
# ID,CKV,WCNT,MAC_Aを同時に読み出す
# ここでWCNTを読み出しているのが唯一の変更点です
block_list = [bc82, bc86, bc90, bc91]
ret = self.read_without_encryption([sc_read], block_list)
ID = ret[:16]
WCNT = ret[32:36] # Felica Lint Lite-Sモードに対応し、4バイトとする
MAC_A_card = ret[48:56]
# カード鍵をIDから生成
CK = self.generate_CK(id= ID)
# Rとカード鍵からセッション鍵SKを作成
_sk = triple_des(CK[7::-1]+CK[:7:-1],CBC,b'\x00'*8).encrypt(self.RC[7::-1]+self.RC[:7:-1])
self.SK1, self.SK2 = _sk[7::-1],_sk[:7:-1]
# MAC_Aの生成
# ここも変更されています
plain = b'\x82\x00\x86\x00\x90\x00\x91\x00'
MAC_A = self._generate_mac(plain, self.SK1, self.SK2, ret[:-16])
外部認証の実装
WCNTとブロック番号とブロックデータからMACの生成
ここでのブロックとはSTATEブロックのことです。
# stateに書き込むデータの作成
state_data = b"\x01".ljust(16,b"\x00")
# WCNTとブロック番号からplainの生成
plain = WCNT.ljust(4,b"\x00") + bc92.number.to_bytes(1,"big") + b"\x00\x91\x00"
# plain,R,SK,ブロックデータからMACの生成
MAC_A_w = self._generate_mac(plain, self.SK2, self.SK1, state_data)
値の書き込み
MAC_Aの構造はこのようになっています。
リトルエンディアン
は実装に関係がないです。
また、マニュアル4.4.4 Write Without Encrypitonにこのように記載があります。
ブロック数が2の場合、2ブロック目のブロック番号が91h(MAC_A)に設定されていること
以上を踏まえてコードを作成します。
# MAC_Aブロックに書き込む値の作成
w_block_data = state_data + (MAC_A_w + WCNT).ljust(16, b'\x00')
self.write_without_encryption([sc_write], [bc92,bc91],w_block_data)
なお、外部認証の書き込みに失敗した場合には以下のエラーが発生します。
nfc.tag.tt3.Type3TagCommandError: verification failure for write with mac operation
状態の確認
認証に成功すると、STATEの値が変化します。
この項の内容は特にFelica Lite-S
に特異的なものと思われます。
これを参考にSTATEの値を読み取り状態を出力します。(この処理自体は認証処理には関係ありません。)
# state の値を確認
state = self.read_without_encryption([self.sc_read],[self.bc92])
state = state[0:2]
state_msg = f"外部(相互)認証: {'未' if state[0] == 0 else ''}完了\nPollingコマンド: {'非' if state[1] == 1 else ''}応答"
print(state_msg)
作成したコード
長くなるので、前提で作成したクラスに記載してある部分についてはカットします
class MyFelicaLiteS(Type3Tag):
def authenticate(self):
assert self.master_key is not None, "Master key must be set using add_masterKey before using this method."
# 乱数RC = RC1[1]-[7] + RC2[1]-[7]
self.RC = secrets.token_bytes(16)
# ランダムチャレンジブロックにRCを書き込む
self.write_without_encryption([sc_write], [bc80], self.RC)
# ID,CKV,WCNT,MAC_Aを同時に読み出す
# MAC_Aの値は最後に読み出す
block_list = [bc82, bc86, bc90, bc91]
ret = self.read_without_encryption([sc_read], block_list)
ID = ret[:16]
WCNT = ret[32:36] # Felica Lint Lite-Sモードに対応し、4バイトとする
MAC_A_card = ret[48:56]
# カード鍵をIDから生成
CK = self.generate_CK(id= ID)
# Rとカード鍵からセッション鍵SKを作成
_sk = triple_des(CK[7::-1]+CK[:7:-1],CBC,b'\x00'*8).encrypt(self.RC[7::-1]+self.RC[:7:-1])
self.SK1, self.SK2 = _sk[7::-1],_sk[:7:-1]
# MAC_Aの生成
plain = b'\x82\x00\x86\x00\x90\x00\x91\x00'
MAC_A = self._generate_mac(plain, self.SK1, self.SK2, ret[:-16])
if not MAC_A_card == MAC_A:
return False
# 外部認証
# stateに書き込むデータの作成
state_data = b"\x01".ljust(16,b"\x00")
# WCNTとブロック番号からplainの生成
plain = WCNT.ljust(4,b"\x00") + bc92.number.to_bytes(1,"big") + b"\x00\x91\x00"
# plain,R,SK,ブロックデータからMACの生成
MAC_A_w = self._generate_mac(plain, self.SK2, self.SK1, state_data)
# MAC_Aブロックに書き込む値の作成
w_block_data = state_data + (MAC_A_w + WCNT).ljust(16, b'\x00')
self.write_without_encryption([sc_write], [bc92,bc91],w_block_data)
return True
最後に
今回何気なくMACつき書き込みの処理が紛れ込んでいました。
次はMACつきの書き込み、読み出しを実装したいです。
参考にしたサイト
今回参考にしたサイト(公式ドキュメント)
前提とた内部認証の実装で参考にした記事