"""Hitag2 cipher + software-sim transponder/reader. Hardware-free.""" import asyncio import pytest from pm3py.sim import SoftwareMedium, Hitag2Tag, Hitag2Reader from pm3py.transponders.lf.nxp.hitag2 import DEFAULT_KEY from pm3py.transponders.lf.nxp.hitag2_crypto import ( hitag2_init, keystream, authenticator, Hitag2Cipher, ) UID = b"\x11\x22\x33\x44" def run(coro): return asyncio.new_event_loop().run_until_complete(coro) # --- The MIKRON golden vector pins the cipher to the Proxmark reference (hitag2_gen_nRaR.py) --- class TestHitag2Cipher: def test_mikron_keystream(self): ks, _ = keystream(hitag2_init(0x4F4E4D494B52, 0x49435769, 0x656E4572), 32) assert ks == 0xD7237FCE def test_mikron_authenticator(self): assert authenticator(0x4F4E4D494B52, 0x49435769, 0x656E4572) == 0xD7237FCE def test_mikron_16_bytes(self): ks, _ = keystream(hitag2_init(0x4F4E4D494B52, 0x49435769, 0x656E4572), 128) assert ks.to_bytes(16, "big").hex().upper() == "D7237FCE8CD037A95749C1E648008AB6" def test_transcrypt_roundtrip(self): a = Hitag2Cipher(0x4F4E4D494B52, 0x49435769, 0x656E4572) b = Hitag2Cipher(0x4F4E4D494B52, 0x49435769, 0x656E4572) a.bits(32) # advance both past aR, identically b.bits(32) data = b"\xDE\xAD\xBE\xEF" enc = a.transcrypt(data) assert enc != data assert b.transcrypt(enc) == data # b decrypts what a encrypted async def _setup(**tag_kw): m = SoftwareMedium() tag = Hitag2Tag(uid=UID, key=DEFAULT_KEY, data=[b"AAAA", b"BBBB", b"CCCC", b"DDDD"], **tag_kw) await m.attach(tag) return m, tag, Hitag2Reader(m, key=DEFAULT_KEY) class TestHitag2Sim: def test_request_uid(self): async def go(): _, _, r = await _setup() return await r.request_uid() assert run(go()) == UID def test_crypto_auth_success(self): async def go(): _, _, r = await _setup(config=0x06) return await r.crypto_auth() page3 = run(go()) assert page3 is not None assert page3[0] == 0x06 # decrypted config byte def test_crypto_auth_wrong_key(self): async def go(): m, _, _ = await _setup() bad = Hitag2Reader(m, key=bytes(6)) return await bad.crypto_auth() assert run(go()) is None def test_crypto_read_page(self): async def go(): _, _, r = await _setup() await r.crypto_auth() return await r.read_page(4) assert run(go()) == b"AAAA" # transcrypt lockstep decrypts correctly def test_crypto_write_then_read(self): async def go(): _, _, r = await _setup() await r.crypto_auth() ok = await r.write_page(5, b"WXYZ") return ok, await r.read_page(5) ok, val = run(go()) assert ok is True and val == b"WXYZ" def test_read_requires_auth(self): async def go(): _, _, r = await _setup() await r.request_uid() # no auth return await r.read_page(4) assert run(go()) is None def test_password_auth(self): async def go(): _, _, r = await _setup(config=0x06) return await r.password_auth(DEFAULT_KEY[0:4]) # page 1 = key[0:4] page3 = run(go()) assert page3 is not None and page3[0] == 0x06 def test_uid_is_read_only(self): async def go(): _, tag, r = await _setup() await r.crypto_auth() await r.write_page(0, b"\xFF\xFF\xFF\xFF") # must be rejected return tag.uid assert run(go()) == UID