46 lines
1.6 KiB
Python
46 lines
1.6 KiB
Python
|
import struct
|
||
|
|
||
|
from crccheck.crc import Crc32Mpeg2
|
||
|
|
||
|
|
||
|
class Keybox:
|
||
|
def __init__(self, data):
|
||
|
length = len(data)
|
||
|
if length not in (128, 132):
|
||
|
raise ValueError(f"Invalid keybox length: {length}. Should be 128 or 132 bytes")
|
||
|
|
||
|
if length == 128: # QSEE style keybox
|
||
|
if data[0x80:0x84] != b"LVL1":
|
||
|
raise ValueError("QSEE style keybox does not end in bytes 'LVL1'")
|
||
|
data = data[0:0x80]
|
||
|
|
||
|
if data[0x78:0x7C] != b"kbox":
|
||
|
raise ValueError("Invalid keybox magic")
|
||
|
|
||
|
body_crc = Crc32Mpeg2.calc(data[:0x7C])
|
||
|
body_crc_expected = struct.unpack(">L", data[0x7C:0x7C + 4])[0]
|
||
|
if body_crc_expected != body_crc:
|
||
|
raise ValueError(f"Keybox CRC is bad. Expected: 0x{body_crc_expected:08X}. Computed: 0x{body_crc:08X}")
|
||
|
|
||
|
self.stable_id = data[0x00:0x20] # aka device ID
|
||
|
self.device_aes_key = data[0x20:0x30]
|
||
|
self.device_id = data[0x30:0x78] # device id sent to google, possibly flags + system_id + encrypted
|
||
|
|
||
|
# known fields
|
||
|
self.flags, self.system_id = struct.unpack(">L", self.device_id[0:8])[:2]
|
||
|
|
||
|
def __str__(self):
|
||
|
return f"{self.stable_id.decode('utf-8').strip()} ({self.system_id})"
|
||
|
|
||
|
def __repr__(self):
|
||
|
return "{name}({items})".format(
|
||
|
name=self.__class__.__name__,
|
||
|
items=", ".join([f"{k}={repr(v)}" for k, v in self.__dict__.items()])
|
||
|
)
|
||
|
|
||
|
@classmethod
|
||
|
def load(cls, file):
|
||
|
"""Load Keybox from a file path."""
|
||
|
with open(file, "rb", encoding="utf-8") as fd:
|
||
|
return cls(fd.read())
|