mirror of
https://github.com/RfidResearchGroup/ChameleonUltra.git
synced 2026-03-29 12:59:59 +00:00
110 lines
3.7 KiB
Python
110 lines
3.7 KiB
Python
import re
|
|
|
|
LFSR48_FILTER_A = 0x9E98
|
|
LFSR48_FILTER_B = 0xB48E
|
|
LFSR48_FILTER_C = 0xEC57E80A
|
|
LFSR48_POLY = 0xE882B0AD621
|
|
U8_TO_ODD4 = [((i & 0x80) >> 4) + ((i & 0x20) >> 3) + ((i & 0x08) >> 2) + ((i & 0x02) >> 1) for i in range(256)]
|
|
EVEN_PARITY_U8 = [0 for i in range(256)]
|
|
|
|
def u8_to_odd4(u8):
|
|
return U8_TO_ODD4[u8 & 0xFF]
|
|
|
|
def get_bit(num, x = 0):
|
|
return (num >> x) & 1
|
|
|
|
for i in range(256):
|
|
tmp = i
|
|
tmp ^= tmp >> 4
|
|
tmp ^= tmp >> 2
|
|
EVEN_PARITY_U8[i] = (tmp ^ (tmp >> 1)) & 1
|
|
|
|
def even_parity_u8(u8):
|
|
return EVEN_PARITY_U8[u8 & 0xFF]
|
|
|
|
def odd_parity_u8(u8):
|
|
return even_parity_u8(u8) ^ 1
|
|
|
|
def even_parity_u16(u16):
|
|
return even_parity_u8((u16 >> 8) ^ u16)
|
|
|
|
def even_parity_u48(u48):
|
|
return even_parity_u16((u48 >> 32) ^ (u48 >> 16) ^ u48)
|
|
|
|
def swap_endian_u16(u16):
|
|
return ((u16 & 0xFF) << 8) | ((u16 >> 8) & 0xFF)
|
|
|
|
def swap_endian_u32(u32):
|
|
return swap_endian_u16(u32 & 0xFFFF) << 16 | swap_endian_u16((u32 >> 16) & 0xFFFF)
|
|
|
|
"""
|
|
ref: https://web.archive.org/web/20081010065744/http://sar.informatik.hu-berlin.de/research/publications/SAR-PR-2008-21/SAR-PR-2008-21_.pdf
|
|
"""
|
|
class Crypto1:
|
|
def __init__(self, new_lfsr48: int = 0):
|
|
self.lfsr48 = new_lfsr48
|
|
|
|
@property
|
|
def key(self) -> bytearray:
|
|
tmp, key = self.lfsr48, bytearray(6)
|
|
for i in range(6):
|
|
key[i] = tmp & 0xFF
|
|
tmp >>= 8
|
|
return key.hex()
|
|
|
|
@key.setter
|
|
def key(self, key: str):
|
|
if not re.match(r"^[a-fA-F0-9]{12}$", key):
|
|
raise ValueError(f"Invalid hex format key: {key}")
|
|
tmp, self.lfsr48 = int(key, 16), 0
|
|
for i in range(6):
|
|
self.lfsr48 = (self.lfsr48 << 8) | tmp & 0xFF
|
|
tmp >>= 8
|
|
|
|
def lfsr48_filter(self):
|
|
f = 0
|
|
f |= get_bit(LFSR48_FILTER_B, u8_to_odd4(self.lfsr48 >> 8)) # fb4
|
|
f |= get_bit(LFSR48_FILTER_A, u8_to_odd4(self.lfsr48 >> 16)) << 1 # fa4
|
|
f |= get_bit(LFSR48_FILTER_A, u8_to_odd4(self.lfsr48 >> 24)) << 2 # fa4
|
|
f |= get_bit(LFSR48_FILTER_B, u8_to_odd4(self.lfsr48 >> 32)) << 3 # fb4
|
|
f |= get_bit(LFSR48_FILTER_A, u8_to_odd4(self.lfsr48 >> 40)) << 4 # fa4
|
|
return get_bit(LFSR48_FILTER_C, f)
|
|
|
|
def lfsr48_bit(self, bit_in: int = 0, is_encrypted: bool = False) -> int:
|
|
out_bit = self.lfsr48_filter()
|
|
bit_feedback = even_parity_u48(LFSR48_POLY & self.lfsr48) ^ (bit_in & 1) ^ (is_encrypted & out_bit)
|
|
self.lfsr48 = (bit_feedback << 47) | (self.lfsr48 >> 1)
|
|
return out_bit
|
|
|
|
def lfsr48_u8(self, u8_in: int = 0, is_encrypted: bool = False) -> int:
|
|
out_u8 = 0
|
|
for i in range(8):
|
|
tmp = self.lfsr48_bit(u8_in >> i, is_encrypted) << i
|
|
out_u8 |= tmp
|
|
return out_u8
|
|
|
|
def lfsr48_u32(self, u32_in: int = 0, is_encrypted: bool = False) -> int:
|
|
out_u32 = 0
|
|
for i in range(3, -1, -1):
|
|
bit_offset = i << 3
|
|
out_u32 |= self.lfsr48_u8(u32_in >> bit_offset, is_encrypted) << bit_offset
|
|
return out_u32
|
|
|
|
@staticmethod
|
|
def prng_next(lfsr32: int, n: int = 1) -> int:
|
|
lfsr32 = swap_endian_u32(lfsr32)
|
|
for i in range(n):
|
|
lfsr32 = even_parity_u8(0x2D & (lfsr32 >> 16)) << 31 | (lfsr32 >> 1)
|
|
return swap_endian_u32(lfsr32)
|
|
|
|
@staticmethod
|
|
def mfkey32_is_reader_has_key(uid: int, nt: int, nrEnc: int, arEnc: int, key: str) -> bool:
|
|
state = Crypto1()
|
|
state.key = key
|
|
state.lfsr48_u32(uid ^ nt, False) # ks0
|
|
state.lfsr48_u32(nrEnc, True) # ks1
|
|
ks2 = state.lfsr48_u32(0, False) # ks2
|
|
ar = arEnc ^ ks2
|
|
result = ar == Crypto1.prng_next(nt, 64)
|
|
# print(f'uid: {hex(uid)}, nt: {hex(nt)}, nrEnc: {hex(nrEnc)}, arEnc: {hex(arEnc)}, key: {key}, result = {result}')
|
|
return result |