修正QIMEI UUID42随机段生成

This commit is contained in:
yml2213
2026-09-02 17:15:06 +08:00
parent 17ee943753
commit 1f3773de28
4 changed files with 32 additions and 12 deletions
+10 -7
View File
@@ -276,8 +276,9 @@ def _qimei_uuid42_generate(
``now_ns`` 保留旧回退函数的纳秒参数约定;SDK 实际只读取本地时间的
年/月/日/时/分/秒,因此毫秒和纳秒不会进入结果。时间前缀的年份和月份
使用 SDK 的十六进制编码,最后三位来自 ``navigator.cookieEnabled``。
``random_value`` 仅用于固定测试;生产调用沿用 ``Math.random()`` 等价的
Python ``random.random()``,并将理论上的 1.0 夹到合法半字节上界 15。
``random_value`` 仅用于固定测试;生产调用会像 SDK 一样独立调用 20 次
``Math.random()`` 等价的 Python ``random.random()``,并将理论上的 1.0
夹到合法半字节上界 15。
"""
now_ms = time.time_ns() // 1_000_000 if now_ns is None else now_ns // 1_000_000
current = datetime.fromtimestamp(now_ms / 1000, tz=UTC).astimezone()
@@ -292,12 +293,14 @@ def _qimei_uuid42_generate(
)
prefix = year_hex + month_hex + date_parts + ("100" if cookie_enabled else "000")
value = random.random() if random_value is None else float(random_value)
nibble = min(15, max(0, int(value * 16)))
repeated = f"{nibble:x}" * 20
template = prefix + "----" + repeated + "----"
random_source = random.random if random_value is None else lambda: random_value
random_segment = "".join(
f"{min(15, max(0, int(float(random_source()) * 16))):x}"
for _ in range(20)
)
template = prefix + "----" + random_segment + "----"
replacement = f"{zlib.crc32(template.encode('ascii')) & 0xFFFFFFFF:08x}"
return prefix + replacement[:4] + repeated + replacement[4:]
return prefix + replacement[:4] + random_segment + replacement[4:]
def fill_web_cookie_fields(
+2 -1
View File
@@ -68,6 +68,7 @@ random=0.25 17b0f060d14100037f4444444444444444444437df
random=0.5 17b0f060d1410053db888888888888888888883ff6
random=0.75 17b0f060d14100966bcccccccccccccccccccc1cf3
random=.999 17b0f060d141003174ffffffffffffffffffffd85d
sequence=0..f,0..3 17b0f060d14100b2230123456789abcdef012396a9
```
每个值都可以拆成 `14 + 4 + 20 + 4 = 42` 位。时间前缀由本地时间组成:年份末两位转十六进制并补两位、月份转十六进制、日/时/分/秒转十六进制并各补两位,最后追加 `cookieEnabled ? 100 : 000`。中间 20 位是 `floor(Math.random() * 16)` 对应的十六进制半字节重复。两个 `----` 占位组成模板后,SDK 对模板 ASCII 字节计算标准 CRC32(多项式 `0xEDB88320`),将 8 位小写结果的前后四位分别替换占位,最后调用 `toLocaleLowerCase()`。Python 实现位于 `core/huya/web_cookie_fields.py`,并用上述固定向量回归;随机上界按 15 处理,避免理论输入 `1.0` 产生两位半字节。VM 追踪在 `/tmp/qvmtrace17.out`,生成器入口为 `e=6064`,模板拼接和替换发生在 `m≈5015..5137`。该实现只用于查询链路的设备态生成,不触发兑换、下单或支付。
每个值都可以拆成 `14 + 4 + 20 + 4 = 42` 位。时间前缀由本地时间组成:年份末两位转十六进制并补两位、月份转十六进制、日/时/分/秒转十六进制并各补两位,最后追加 `cookieEnabled ? 100 : 000`。中间 20 位由 20 次独立的 `floor(Math.random() * 16)` 依次拼接;上面的单值向量因为探针固定了每次随机返回值,所以表现为相同半字节重复。两个 `----` 占位组成模板后,SDK 对模板 ASCII 字节计算标准 CRC32(多项式 `0xEDB88320`),将 8 位小写结果的前后四位分别替换占位,最后调用 `toLocaleLowerCase()`。Python 实现位于 `core/huya/web_cookie_fields.py`,并用上述固定与递增序列向量回归;随机上界按 15 处理,避免理论输入 `1.0` 产生两位半字节。VM 追踪在 `/tmp/qvmtrace17.out`,生成器入口为 `e=6064`,模板拼接和替换发生在 `m≈5015..5137`。该实现只用于查询链路的设备态生成,不触发兑换、下单或支付。
+3 -3
View File
@@ -64,7 +64,7 @@ CBCC 06ECB5 0000 0001 B63016D6162BC990 ← reqable 8.31
17b0f060d1410053db888888888888888888883ff6 ← 固定时间/随机 .5
17b0f060d141003174ffffffffffffffffffffd85d ← 固定时间/随机 .999
```
时间前缀的编码为:年份末两位十六进制(补两位)+ 月份十六进制 + 日/时/分/秒各两位十六进制 + `cookieEnabled` 标志(`100`/`000`)。模板 `prefix + "----" + nibble * 20 + "----"` 的标准 CRC32 小写结果会拆成前后四位填回两个占位;`nibble = floor(Math.random() * 16)`,因此合法范围为 `0` 到 `f`。
时间前缀的编码为:年份末两位十六进制(补两位)+ 月份十六进制 + 日/时/分/秒各两位十六进制 + `cookieEnabled` 标志(`100`/`000`)。模板 `prefix + "----" + random_nibbles + "----"` 的标准 CRC32 小写结果会拆成前后四位填回两个占位;`random_nibbles` 由 20 次独立的 `floor(Math.random() * 16)` 依次拼接,因此每位合法范围为 `0` 到 `f`。
### _qimei_fingerprint = x64hash128(feature_string, 31) 32hex
qimeisdk-web 0.6.3 源码已定位:
@@ -121,10 +121,10 @@ B = (t=35) => { const e="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012
清理 localStorage/cookie 后对 QIMEI SDK 0.6.3 做固定时间、固定随机值追踪,定位到 VM 生成器 `e=6064`。它先拼出:
```text
14 位时间前缀 + "----" + 20 位随机半字节重复串 + "----"
14 位时间前缀 + "----" + 20 次独立抽样组成的随机半字节串 + "----"
```
随后计算模板 ASCII 字节的标准 CRC32,将 8 位小写结果的前 4 位和后 4 位分别填入占位,最后调用 `toLocaleLowerCase()`。例如 `random=0.25` 的模板是 `17b0f060d14100----44444444444444444444----`,替换值是 `037f37df`,结果为 `17b0f060d14100037f4444444444444444444437df`。`navigator.cookieEnabled` 只影响时间前缀末三位(`100`/`000`)。
随后计算模板 ASCII 字节的标准 CRC32,将 8 位小写结果的前 4 位和后 4 位分别填入占位,最后调用 `toLocaleLowerCase()`。例如固定每次 `random=0.25` 时,模板是 `17b0f060d14100----44444444444444444444----`,替换值是 `037f37df`,结果为 `17b0f060d14100037f4444444444444444444437df`;递增序列探针则得到随机段 `0123456789abcdef0123`,证明 SDK 会独立抽样 20 次。`navigator.cookieEnabled` 只影响时间前缀末三位(`100`/`000`)。
- 由 qimeisdk-web 0.6.3 内部 VM 生成(`_qimei_ssid`/`_qimei_last_get_id_time_<16hex>` 同源);字段本身为 42 位小写十六进制。
- Python 实现按 SDK 时间、CRC32 和随机上界复刻,固定向量覆盖 `0`、`.25`、`.5`、`.75`、`.999`。
+17 -1
View File
@@ -61,6 +61,22 @@ def test_qimei_uuid42_matches_sdk_vectors_and_random_upper_bound():
assert all(ch in "0123456789abcdef" for ch in upper)
def test_qimei_uuid42_uses_twenty_independent_random_draws(monkeypatch):
values = iter(value / 16 for value in (*range(16), *range(4)))
calls = 0
def next_random():
nonlocal calls
calls += 1
return next(values)
monkeypatch.setattr("core.huya.web_cookie_fields.random.random", next_random)
assert _qimei_uuid42_generate(1700000000000000000) == (
"17b0f060d14100b2230123456789abcdef012396a9"
)
assert calls == 20
def test_qimei_uuid42_encodes_local_date_and_cookie_flag():
base = 1700000000000000000
assert _qimei_uuid42_generate(base, random_value=0, cookie_enabled=False) == (
@@ -75,7 +91,7 @@ def test_qimei_uuid42_handles_year_zero_padding():
def test_sdk_uuid42_trace_vectors_have_confirmed_outer_layout():
"""锁定 SDK 追踪确认的 14+4+20+4 外层布局,不约束未闭合的替换算法。"""
"""锁定 SDK 追踪确认的 14+4+20+4 外层布局和固定随机向量。"""
vectors = {
0.0: "17b0f060d1410085cc000000000000000000003207",
0.25: "17b0f060d14100037f4444444444444444444437df",