0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【python】NFC Felica Lite-S 相互認証(外部認証)の実装

Last updated at Posted at 2024-10-13

内部認証の実装の記事に続けて、相互認証を実装していきます。相互認証は内部認証につづき外部認証を行うことですので、本記事では外部認証の実装を主に記載しています。(内部認証は以前の記事をご覧ください)

本記事では、Felica Lite-Sを主として扱っています。

前提

前回の内部認証の実装をベースとしています。
作成済みコードのみ記載しますが、詳細は記事をご覧ください。

felica_lite_s.py
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]


internal_authenticate.py
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つき書き込みを実装せよ

とのことです。
image.png
image.png

内部認証の実装

相互認証を実施するにあたって、内部認証で値を読み出す際に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の構造はこのようになっています。

image.png

リトルエンディアンは実装に関係がないです。

また、マニュアル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に特異的なものと思われます。
image.png

これを参考に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)

作成したコード

長くなるので、前提で作成したクラスに記載してある部分についてはカットします

felica_lite_s.py
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つきの書き込み、読み出しを実装したいです。

参考にしたサイト

今回参考にしたサイト(公式ドキュメント)

前提とた内部認証の実装で参考にした記事

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?