nfctester 0.0.35__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. nfctester/__init__.py +4 -0
  2. nfctester/cards/__init__.py +8 -0
  3. nfctester/cards/base_card.py +46 -0
  4. nfctester/cards/base_tag.py +25 -0
  5. nfctester/cards/mifare_classic.py +71 -0
  6. nfctester/cards/ntag21x.py +55 -0
  7. nfctester/cards/ntag224.py +111 -0
  8. nfctester/cards/type2tag.py +97 -0
  9. nfctester/crypto/__init__.py +5 -0
  10. nfctester/crypto/aes128.py +27 -0
  11. nfctester/crypto/base_crypto.py +26 -0
  12. nfctester/crypto/crypto1.py +163 -0
  13. nfctester/drivers/__init__.py +4 -0
  14. nfctester/drivers/card_reader.py +98 -0
  15. nfctester/drivers/pn532_hsu.py +304 -0
  16. nfctester/hardware/__init__.py +4 -0
  17. nfctester/hardware/base.py +33 -0
  18. nfctester/hardware/serial_transport.py +27 -0
  19. nfctester/main.py +6 -0
  20. nfctester/parsers/__init__.py +6 -0
  21. nfctester/parsers/base_parser.py +45 -0
  22. nfctester/parsers/mifare_classic_parser.py +53 -0
  23. nfctester/parsers/pn532_hsu_parser.py +128 -0
  24. nfctester/parsers/t2t_parser.py +47 -0
  25. nfctester/pn532_test.py +131 -0
  26. nfctester/tools/__init__.py +1 -0
  27. nfctester/tools/aes128_cli.py +29 -0
  28. nfctester/tools/pn532_scanner.py +74 -0
  29. nfctester/tools/pn532_transceive.py +77 -0
  30. nfctester/trace/__init__.py +6 -0
  31. nfctester/trace/decoder.py +56 -0
  32. nfctester/trace/formatter.py +92 -0
  33. nfctester/trace/handler.py +44 -0
  34. nfctester/trace/manager.py +90 -0
  35. nfctester/utils/__init__.py +4 -0
  36. nfctester/utils/bitops.py +87 -0
  37. nfctester/utils/crc.py +12 -0
  38. nfctester-0.0.35.dist-info/METADATA +87 -0
  39. nfctester-0.0.35.dist-info/RECORD +43 -0
  40. nfctester-0.0.35.dist-info/WHEEL +5 -0
  41. nfctester-0.0.35.dist-info/entry_points.txt +4 -0
  42. nfctester-0.0.35.dist-info/licenses/LICENSE +201 -0
  43. nfctester-0.0.35.dist-info/top_level.txt +1 -0
nfctester/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ from .trace.manager import trace
2
+ from .drivers.pn532_hsu import PN532_HSU
3
+
4
+ __all__ = ["trace", "PN532_HSU"]
@@ -0,0 +1,8 @@
1
+ from .base_card import BaseCard
2
+ from .base_tag import BaseTag
3
+ from .mifare_classic import MifareClassicCard
4
+ from .ntag21x import NTAG21x
5
+ from .ntag224 import NTAG224
6
+ from .type2tag import Type2Tag
7
+
8
+ __all__ = ["BaseCard", "BaseTag", "MifareClassicCard", "NTAG21x", "NTAG224", "Type2Tag"]
@@ -0,0 +1,46 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+
4
+ class BaseCard(ABC):
5
+ """
6
+ 加密卡基类 (如 Mifare Classic)
7
+ """
8
+
9
+ def __init__(self, reader, uid: bytes):
10
+ self.reader = reader
11
+ self.uid = uid
12
+
13
+ @abstractmethod
14
+ def authenticate(self, block_addr: int, key: bytes, key_type: int) -> bool:
15
+ """执行认证逻辑"""
16
+ pass
17
+
18
+ @abstractmethod
19
+ def read_block(self, block_addr: int) -> bytes:
20
+ """读取块数据"""
21
+ pass
22
+
23
+ @abstractmethod
24
+ def write_block(self, block_addr: int, data: bytes) -> bool:
25
+ """写入块数据"""
26
+ pass
27
+
28
+ @abstractmethod
29
+ def increment_block(self, block_addr: int, value: int) -> bool:
30
+ """递增块"""
31
+ pass
32
+
33
+ @abstractmethod
34
+ def decrement_block(self, block_addr: int, value: int) -> bool:
35
+ """递减块"""
36
+ pass
37
+
38
+ @abstractmethod
39
+ def restore_block(self, block_addr: int) -> bool:
40
+ """恢复块"""
41
+ pass
42
+
43
+ @abstractmethod
44
+ def transfer_block(self, block_addr: int) -> bool:
45
+ """传输块"""
46
+ pass
@@ -0,0 +1,25 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+
4
+ class BaseTag(ABC):
5
+ """
6
+ RFID 标签基类
7
+ """
8
+
9
+ def __init__(self, reader, uid: bytes):
10
+ self.reader = reader
11
+ self.uid = uid
12
+
13
+ def transceive(self, data: bytes) -> bytes:
14
+ """透传数据到读卡器"""
15
+ return self.reader.transceive(data)
16
+
17
+ @abstractmethod
18
+ def read_page(self, page_addr: int) -> bytes:
19
+ """读取页数据"""
20
+ pass
21
+
22
+ @abstractmethod
23
+ def write_page(self, page_addr: int, data: bytes) -> bool:
24
+ """写入页数据"""
25
+ pass
@@ -0,0 +1,71 @@
1
+ from .base_card import BaseCard
2
+
3
+
4
+ class MifareClassicCard(BaseCard):
5
+ """MIFARE Classic 完整操作实现"""
6
+
7
+ CMD_AUTHENT = 0x40
8
+ CMD_READ = 0x30
9
+ CMD_WRITE = 0xA0
10
+ CMD_INCREMENT = 0xC1
11
+ CMD_DECREMENT = 0xC0
12
+ CMD_RESTORE = 0xC2
13
+ CMD_TRANSFER = 0xB0
14
+
15
+ def __init__(self, reader, uid: bytes):
16
+ super().__init__(reader, uid)
17
+
18
+ def authenticate(self, block_addr: int, key: bytes, key_type: int = 0x60) -> bool:
19
+ """
20
+ MIFARE Classic 认证 (使用 PN532 硬件实现)
21
+ :param block_addr: 块地址
22
+ :param key: 6 字节密钥
23
+ :param key_type: 0x60 (KeyA) 或 0x61 (KeyB)
24
+ """
25
+ if len(key) != 6:
26
+ raise ValueError("Key must be 6 bytes")
27
+
28
+ # 使用读卡器提供的 exchange (InDataExchange 自动封装)
29
+ res = self.reader.exchange(bytes([key_type, block_addr]) + key + self.uid)
30
+ return res is not None
31
+
32
+ def increment_block(self, block_addr: int, value: int) -> bool:
33
+ """对块进行递增操作"""
34
+ if not (0 <= value < (1 << 32)):
35
+ raise ValueError("value must be a 32-bit unsigned integer")
36
+ cmd = bytes([self.CMD_INCREMENT, block_addr]) + value.to_bytes(4, "little")
37
+ res = self.reader.exchange(cmd)
38
+ return res is not None
39
+
40
+ def decrement_block(self, block_addr: int, value: int) -> bool:
41
+ """对块进行递减操作"""
42
+ if not (0 <= value < (1 << 32)):
43
+ raise ValueError("value must be a 32-bit unsigned integer")
44
+ cmd = bytes([self.CMD_DECREMENT, block_addr]) + value.to_bytes(4, "little")
45
+ res = self.reader.exchange(cmd)
46
+ return res is not None
47
+
48
+ def restore_block(self, block_addr: int) -> bool:
49
+ """恢复块的临时值"""
50
+ cmd = bytes([self.CMD_RESTORE, block_addr])
51
+ res = self.reader.exchange(cmd)
52
+ return res is not None
53
+
54
+ def transfer_block(self, block_addr: int) -> bool:
55
+ """将临时值写回块"""
56
+ cmd = bytes([self.CMD_TRANSFER, block_addr])
57
+ res = self.reader.exchange(cmd)
58
+ return res is not None
59
+
60
+ def read_block(self, block_addr: int) -> bytes:
61
+ """读取块数据"""
62
+ cmd = bytes([self.CMD_READ, block_addr])
63
+ return self.reader.exchange(cmd)
64
+
65
+ def write_block(self, block_addr: int, data: bytes) -> bool:
66
+ """写入块数据"""
67
+ if len(data) != 16:
68
+ raise ValueError("Data must be 16 bytes")
69
+ cmd = bytes([self.CMD_WRITE, block_addr]) + data
70
+ res = self.reader.exchange(cmd)
71
+ return res is not None
@@ -0,0 +1,55 @@
1
+ from .type2tag import Type2Tag
2
+
3
+
4
+ class NTAG21x(Type2Tag):
5
+ """
6
+ NXP NTAG21x 系列专用驱动 (如 NTAG213, NTAG215, NTAG216)
7
+ """
8
+
9
+ CMD_GET_VERSION = 0x60
10
+ CMD_PWD_AUTH = 0x1B
11
+
12
+ def __init__(self, reader, uid: bytes):
13
+ super().__init__(reader, uid)
14
+
15
+ def get_version(self) -> bytes:
16
+ """
17
+ 发送 0x60 指令,获取 8 字节版本信息
18
+ """
19
+ cmd = bytes([self.CMD_GET_VERSION])
20
+ return self.transceive(cmd)
21
+
22
+ def auth(self, password: bytes) -> bytes:
23
+ """
24
+ 发送 0x1B 指令进行密码
25
+ 认证认证流程说明:
26
+ 1. 发送 0x1B + 4字节 PWD。
27
+ 2. 如果 PWD 正确:芯片返回 2 字节的 PACK (Password Acknowledge)。
28
+ - PACK 的值存储在配置区的特定地址(如 NTAG213 的 Page 0x2C 的 Byte 0-1)。
29
+ - 默认值通常为 0x00 0x00,取决于生产商或之前的设置。
30
+ 3. 如果 PWD 错误:芯片返回 4-bit 的 NAK (通常为 0x0)。
31
+ - 注意:驱动会将 NAK 视为传输错误或超时,返回0字节数据包。
32
+
33
+ :param password: 4 字节密码
34
+ :return: 2 字节的 PACK 响应
35
+ """
36
+ if len(password) != 4:
37
+ raise ValueError("NTAG21x password must be 4 bytes")
38
+
39
+ # 1. 组装指令: 0x1B + 4字节密码
40
+ cmd = bytes([self.CMD_PWD_AUTH]) + password
41
+
42
+ # 2. 发送指令
43
+ # 注意:如果认证失败,芯片会返回 4-bit 的 NAK (0x0),
44
+ # 许多读写器驱动(如 PN532)会将 NAK 转换成通信超时或传输错误异常。
45
+ res = self.transceive(cmd)
46
+
47
+ # 3. 验证响应
48
+ if res is None or len(res) == 0:
49
+ raise PermissionError("Authentication failed: No response (NAK)")
50
+
51
+ # NTAG21x 认证成功会返回 2 字节的 PACK
52
+ if len(res) == 2:
53
+ return res
54
+ else:
55
+ raise PermissionError(f"Authentication failed: Unexpected response length {len(res)}")
@@ -0,0 +1,111 @@
1
+ import secrets
2
+ from .type2tag import Type2Tag
3
+ from nfctester.crypto import AES128Crypto
4
+ from nfctester.utils import BitOps
5
+ from nfctester.trace import trace
6
+
7
+
8
+ class NTAG224(Type2Tag):
9
+ """
10
+ NXP NTAG224 DNA 系列专用驱动
11
+ """
12
+
13
+ CMD_GET_VERSION = 0x60
14
+ CMD_PWD_AUTH_A = 0x1A
15
+ CMD_PWD_AUTH_A_RES = 0xAF
16
+ CMD_PWD_AUTH_B = 0xAF
17
+ CMD_PWD_AUTH_B_RES = 0x00
18
+
19
+ def __init__(self, reader, uid: bytes):
20
+ super().__init__(reader, uid)
21
+
22
+ def get_version(self) -> bytes:
23
+ """
24
+ 发送 0x60 指令,获取 8 字节版本信息
25
+ """
26
+ cmd = bytes([self.CMD_GET_VERSION])
27
+ return self.transceive(cmd)
28
+
29
+ def write_key(self, key: bytes):
30
+ """
31
+ 写入 16 字节 AES 密钥。
32
+ 根据 NTAG224 规范,密钥需以反向字节序写入 Page 0x40-0x43。
33
+ """
34
+ if len(key) != 16:
35
+ raise ValueError("AES key must be 16 bytes")
36
+
37
+ # 按照规范,字节序需要反转
38
+ reversed_key = key[::-1]
39
+ for i in range(4):
40
+ page_addr = 0x40 + i
41
+ chunk = reversed_key[i*4 : (i+1)*4]
42
+ self.write_page(page_addr, chunk)
43
+
44
+ def auth(self, password: bytes):
45
+ """
46
+ 发送 0x1A 指令进行密码认证
47
+ 认证流程说明:
48
+ 1. 发送 0x1A + 0x00。
49
+ 2. 接收 0xAF + 16byte ek(RndB)
50
+ 3. 发送 0xAF + 32byte ek(RndA || RndB')
51
+ 4. 接收 0x00 + 16byte ek(RndA')
52
+
53
+ :param password: 16 字节密码
54
+ """
55
+ if len(password) != 16:
56
+ raise ValueError("NTAG224 password must be 16 bytes")
57
+
58
+ crypto = AES128Crypto()
59
+
60
+ # 1. 获取加密随机数: 发送 0x1A + 0x00
61
+ cmd = bytes([self.CMD_PWD_AUTH_A, 0x00])
62
+ res = self.transceive(cmd)
63
+
64
+ if not res or res[0] != self.CMD_PWD_AUTH_A_RES or len(res) != 17:
65
+ raise PermissionError(f"Auth Step 1 failed: {res.hex() if res else 'No response'}")
66
+
67
+ ek_rndb = res[1:]
68
+ trace.debug(f"{'Received ek(RndB)':<25}: {ek_rndb.hex(' ').upper()}")
69
+
70
+ # 2. 解密 RndB 并生成 RndA
71
+ rndb = crypto.decrypt(ek_rndb, password)
72
+ rndb_prime = BitOps.rol(rndb)
73
+ trace.debug(f"{'Decrypted RndB':<25}: {rndb.hex(' ').upper()}")
74
+ trace.debug(f"{'Rotated RndB\'':<25}: {rndb_prime.hex(' ').upper()}")
75
+
76
+ # 调试用:固定值或伪随机数
77
+ # rnda = bytes.fromhex("00112233445566778899AABBCCDDEEFF")
78
+ rnda = secrets.token_bytes(16)
79
+ trace.debug(f"{'Generated RndA':<25}: {rnda.hex(' ').upper()}")
80
+
81
+ # 3. 加密 RndA || RndB' (使用 AES-128 ECB 模拟 CBC)
82
+ # Block 1: ek1 = AES_Encrypt(RndA)
83
+ ek1 = crypto.encrypt(rnda, password)
84
+ trace.debug(f"{'Encrypted Block 1 (ek1)':<25}: {ek1.hex(' ').upper()}")
85
+
86
+ # Block 2: ek2 = AES_Encrypt(RndB' ^ ek1)
87
+ xor_in = BitOps.xor(rndb_prime, ek1)
88
+ trace.debug(f"{'XOR Input for Block 2':<25}: {xor_in.hex(' ').upper()}")
89
+ ek2 = crypto.encrypt(xor_in, password)
90
+ trace.debug(f"{'Encrypted Block 2 (ek2)':<25}: {ek2.hex(' ').upper()}")
91
+
92
+ # 4. 发送 0xAF + ek1 + ek2
93
+ cmd = bytes([self.CMD_PWD_AUTH_B]) + ek1 + ek2
94
+ res = self.transceive(cmd)
95
+
96
+ if not res or res[0] != self.CMD_PWD_AUTH_B_RES or len(res) != 17:
97
+ raise PermissionError(f"Auth Step 2 failed: {res.hex() if res else 'No response'}")
98
+
99
+ ek_rnda_prime = res[1:]
100
+ trace.debug(f"{'Received ek(RndA\')':<25}: {ek_rnda_prime.hex(' ').upper()}")
101
+
102
+ # 5. 解密并验证 RndA'
103
+ # 根据 NTAG224 手册,此处解密使用 ECB 模式(或 IV 链重置)
104
+ rnda_prime_from_tag = crypto.decrypt(ek_rnda_prime, password)
105
+ expected_rnda_prime = BitOps.rol(rnda)
106
+
107
+ trace.debug(f"{'Decrypted RndA\'':<25}: {rnda_prime_from_tag.hex(' ').upper()}")
108
+ trace.debug(f"{'Expected RndA\'':<25}: {expected_rnda_prime.hex(' ').upper()}")
109
+
110
+ if rnda_prime_from_tag != expected_rnda_prime:
111
+ raise PermissionError("Authentication failed: RndA verification failed")
@@ -0,0 +1,97 @@
1
+ from .base_tag import BaseTag
2
+
3
+
4
+ class Type2Tag(BaseTag):
5
+ """
6
+ NFC Forum Type 2 Tag 标准指令集实现 (如 NTAG21x 系列)
7
+ """
8
+
9
+ CMD_READ = 0x30
10
+ CMD_WRITE = 0xA2
11
+
12
+ def __init__(self, reader, uid: bytes):
13
+ super().__init__(reader, uid)
14
+
15
+ def read_page(self, page_addr: int) -> bytes:
16
+ """
17
+ 读取页数据
18
+ T2T READ 指令会返回从 page_addr 开始的 16 字节 (即 4 个页的数据)
19
+ :param page_addr: 页地址
20
+ """
21
+ cmd = bytes([self.CMD_READ, page_addr])
22
+ res = self.transceive(cmd)
23
+ if not res:
24
+ raise RuntimeError(f"Type 2 Tag read_page(0x{page_addr:02X}) failed: No response from card")
25
+ return res
26
+
27
+ def write_page(self, page_addr: int, data: bytes):
28
+ """
29
+ 写入页数据 (T2T 规范每次写入 4 字节)
30
+ :param page_addr: 页地址
31
+ :param data: 4 字节待写入数据
32
+ """
33
+ if len(data) != 4:
34
+ raise ValueError("Type 2 Tag write_page requires exactly 4 bytes of data")
35
+
36
+ cmd = bytes([self.CMD_WRITE, page_addr]) + data
37
+ self.reader.set_crc(True, False)
38
+ res = self.transceive(cmd)
39
+ self.reader.set_crc(True, True)
40
+
41
+ if not res:
42
+ raise RuntimeError(f"Type 2 Tag write_page(0x{page_addr:02X}) failed: No response from card")
43
+ if res != b'\x0A':
44
+ raise RuntimeError(f"Type 2 Tag write_page(0x{page_addr:02X}) failed: NAK(0x{res.hex(' ').upper()}) from card")
45
+
46
+ def read_ndef(self) -> dict:
47
+ """
48
+ 读取 NDEF 信息
49
+ 逻辑:读取 Page 3 (CC) 获取容量,从 Page 4 开始解析 TLV 结构
50
+ """
51
+ # 1. 读取 CC (Page 3)
52
+ res = self.read_page(3)
53
+ cc = res[:4]
54
+ if cc[0] != 0xE1:
55
+ return {"cc": cc, "ndef": None}
56
+
57
+ # cc[2] 是数据区大小 (ML), 单位 8 字节
58
+ capacity = cc[2] * 8
59
+
60
+ # 2. 读取数据区并解析 TLV (从 Page 4 开始)
61
+ data = bytearray()
62
+ # 按照 4 页 (16 字节) 为单位批量读取
63
+ for p in range(4, 4 + (capacity // 4), 4):
64
+ data.extend(self.read_page(p))
65
+
66
+ ndef = None
67
+ ptr = 0
68
+ while ptr < len(data):
69
+ t = data[ptr]
70
+ if t == 0x00: # NULL TLV
71
+ ptr += 1
72
+ continue
73
+ if t == 0xFE: # Terminator TLV
74
+ break
75
+
76
+ # 解析 L (长度)
77
+ ptr += 1
78
+ if ptr >= len(data): break
79
+ l = data[ptr]
80
+ ptr += 1
81
+ if l == 0xFF: # 3 字节长度格式
82
+ if ptr + 1 >= len(data): break
83
+ l = (data[ptr] << 8) | data[ptr+1]
84
+ ptr += 2
85
+
86
+ # 识别 NDEF TLV (0x03)
87
+ if t == 0x03:
88
+ ndef = bytes(data[ptr : ptr + l])
89
+ break
90
+
91
+ ptr += l
92
+
93
+ return {
94
+ "cc": cc,
95
+ "capacity": capacity,
96
+ "ndef": ndef
97
+ }
@@ -0,0 +1,5 @@
1
+ from .base_crypto import BaseCrypto
2
+ from .aes128 import AES128Crypto
3
+ from .crypto1 import MifareCrypto1
4
+
5
+ __all__ = ["BaseCrypto", "AES128Crypto", "MifareCrypto1"]
@@ -0,0 +1,27 @@
1
+ from Crypto.Cipher import AES
2
+ from .base_crypto import BaseCrypto
3
+
4
+
5
+ class AES128Crypto(BaseCrypto):
6
+ """
7
+ AES-128 ECB 实现
8
+ """
9
+
10
+ def _validate_params(self, indata: bytes, key: bytes):
11
+ # 密钥长度:16 bytes
12
+ if len(key) != 16:
13
+ raise ValueError(f"AES-128 ECB 密钥为 16 字节,当前长度为 {len(key)}")
14
+
15
+ # 数据块长度:16 bytes
16
+ if len(indata) != 16:
17
+ raise ValueError(f"AES-128 ECB 数据为 16 字节,当前长度为 {len(indata)}")
18
+
19
+ def encrypt(self, indata: bytes, key: bytes) -> bytes:
20
+ self._validate_params(indata, key)
21
+ cipher = AES.new(key, AES.MODE_ECB)
22
+ return cipher.encrypt(indata)
23
+
24
+ def decrypt(self, indata: bytes, key: bytes) -> bytes:
25
+ self._validate_params(indata, key)
26
+ cipher = AES.new(key, AES.MODE_ECB)
27
+ return cipher.decrypt(indata)
@@ -0,0 +1,26 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ class BaseCrypto(ABC):
4
+ """
5
+ 加解密抽象基类
6
+ """
7
+
8
+ @abstractmethod
9
+ def encrypt(self, indata: bytes, key: bytes) -> bytes:
10
+ """
11
+ 加密数据
12
+ :param indata: 输入原始数据
13
+ :param key: 密钥
14
+ :return: 加密后的字节流
15
+ """
16
+ pass
17
+
18
+ @abstractmethod
19
+ def decrypt(self, indata: bytes, key: bytes) -> bytes:
20
+ """
21
+ 解密数据
22
+ :param indata: 输入加密数据
23
+ :param key: 密钥
24
+ :return: 解密后的原始字节流
25
+ """
26
+ pass
@@ -0,0 +1,163 @@
1
+ from .base_crypto import BaseCrypto
2
+
3
+ class MifareCrypto1(BaseCrypto):
4
+ """
5
+ Mifare Classic Crypto1 算法加解密实现类
6
+ 有状态的流加密引擎
7
+ """
8
+
9
+ def __init__(self):
10
+ self._state = None
11
+
12
+ # LFSR 奇数和偶数部分的反馈多项式掩码
13
+ LF_POLY_ODD = 0x29CE5C
14
+ LF_POLY_EVEN = 0x870804
15
+
16
+ @staticmethod
17
+ def prng_successor(x_int: int, steps: int) -> int:
18
+ """
19
+ Mifare PRNG 后继函数 (算法归口)
20
+ :param x_int: 当前 PRNG 值
21
+ :param steps: 步进次数
22
+ :return: 更新后的 PRNG 值
23
+ """
24
+ x = x_int
25
+ for _ in range(steps):
26
+ x = (x >> 1) | (((x >> 0) ^ (x >> 2) ^ (x >> 3) ^ (x >> 5)) & 1) << 31
27
+ return x & 0xFFFFFFFF
28
+
29
+ @staticmethod
30
+ def _bit(x: int, n: int) -> int:
31
+ """获取整数 x 的第 n 位值 (0 或 1)"""
32
+ return (x >> n) & 1
33
+
34
+ @staticmethod
35
+ def _parity(x: int) -> int:
36
+ """计算 LFSR 内部特定的奇偶校验位"""
37
+ x ^= x >> 16
38
+ x ^= x >> 8
39
+ x ^= x >> 4
40
+ return MifareCrypto1._bit(0x6996, x & 0xf)
41
+
42
+ @staticmethod
43
+ def _filter(x: int) -> int:
44
+ """
45
+ Crypto1 算法的 2 级非线性滤波函数(Filter Function)
46
+ 从当前 LFSR 中取出 20 bit,经过多重非线性布尔函数得出 1 bit 密钥流
47
+ """
48
+ f = (0xf22c0 >> (x & 0xf)) & 16
49
+ f |= (0x6c9c0 >> ((x >> 4) & 0xf)) & 8
50
+ f |= (0x3c8b0 >> ((x >> 8) & 0xf)) & 4
51
+ f |= (0x1e458 >> ((x >> 12) & 0xf)) & 2
52
+ f |= (0x0d938 >> ((x >> 16) & 0xf)) & 1
53
+ return MifareCrypto1._bit(0xEC57E80A, f)
54
+
55
+ class State:
56
+ """Crypto1 内部 LFSR 状态,模拟底层的硬件移位寄存器"""
57
+ def __init__(self, key: bytes = None, odd: int = None, even: int = None):
58
+ """
59
+ 初始化 Crypto1 状态
60
+ :param key: 6 字节 Mifare 密钥 (可选)
61
+ :param odd: 寄存器奇数部分状态 (可选)
62
+ :param even: 寄存器偶数部分状态 (可选)
63
+ """
64
+ self.odd = odd if odd is not None else 0
65
+ self.even = even if even is not None else 0
66
+
67
+ if key is not None:
68
+ # 将 6 bytes 密钥转为整数 (大端序)
69
+ key_int = int.from_bytes(key, byteorder='big')
70
+ self.odd = 0
71
+ self.even = 0
72
+ # 将 48 位 Mifare 密钥位按照大端与反转顺序交叉分离进入奇/偶寄存器中
73
+ # i 从 47 递减到 1 (步长为 2)
74
+ for i in range(47, -1, -2):
75
+ self.odd = (self.odd << 1) | MifareCrypto1._bit(key_int, (i - 1) ^ 7)
76
+ self.even = (self.even << 1) | MifareCrypto1._bit(key_int, i ^ 7)
77
+
78
+ def get_filter_bit(self) -> int:
79
+ """获取当前状态下的滤波输出位 (Keystream bit)"""
80
+ return MifareCrypto1._filter(self.odd)
81
+
82
+ def _shift(self, bit: int, feedback: bool = True) -> int:
83
+ """
84
+ 执行单次移位与混淆 (原子操作)
85
+ :param bit: 输入位 (用于参与反馈或作为输入)
86
+ :param feedback: 是否启用 LFSR 反馈逻辑
87
+ :return: 当前生成的密钥流 bit (ks_bit)
88
+ """
89
+ # 获取当前的滤波输出位作为密钥流位 (在移位前)
90
+ ks_bit = self.get_filter_bit()
91
+
92
+ # 基础反馈来自于多项式掩码与当前状态的奇偶校验
93
+ feedin = (MifareCrypto1.LF_POLY_ODD & self.odd) ^ (MifareCrypto1.LF_POLY_EVEN & self.even)
94
+ # 如果开启反馈(即加密/同步模式),则将输入位混入反馈回路
95
+ if feedback:
96
+ feedin ^= bit
97
+
98
+ # 推入反馈位并确保状态字截断在 32 位界限以内
99
+ new_even = ((self.even << 1) | MifareCrypto1._parity(feedin)) & 0xFFFFFFFF
100
+ self.odd, self.even = new_even, self.odd
101
+
102
+ return ks_bit
103
+
104
+ def initialize(self, key: bytes):
105
+ """
106
+ 重新实例化加密状态
107
+ :param key: 6 字节 Mifare 密钥
108
+ """
109
+ self._state = self.State(key=key)
110
+
111
+ def encrypt(self, indata: bytes, feedback: bool = True) -> bytes:
112
+ """
113
+ 加密数据
114
+ :param indata: 输入原始数据 (bytes)
115
+ :param feedback: 是否启用反馈 (认证 Token 计算时通常为 False)
116
+ :return: 加密后的字节流 (bytes)
117
+ """
118
+ if self._state is None:
119
+ raise RuntimeError("MifareCrypto1 state not initialized. Call initialize() first.")
120
+
121
+ out = bytearray()
122
+ for p_byte in indata:
123
+ c_byte = 0
124
+ # 在 Mifare 协议中,数据是逐 bit 处理的
125
+ for i in range(8):
126
+ # 1. 提取明文位并执行状态移位
127
+ p_bit = self._bit(p_byte, i)
128
+ ks_bit = self._state._shift(p_bit, feedback=feedback)
129
+
130
+ # 2. 与流密钥异或得出密文位
131
+ c_bit = p_bit ^ ks_bit
132
+ c_byte |= (c_bit << i)
133
+
134
+ out.append(c_byte)
135
+ return bytes(out)
136
+
137
+ def decrypt(self, indata: bytes, feedback: bool = True) -> bytes:
138
+ """
139
+ 解密数据
140
+ :param indata: 输入加密数据 (bytes)
141
+ :param feedback: 是否启用反馈 (解密通常需要反馈以保持同步)
142
+ :return: 解密后的原始字节流 (bytes)
143
+ """
144
+ if self._state is None:
145
+ raise RuntimeError("MifareCrypto1 state not initialized. Call initialize() first.")
146
+
147
+ out = bytearray()
148
+ for c_byte in indata:
149
+ p_byte = 0
150
+ for i in range(8):
151
+ # 1. 解密时先获取流密钥
152
+ ks_bit = self._state.get_filter_bit()
153
+
154
+ # 2. 异或密文位得出明文位
155
+ c_bit = self._bit(c_byte, i)
156
+ p_bit = c_bit ^ ks_bit
157
+ p_byte |= (p_bit << i)
158
+
159
+ # 3. 将明文位混入反馈状态中,保持状态机同步
160
+ self._state._shift(p_bit, feedback=feedback)
161
+
162
+ out.append(p_byte)
163
+ return bytes(out)
@@ -0,0 +1,4 @@
1
+ from .card_reader import CardReader
2
+ from .pn532_hsu import PN532_HSU
3
+
4
+ __all__ = ["CardReader", "PN532_HSU"]