ipodsync 0.1.0__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.
- ipodsync/__init__.py +27 -0
- ipodsync/__main__.py +5 -0
- ipodsync/_art_templates.py +29 -0
- ipodsync/_hashab_gen.py +1588 -0
- ipodsync/_hashab_gen2.py +838 -0
- ipodsync/_hashab_gen_rt.py +134 -0
- ipodsync/_hashab_py.py +285 -0
- ipodsync/_hashab_tables.bin +0 -0
- ipodsync/_hashab_tables.py +31 -0
- ipodsync/_lib_templates.py +16 -0
- ipodsync/artwork_writer.py +252 -0
- ipodsync/cbk.py +44 -0
- ipodsync/cli.py +314 -0
- ipodsync/export.py +91 -0
- ipodsync/hashab.py +44 -0
- ipodsync/importer.py +137 -0
- ipodsync/library.py +360 -0
- ipodsync/models.py +31 -0
- ipodsync/sysinfo.py +50 -0
- ipodsync/transport.py +165 -0
- ipodsync-0.1.0.dist-info/METADATA +114 -0
- ipodsync-0.1.0.dist-info/RECORD +25 -0
- ipodsync-0.1.0.dist-info/WHEEL +4 -0
- ipodsync-0.1.0.dist-info/entry_points.txt +2 -0
- ipodsync-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Runtime for the transpiled generate_* functions of hashAB.
|
|
2
|
+
|
|
3
|
+
`U32` reproduces the semantics of C's 32-bit unsigned arithmetic: every operation
|
|
4
|
+
is masked to 2^32, `>>` is a logical shift (in the source all shifts are unsigned),
|
|
5
|
+
and `__index__` lets a value be used as a list index. The transpiler
|
|
6
|
+
(tools/transpile_hashab.py) emits nearly literal Python where every literal is
|
|
7
|
+
U32(...), and this class holds the semantics. The generate_buffer_from_state_mixing
|
|
8
|
+
helpers are ported by hand (they're short).
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
MASK = 0xFFFFFFFF
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _v(x) -> int:
|
|
16
|
+
return x.v if isinstance(x, U32) else (x & MASK)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# --- narrow C casts (NOT identity: truncation/sign extension) --------------------
|
|
20
|
+
def u8c(x): return U32(_v(x) & 0xFF)
|
|
21
|
+
def u16c(x): return U32(_v(x) & 0xFFFF)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def i8c(x):
|
|
25
|
+
b = _v(x) & 0xFF
|
|
26
|
+
return U32(b - 0x100 if b & 0x80 else b)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def i16c(x):
|
|
30
|
+
w = _v(x) & 0xFFFF
|
|
31
|
+
return U32(w - 0x10000 if w & 0x8000 else w)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# --- byte_helpers.h macros ------------------------------------------------------
|
|
35
|
+
def LOBYTE(x): return U32(_v(x) & 0xFF)
|
|
36
|
+
def BYTE1(x): return U32((_v(x) >> 8) & 0xFF)
|
|
37
|
+
def BYTE2(x): return U32((_v(x) >> 16) & 0xFF)
|
|
38
|
+
def HIBYTE(x): return U32((_v(x) >> 24) & 0xFF)
|
|
39
|
+
def LOWORD(x): return U32(_v(x) & 0xFFFF)
|
|
40
|
+
def HIWORD(x): return U32((_v(x) >> 16) & 0xFFFF)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class U32:
|
|
44
|
+
__slots__ = ("v",)
|
|
45
|
+
|
|
46
|
+
def __init__(self, v: int):
|
|
47
|
+
self.v = (v.v if type(v) is U32 else v) & MASK
|
|
48
|
+
|
|
49
|
+
# arithmetic (all results mod 2^32; sign doesn't matter for + - * ^ & | ~ <<)
|
|
50
|
+
def __add__(s, o): return U32(s.v + _v(o))
|
|
51
|
+
def __radd__(s, o): return U32(_v(o) + s.v)
|
|
52
|
+
def __sub__(s, o): return U32(s.v - _v(o))
|
|
53
|
+
def __rsub__(s, o): return U32(_v(o) - s.v)
|
|
54
|
+
def __mul__(s, o): return U32(s.v * _v(o))
|
|
55
|
+
def __rmul__(s, o): return U32(_v(o) * s.v)
|
|
56
|
+
def __and__(s, o): return U32(s.v & _v(o))
|
|
57
|
+
def __rand__(s, o): return U32(s.v & _v(o))
|
|
58
|
+
def __or__(s, o): return U32(s.v | _v(o))
|
|
59
|
+
def __ror__(s, o): return U32(s.v | _v(o))
|
|
60
|
+
def __xor__(s, o): return U32(s.v ^ _v(o))
|
|
61
|
+
def __rxor__(s, o): return U32(s.v ^ _v(o))
|
|
62
|
+
def __lshift__(s, o): return U32(s.v << _v(o))
|
|
63
|
+
def __rlshift__(s, o): return U32(_v(o) << s.v)
|
|
64
|
+
def __rshift__(s, o): return U32(s.v >> _v(o)) # logical (s.v >= 0)
|
|
65
|
+
def __rrshift__(s, o): return U32(_v(o) >> s.v)
|
|
66
|
+
def __floordiv__(s, o): return U32(s.v // _v(o)) # unsigned division
|
|
67
|
+
def __rfloordiv__(s, o): return U32(_v(o) // s.v)
|
|
68
|
+
def __truediv__(s, o): return U32(s.v // _v(o))
|
|
69
|
+
def __rtruediv__(s, o): return U32(_v(o) // s.v)
|
|
70
|
+
def __mod__(s, o): return U32(s.v % _v(o))
|
|
71
|
+
def __rmod__(s, o): return U32(_v(o) % s.v)
|
|
72
|
+
def __neg__(s): return U32(-s.v)
|
|
73
|
+
def __invert__(s): return U32(~s.v)
|
|
74
|
+
|
|
75
|
+
def __index__(s): return s.v
|
|
76
|
+
def __int__(s): return s.v
|
|
77
|
+
def __eq__(s, o): return s.v == _v(o)
|
|
78
|
+
def __hash__(s): return hash(s.v)
|
|
79
|
+
def __repr__(s): return f"U32(0x{s.v:08x})"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# --- InputSeed + make_seed (from generate_buffer_from_state_mixing.c) ------------
|
|
83
|
+
class InputSeed:
|
|
84
|
+
__slots__ = ("raw", "doubled", "masked_and", "masked_or", "init")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def make_seed(value) -> InputSeed:
|
|
88
|
+
value = U32(value)
|
|
89
|
+
doubled = value * 2 + U32(0xD1EB870E)
|
|
90
|
+
s = InputSeed()
|
|
91
|
+
s.raw = value
|
|
92
|
+
s.doubled = doubled
|
|
93
|
+
s.masked_and = doubled & U32(0xA50FF288)
|
|
94
|
+
s.masked_or = doubled | U32(0xA50FF288)
|
|
95
|
+
s.init = U32(0x44824335) + (U32(-0x2D7806BC) ^ (U32(-0x170A3C79) + value))
|
|
96
|
+
return s
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# --- helper functions (ported by hand; they contain no shifts/divisions) --------
|
|
100
|
+
_SLOT_MUL = U32(-0x362C9C11)
|
|
101
|
+
_SLOT_INV = U32(0x362C9C11)
|
|
102
|
+
_COEFF_XOR = U32(0x49D363EF)
|
|
103
|
+
_SLOT_INNER_MUL = U32(-0x1E6C2B0F)
|
|
104
|
+
_COEFF_AND = U32(0x6C593822)
|
|
105
|
+
_INNER_CONST_AND = U32(-0x2E079947)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def slot_mix_add(slot_value, mix_value, mix_mask, offset, tweak):
|
|
109
|
+
diff = (mix_mask - mix_value) * 2 + tweak
|
|
110
|
+
gate = diff & (slot_value * U32(0x3CD8561E) + U32(0x5C0F3290))
|
|
111
|
+
return (offset - slot_value) + mix_value * _SLOT_MUL + mix_mask * _SLOT_INV + gate * _SLOT_MUL
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def slot_mix_or(slot_value, term1, term2, offset):
|
|
115
|
+
return slot_value + offset + term1 + (term2 | (slot_value * U32(-0x3CD8561E) + U32(0xA3F0CD6F))) * _SLOT_MUL
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def slot_mix_xor(slot_value, term1, term2, offset, xor_const, inner_const):
|
|
119
|
+
return offset - _COEFF_XOR * (xor_const ^ (inner_const + term1 + slot_value * _SLOT_INNER_MUL + term2 * 2))
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def slot_mix_and(slot_value, term1_outer, term1_inner, term2, offset, mask):
|
|
123
|
+
return (offset + slot_value + term1_outer + (_COEFF_AND * term2)
|
|
124
|
+
- _COEFF_AND * (mask & (_INNER_CONST_AND + term1_inner + slot_value * _SLOT_INNER_MUL + term2 * 2)))
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def masked_step(value, addend, bias, mask):
|
|
128
|
+
nxt = value + addend
|
|
129
|
+
gated = (value * 2 + bias) & mask
|
|
130
|
+
return nxt - gated
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def masked_gate(value, gate_bias, mask):
|
|
134
|
+
return (value * 2 + gate_bias) & mask
|
ipodsync/_hashab_py.py
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"""Pure-Python port of hashAB (white-box AES) — WITHOUT a native lib.
|
|
2
|
+
|
|
3
|
+
Ported from dstaley/hashab (public domain). The tables (433 KB) live in
|
|
4
|
+
`_hashab_tables.bin` (see tools/gen_hashab_tables.py); the logic is here.
|
|
5
|
+
C overflow semantics are reproduced exactly: everything is computed in Python int
|
|
6
|
+
and masked (u32/u8); signed shifts/divisions are marked explicitly.
|
|
7
|
+
|
|
8
|
+
The port is validated against instrumented C and 100/100 test vectors
|
|
9
|
+
(tests/test_hashab*.py). The orchestrator (phase1/round_state/phase2/final) is here;
|
|
10
|
+
generate_key_material/generate_initial_buffer are in _hashab_gen2 (built by
|
|
11
|
+
tools/build_gen2.py); generate_buffer_from_state_mixing is in _hashab_gen.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import struct
|
|
16
|
+
from importlib import resources
|
|
17
|
+
|
|
18
|
+
from ._hashab_tables import TABLES
|
|
19
|
+
|
|
20
|
+
# --- table loading ---------------------------------------------------------------
|
|
21
|
+
_BLOB = resources.files(__package__).joinpath("_hashab_tables.bin").read_bytes()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _t(name: str) -> bytes:
|
|
25
|
+
off, ln = TABLES[name]
|
|
26
|
+
return _BLOB[off:off + ln]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
INPUT_SBOX = _t("INPUT_SBOX")
|
|
30
|
+
NIBBLE_SBOX_EVEN = _t("NIBBLE_SBOX_EVEN")
|
|
31
|
+
NIBBLE_SBOX_ODD = _t("NIBBLE_SBOX_ODD")
|
|
32
|
+
NIBBLE_SBOX_MAIN = _t("NIBBLE_SBOX_MAIN")
|
|
33
|
+
FINAL_SBOX = _t("FINAL_SBOX")
|
|
34
|
+
OUTPUT_SBOX = _t("OUTPUT_SBOX")
|
|
35
|
+
ROUND_KEYS = _t("ROUND_KEYS")
|
|
36
|
+
MIXCOL_STATE = _t("MIXCOL_STATE")
|
|
37
|
+
MIXCOL_MULT = _t("MIXCOL_MULT")
|
|
38
|
+
FINAL_PERM = _t("FINAL_PERM")
|
|
39
|
+
WB_STATE_EXTRACT = _t("WB_STATE_EXTRACT")
|
|
40
|
+
WB_INPUT_A = _t("WB_INPUT_A")
|
|
41
|
+
WB_INPUT_B = _t("WB_INPUT_B")
|
|
42
|
+
WB_MIX_A = _t("WB_MIX_A")
|
|
43
|
+
WB_MIX_B = _t("WB_MIX_B")
|
|
44
|
+
WB_T_TABLES = _t("WB_T_TABLES")
|
|
45
|
+
WB_T_MIX = _t("WB_T_MIX")
|
|
46
|
+
WB_FINAL_SBOX = _t("WB_FINAL_SBOX")
|
|
47
|
+
|
|
48
|
+
# inline phase 2 tables (from calcHashAB.c)
|
|
49
|
+
LOW_SUB = bytes.fromhex("06030c09020f08050e0b04010a07000d")
|
|
50
|
+
NIBBLE_LO_TABLE = bytes.fromhex(
|
|
51
|
+
"cec0c2c4c7c9cbcacccec1c3c5c4c6c8cbcdcfc1c0c2c4c7c9cbcacccec1c3c5c4c6c8cacd"
|
|
52
|
+
"cfc1c0c2c4c7c9cbcacccec1c3c5c7c6c8cacdcfc1c0c2c4c7c9cbcacccec0c3c5c7c6c8ca"
|
|
53
|
+
"cdcfc1c0c2c4c7c9cbcdcccec0c3c5c7c6c8cacdcfc1c0c2c4c6c9cbcdcccec0c3c5c7c6c8"
|
|
54
|
+
"cacdcfc1c3c2c4c6c9cbcdcccec0c3c5c7c6c8cacccfc1c3c2c4c6c9cbcdcccec0c3c5c7c9"
|
|
55
|
+
"c8cacccfc1c3c2c4c6c9cbcdcccec0c2c5c7c9c8cacccfc1c3c2c4c6c9cbcdcfcec0c2c5c7"
|
|
56
|
+
"c9c8cacccfc1c3c2c4c6c8cbcdcfcec0c2c5c7c9c8cacccfc1c3c5c4c6c8cbcdcfcec0c2c5"
|
|
57
|
+
"c7c9c8cacccec1c3c5c4c6c8cbcdcfcec0c2c5c7c9cbcacccec1c3c5c4c6c8cbcdcf")
|
|
58
|
+
NIBBLE_HI_TABLE = bytes.fromhex(
|
|
59
|
+
"9d78573209e4c3deb5906f4a213c1bf6cda887627954330ee5c0dfba916c4b263d18f7d2a9"
|
|
60
|
+
"84637e55300feac1dcbb966d48270219f4d3ae85607f5a310cebc6ddb897724924031ef5d0"
|
|
61
|
+
"af8a617c5b360de8c7a2b994734e25001ffad1ac8b667d583712e9c4a3be95704f2a011cfb"
|
|
62
|
+
"d6ad886742593413eec5a0bf9a714c2b061df8d7b28964435e3510efcaa1bc9b764d2807e2"
|
|
63
|
+
"f9d4b38e65405f3a11eccba6bd9877522904e3fed5b08f6a415c3b16edc8a7829974532e05"
|
|
64
|
+
"e0ffdab18c6b465d3817f2c9a4839e75502f0ae1fcdbb68d6847223914f3cea5809f7a512c"
|
|
65
|
+
"0be6fdd8b7926944233e15f0cfaa819c7b562d08e7c2d9b4936e45203f1af1ccab86")
|
|
66
|
+
PERMUTATION_TABLE = bytes.fromhex(
|
|
67
|
+
"06181107130d0e09151d021f01041c1a10140b1e030a1b19050f16001217080c")
|
|
68
|
+
|
|
69
|
+
# --- inline constants (from calcHashAB.c) --------------------------------------
|
|
70
|
+
AES_SHIFT_ROWS = (0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, 1, 6, 11)
|
|
71
|
+
MIXCOL_OFFSETS = (0, 1024, 2048, 3072)
|
|
72
|
+
KEY_STATE_INIT = (0xC9, 0xA1, 0x5C, 0x44, 0x17, 0x4B, 0xFA, 0xCD,
|
|
73
|
+
0xF3, 0x32, 0x85, 0x94, 0x00, 0x5C, 0x0E, 0x5E)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# --- helpers: exact C uint semantics ---------------------------------------------
|
|
77
|
+
def u8(x: int) -> int:
|
|
78
|
+
return x & 0xFF
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def u32(x: int) -> int:
|
|
82
|
+
return x & 0xFFFFFFFF
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# --- phase 1: CBC-MAC-like compression (5 blocks of 16 bytes) -------------------
|
|
86
|
+
def _apply_sbox_pair(table: bytes, inp: int, key: int) -> int:
|
|
87
|
+
high = table[(inp & 0xF0) + (key >> 4)]
|
|
88
|
+
low = table[256 + u8((key & 0xF) + 16 * inp)]
|
|
89
|
+
return u8((high << 4) | low)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _mix_column_byte(ms: bytes, idx0: int, idx1: int, idx2: int, idx3: int, j: int) -> int:
|
|
93
|
+
base = j * 1536
|
|
94
|
+
a = ms[idx0 + j]
|
|
95
|
+
b = ms[idx1 + j]
|
|
96
|
+
c = ms[idx2 + j]
|
|
97
|
+
d = ms[idx3 + j]
|
|
98
|
+
t1 = MIXCOL_MULT[(d & 0xF) + base + 768 + u8(c * 16)] & 0xF
|
|
99
|
+
t2 = MIXCOL_MULT[(b & 0xF) + base + 256 + u8(a * 16)]
|
|
100
|
+
low = MIXCOL_MULT[t1 + base + 1280 + u8(t2 * 16)] & 0xF
|
|
101
|
+
u1 = MIXCOL_MULT[(b >> 4) + (a & 0xF0) + base]
|
|
102
|
+
u2 = MIXCOL_MULT[(c & 0xF0) + base + 512 + (d >> 4)] & 0xF
|
|
103
|
+
high = MIXCOL_MULT[u8(u1 * 16) + base + 1024 + u2]
|
|
104
|
+
return u8(low + 16 * high)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def phase1(input_data: bytes) -> bytes:
|
|
108
|
+
"""input_data — 80 bytes (uuid8+sha20+rnd23+padding29). -> phase1_output[80]."""
|
|
109
|
+
key_state = list(KEY_STATE_INIT)
|
|
110
|
+
round_key = ROUND_KEYS
|
|
111
|
+
out = bytearray(80)
|
|
112
|
+
|
|
113
|
+
for block in range(5):
|
|
114
|
+
boff = block * 16
|
|
115
|
+
first_sbox = NIBBLE_SBOX_ODD if block else NIBBLE_SBOX_EVEN
|
|
116
|
+
|
|
117
|
+
cipher_state = [0] * 16
|
|
118
|
+
for i in range(16):
|
|
119
|
+
ti = INPUT_SBOX[input_data[boff + i]]
|
|
120
|
+
cipher_state[i] = _apply_sbox_pair(first_sbox, ti, key_state[i])
|
|
121
|
+
|
|
122
|
+
round_output = [_apply_sbox_pair(NIBBLE_SBOX_MAIN, cipher_state[i], round_key[i])
|
|
123
|
+
for i in range(16)]
|
|
124
|
+
|
|
125
|
+
for rnd in range(1, 10):
|
|
126
|
+
rk = round_key[rnd * 16:]
|
|
127
|
+
perm = [4 * round_output[AES_SHIFT_ROWS[i]] + MIXCOL_OFFSETS[i % 4]
|
|
128
|
+
for i in range(16)]
|
|
129
|
+
for col in range(4):
|
|
130
|
+
idx0 = perm[col * 4 + 0]
|
|
131
|
+
idx1 = perm[col * 4 + 1]
|
|
132
|
+
idx2 = perm[col * 4 + 2]
|
|
133
|
+
idx3 = perm[col * 4 + 3]
|
|
134
|
+
for j in range(4):
|
|
135
|
+
cipher_state[col * 4 + j] = _mix_column_byte(
|
|
136
|
+
MIXCOL_STATE, idx0, idx1, idx2, idx3, j)
|
|
137
|
+
round_output = [_apply_sbox_pair(NIBBLE_SBOX_MAIN, cipher_state[i], rk[i])
|
|
138
|
+
for i in range(16)]
|
|
139
|
+
|
|
140
|
+
permuted = [round_output[AES_SHIFT_ROWS[i]] for i in range(16)]
|
|
141
|
+
for i in range(16):
|
|
142
|
+
cipher_state[i] = _apply_sbox_pair(
|
|
143
|
+
NIBBLE_SBOX_MAIN, FINAL_SBOX[permuted[i]], round_key[160 + i])
|
|
144
|
+
|
|
145
|
+
key_state = cipher_state[:]
|
|
146
|
+
for i in range(16):
|
|
147
|
+
out[boff + i] = OUTPUT_SBOX[cipher_state[i]]
|
|
148
|
+
|
|
149
|
+
return bytes(out)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def build_input_data(sha1: bytes, uuid: bytes, rnd: bytes) -> bytes:
|
|
153
|
+
"""80 bytes: uuid(8)+sha1(20)+rnd(23)+padding(29 x 0xA5)."""
|
|
154
|
+
return bytes(uuid) + bytes(sha1) + bytes(rnd) + b"\xa5" * 29
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# --- round_state: permutation of phase1_output -> 8 uint32 ----------------------
|
|
158
|
+
def derive_round_state(phase1_output: bytes) -> tuple[list[int], bytes]:
|
|
159
|
+
"""-> (round_state[8 int], cipher_block[32 bytes])."""
|
|
160
|
+
cb = bytes(phase1_output[PERMUTATION_TABLE[i] + 44] for i in range(32))
|
|
161
|
+
rs = list(struct.unpack("<8I", cb))
|
|
162
|
+
return rs, cb
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def init_target_rnd(rnd: bytes) -> bytearray:
|
|
166
|
+
"""target[57]: 03 00 + rnd(23) with mixing + room for phase2."""
|
|
167
|
+
target = bytearray(57)
|
|
168
|
+
target[0] = 3
|
|
169
|
+
target[1] = 0
|
|
170
|
+
target[2:25] = rnd
|
|
171
|
+
for i in range(23):
|
|
172
|
+
x = target[i + 2]
|
|
173
|
+
target[i + 2] = u8(69 * x + 118 * (x & 0x5D) + 17)
|
|
174
|
+
return target
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
# --- phase 2: white-box AES (2 blocks of 16 bytes) ------------------------------
|
|
178
|
+
def _nibble_lo(x: int) -> int:
|
|
179
|
+
return NIBBLE_LO_TABLE[x]
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _nibble_hi(x: int) -> int:
|
|
183
|
+
return NIBBLE_HI_TABLE[x]
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _phase2_transform(rs: list[int], inp: bytes, tab_in: bytes, tab_mix: bytes) -> bytearray:
|
|
187
|
+
rs_bytes = [0] * 16
|
|
188
|
+
for w in range(4):
|
|
189
|
+
for b in range(4):
|
|
190
|
+
byte_val = (rs[w] >> (b * 8)) & 0xFF
|
|
191
|
+
rs_bytes[w * 4 + b] = WB_STATE_EXTRACT[byte_val + (b << 8) + (w << 10)]
|
|
192
|
+
input_trans = [tab_in[inp[i] + i * 256] for i in range(16)]
|
|
193
|
+
out = bytearray(16)
|
|
194
|
+
for i in range(16):
|
|
195
|
+
base = i * 512
|
|
196
|
+
rs_b = rs_bytes[i]
|
|
197
|
+
in_b = input_trans[i]
|
|
198
|
+
out[i] = u8(16 * tab_mix[(rs_b & 0xF0) + base + (in_b >> 4)]
|
|
199
|
+
+ (tab_mix[u8(16 * rs_b + (in_b & 0xF)) + base + 256] & 0xF))
|
|
200
|
+
return out
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def phase2(round_state: list[int], ibuff_result: bytes, cipher_block0: bytes) -> bytes:
|
|
204
|
+
"""2 iterations of WB-AES. cipher_block0 — the round_state bytes (input to the 2nd iteration).
|
|
205
|
+
-> 32 bytes (into target[25:57])."""
|
|
206
|
+
output = bytearray(32)
|
|
207
|
+
cipher_block = bytearray(cipher_block0)
|
|
208
|
+
for it in range(2):
|
|
209
|
+
inp = ibuff_result if it == 0 else bytes(cipher_block)
|
|
210
|
+
tab_in = WB_INPUT_A if it == 0 else WB_INPUT_B
|
|
211
|
+
tab_mix = WB_MIX_A if it == 0 else WB_MIX_B
|
|
212
|
+
rs = round_state[it * 4: it * 4 + 4]
|
|
213
|
+
|
|
214
|
+
phase2_out = _phase2_transform(rs, inp, tab_in, tab_mix)
|
|
215
|
+
cipher_block[:16] = phase2_out
|
|
216
|
+
mix_state = bytearray(phase2_out)
|
|
217
|
+
|
|
218
|
+
for rnd in range(9):
|
|
219
|
+
round_offset = rnd * 4096
|
|
220
|
+
perm = [4 * (round_offset + mix_state[AES_SHIFT_ROWS[i]] + i * 256)
|
|
221
|
+
for i in range(16)]
|
|
222
|
+
for col in range(4):
|
|
223
|
+
co = col * 4
|
|
224
|
+
idx0 = perm[co + 1]
|
|
225
|
+
idx1 = perm[co + 2]
|
|
226
|
+
idx2 = perm[co + 3]
|
|
227
|
+
idx3 = perm[co]
|
|
228
|
+
for bi in range(4):
|
|
229
|
+
block = rnd * 16 + col * 4 + bi
|
|
230
|
+
t0 = WB_T_TABLES[idx1 + bi]
|
|
231
|
+
t1 = WB_T_TABLES[idx3 + bi]
|
|
232
|
+
t2 = WB_T_TABLES[idx0 + bi]
|
|
233
|
+
t3 = WB_T_TABLES[idx2 + bi]
|
|
234
|
+
hi_nib_t0 = _nibble_hi(t0)
|
|
235
|
+
hi_nib_t1 = _nibble_hi(t1)
|
|
236
|
+
lo_nib_t2 = _nibble_lo(t2)
|
|
237
|
+
lo_nib_t3 = _nibble_lo(t3)
|
|
238
|
+
t0_x27 = u8(27 * t0)
|
|
239
|
+
mixed_t1 = u8(128 + (122 ^ u8(-27 * t1)))
|
|
240
|
+
i1 = block * 1536 + 0 * 256 + ((((hi_nib_t1 >> 4) ^ 0x09) & 0x0F) << 4) | ((lo_nib_t2 ^ 0x0E) & 0x0F)
|
|
241
|
+
i3 = block * 1536 + 1 * 256 + ((((mixed_t1 & 0x0F) ^ 0x0C) << 4) | (((5 * t2 & 0x0F) ^ 0x06) & 0x0F))
|
|
242
|
+
i2 = block * 1536 + 2 * 256 + ((((hi_nib_t0 >> 4) ^ 0x09) & 0x0F) << 4) | ((lo_nib_t3 ^ 0x0E) & 0x0F)
|
|
243
|
+
i0 = block * 1536 + 3 * 256 + (((((t0_x27 & 0x0F) + 7) & 0x0F) ^ 0x1) << 4) | LOW_SUB[t3 & 0x0F]
|
|
244
|
+
i4 = block * 1536 + 4 * 256 + ((WB_T_MIX[i1] & 0x0F) << 4) | (WB_T_MIX[i2] & 0x0F)
|
|
245
|
+
i5 = block * 1536 + 5 * 256 + ((WB_T_MIX[i3] & 0x0F) << 4) | (WB_T_MIX[i0] & 0x0F)
|
|
246
|
+
cipher_block[co + bi] = u8(16 * WB_T_MIX[i4] + (WB_T_MIX[i5] & 0xF))
|
|
247
|
+
if rnd < 8:
|
|
248
|
+
mix_state = bytearray(cipher_block[:16])
|
|
249
|
+
|
|
250
|
+
temp = bytearray(16)
|
|
251
|
+
for i in range(16):
|
|
252
|
+
temp[i] = WB_FINAL_SBOX[cipher_block[AES_SHIFT_ROWS[i]] + i * 256]
|
|
253
|
+
cipher_block[:16] = temp
|
|
254
|
+
output[it * 16: it * 16 + 16] = cipher_block[:16]
|
|
255
|
+
return bytes(output)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def final_permute(target: bytearray) -> bytearray:
|
|
259
|
+
"""Final permutation (FINAL_PERM) + nonlinear mixing of bytes 2..56."""
|
|
260
|
+
perm = struct.unpack("<55I", FINAL_PERM)
|
|
261
|
+
for i in range(55):
|
|
262
|
+
p = perm[i] + 2
|
|
263
|
+
target[p], target[i + 2] = target[i + 2], target[p]
|
|
264
|
+
for i in range(55):
|
|
265
|
+
x = target[i + 2]
|
|
266
|
+
y = u8(93 ^ u8(13 * x))
|
|
267
|
+
if x % 2 == 1:
|
|
268
|
+
y ^= 0x80
|
|
269
|
+
target[i + 2] = y
|
|
270
|
+
return target
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
# --- full pure-Python hashAB -----------------------------------------------------
|
|
274
|
+
def calc_hashab(sha1: bytes, uuid: bytes, rnd: bytes) -> bytes:
|
|
275
|
+
"""Pure-Python calcHashAB: SHA1(20)+FirewireGuid(8)+rnd(23) -> 57-byte signature."""
|
|
276
|
+
from ._hashab_gen2 import generate_initial_buffer, generate_key_material
|
|
277
|
+
|
|
278
|
+
p1 = phase1(build_input_data(sha1, uuid, rnd))
|
|
279
|
+
round_state, cipher_block = derive_round_state(p1)
|
|
280
|
+
target = init_target_rnd(rnd)
|
|
281
|
+
expanded = generate_key_material(p1)
|
|
282
|
+
ibuff = generate_initial_buffer(expanded)
|
|
283
|
+
target[25:57] = phase2(round_state, ibuff, cipher_block)
|
|
284
|
+
final_permute(target)
|
|
285
|
+
return bytes(target)
|
|
Binary file
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""hashAB tables manifest: name -> (offset, length) in _hashab_tables.bin.
|
|
2
|
+
|
|
3
|
+
Generated by tools/gen_hashab_tables.py from dstaley/hashab (public domain).
|
|
4
|
+
The bytes are loaded by hashab_py.py via importlib.resources.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
TABLES = {
|
|
8
|
+
'FINAL_PERM': (0, 220),
|
|
9
|
+
'FINAL_SBOX': (220, 256),
|
|
10
|
+
'GEN190_SBOX': (476, 2048),
|
|
11
|
+
'GEN190_STATE_TABLE': (2524, 2048),
|
|
12
|
+
'IBUFF_KEY_TABLE': (4572, 256),
|
|
13
|
+
'IBUFF_MIX_SEED_A': (4828, 256),
|
|
14
|
+
'IBUFF_MIX_SEED_B': (5084, 256),
|
|
15
|
+
'INPUT_SBOX': (5340, 256),
|
|
16
|
+
'MIXCOL_MULT': (5596, 6144),
|
|
17
|
+
'MIXCOL_STATE': (11740, 4096),
|
|
18
|
+
'NIBBLE_SBOX_EVEN': (15836, 512),
|
|
19
|
+
'NIBBLE_SBOX_MAIN': (16348, 512),
|
|
20
|
+
'NIBBLE_SBOX_ODD': (16860, 512),
|
|
21
|
+
'OUTPUT_SBOX': (17372, 256),
|
|
22
|
+
'ROUND_KEYS': (17628, 176),
|
|
23
|
+
'WB_FINAL_SBOX': (17804, 4096),
|
|
24
|
+
'WB_INPUT_A': (21900, 4096),
|
|
25
|
+
'WB_INPUT_B': (25996, 4096),
|
|
26
|
+
'WB_MIX_A': (30092, 8192),
|
|
27
|
+
'WB_MIX_B': (38284, 8192),
|
|
28
|
+
'WB_STATE_EXTRACT': (46476, 4096),
|
|
29
|
+
'WB_T_MIX': (50572, 245760),
|
|
30
|
+
'WB_T_TABLES': (296332, 147456),
|
|
31
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Row templates for adding a track to an EMPTY .itlp library.
|
|
2
|
+
|
|
3
|
+
SYNTHETIC: all string fields are blanked (personal data removed); pid/order are
|
|
4
|
+
overwritten in library.add_track. Regenerate with tools/gen_lib_templates.py
|
|
5
|
+
(from a local backup — the backup is not part of the public repo).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
ITEM_TEMPLATE = {'pid': -9216401694829997841, 'revision_level': None, 'media_kind': 1, 'is_song': 1, 'is_audio_book': 0, 'is_music_video': 0, 'is_movie': 0, 'is_tv_show': 0, 'is_home_video': 0, 'is_ringtone': 0, 'is_tone': 0, 'is_voice_memo': 0, 'is_book': 0, 'is_rental': 0, 'is_itunes_u': 0, 'is_digital_booklet': 0, 'is_podcast': 0, 'date_modified': 758845134, 'year': 2013, 'content_rating': 0, 'content_rating_level': 0, 'is_compilation': 0, 'is_user_disabled': 0, 'remember_bookmark': 0, 'exclude_from_shuffle': 0, 'part_of_gapless_album': 0, 'chosen_by_auto_fill': 0, 'artwork_status': 0, 'artwork_cache_id': 0, 'start_time_ms': 0.0, 'stop_time_ms': 0.0, 'total_time_ms': 224888.0, 'total_burn_time_ms': None, 'track_number': 3, 'track_count': 0, 'disc_number': 0, 'disc_count': 0, 'bpm': 0, 'relative_volume': 0, 'eq_preset': None, 'radio_stream_status': None, 'genius_id': 0, 'genre_id': 12, 'category_id': 0, 'album_pid': -8778576643414911648, 'artist_pid': 3083228396371532061, 'composer_pid': 0, 'title': '', 'artist': '', 'album': '', 'album_artist': '', 'composer': None, 'sort_title': '', 'sort_artist': '', 'sort_album': '', 'sort_album_artist': '', 'sort_composer': None, 'title_order': 89300, 'artist_order': 53900, 'album_order': 32300, 'genre_order': 4700, 'composer_order': 200, 'album_artist_order': 44400, 'album_by_artist_order': None, 'series_name_order': 100, 'comment': None, 'grouping': None, 'description': None, 'description_long': None, 'collection_description': None, 'copyright': None, 'track_artist_pid': 539, 'physical_order': 1245, 'has_lyrics': 0, 'date_released': 0}
|
|
9
|
+
|
|
10
|
+
ARTIST_TEMPLATE = {'pid': -9211478403244814981, 'kind': 2, 'artwork_status': 0, 'artwork_album_pid': 0, 'name': '', 'name_order': 26600, 'sort_name': '', 'is_unknown': 0, 'has_songs': 1, 'has_music_videos': 0, 'has_non_compilation_tracks': 1, 'album_count': 1}
|
|
11
|
+
|
|
12
|
+
ALBUM_TEMPLATE = {'pid': -9152521762437827186, 'kind': 2, 'artwork_status': 0, 'artwork_item_pid': 0, 'artist_pid': -8213557128273776184, 'user_rating': 0, 'name': '', 'name_order': 4600, 'all_compilations': 0, 'feed_url': None, 'season_number': 0, 'is_unknown': 0, 'has_songs': 1, 'has_music_videos': 0, 'sort_order': 4600, 'artist_order': 2600, 'has_any_compilations': 0, 'sort_name': '', 'artist_count_calc': 1, 'has_movies': 0, 'item_count': 1, 'min_volume_normalization_energy': 2438}
|
|
13
|
+
|
|
14
|
+
TRACK_ARTIST_TEMPLATE = {'pid': 1, 'name': '', 'name_order': 100, 'sort_name': '', 'has_songs': 1, 'has_music_videos': 0, 'has_non_compilation_tracks': 1, 'is_unknown': 0, 'album_count': 1}
|
|
15
|
+
|
|
16
|
+
LOCATION_TEMPLATE = {'item_pid': -7383142142304564027, 'sub_id': 0, 'base_location_id': 1, 'location_type': 1179208773, 'location': '', 'extension': 1297101600, 'kind_id': 1, 'date_created': 737588774, 'file_size': 2192482, 'file_creator': None, 'file_type': None, 'num_dir_levels_file': None, 'num_dir_levels_lib': None}
|