Twitter(二)

背景

x-client-transaction-id 是 X(原 Twitter)API 请求中的一个头字段,用于标识请求的唯一性。其生成涉及多个步骤,包括哈希前拼接、SHA256 哈希、哈希后拼接、字节数组转字符串、和 Base64 编码。

输入

1
2
3
4
5
6
7
请求字符串:POST!/i/api/1.1/jot/error_log.json!62368548obfiowerehiring8abafa10170a3d70a3d70a0170a3d70a3d70a100

method:POST

endpoint:/i/api/1.1/jot/error_log.json

additionalData:62368548obfiowerehiring8abafa10170a3d70a3d70a0170a3d70a3d70a100

动态值:

1
2
3
4
yr(o)(rand_bytes):长度为 48 字节
yr(u)(time_bytes):长度为 4 字节
sc(index_bytes):长度为 2 字节([45, 48])
qo["DeRae"]:obfiowerehiring(来自 additionalData 的子字符串)

生成逻辑

1
2
3
4
5
6
7
u:基于当前时间与固定时间戳 Wr = 1682924400 之间的差值,除以 1000 后向下取整。
c:u 转为 Uint32Array,再转为 Uint8Array。
e:从页面 <meta name="twitter-site-verification"> 中获取 content 值,并 Base64 编码后转为 Uint8Array。
i:结合页面中的 SVG 元素的 color 和 transform 样式,提取数字并转为 16 进制字符串。
构造 additionalData:拼接 u、obfiowerehiring、e 的部分字节以及 i。
盐值
盐值:需要从混淆代码 Rr[d(0, 0, "$pj6", oW)] 获取(可能是 "my_secret_key" 或其他值)。

生成步骤

生成 u 和 c:通过时间戳计算得到 u,并将其转为 Uint8Array 格式的 c。

1
2
3
4
const Wr = 1682924400;
const u = Math.floor((Date.now() - Wr * 1000) / 1000);
const c = new Uint8Array(new Uint32Array([u]).buffer);
生成 e:假设页面 <meta> 中的 content 值为 some_verification_token,并转换为 Base64 编码的 Uint8Array
1
2
const content = "some_verification_token";
const e = new Uint8Array([...atob(btoa(content))].map(c => c.charCodeAt(0)));

生成 i:从 SVG 样式计算得到 i,在此假设为 170a3d70a3d70a0170a3d70a3d70a100。
构造 additionalData:拼接 u、obfiowerehiring、e 的部分字节(16 进制),以及 i。

1
const additionalData = `${u}obfiowerehiring${e.slice(0, 8).map(b => b.toString(16).padStart(2, '0')).join('')}${i}`;

哈希前拼接:拼接 method、endpoint、additionalData 和盐值。

1
const input = [method, endpoint, additionalData].join("!") + salt;

SHA256 哈希:计算拼接字符串的 SHA256 哈希。

1
const hash = crypto.createHash('sha256').update(input).digest();

哈希后拼接:拼接 yr_o、yr_u、hash、cc=3、sc 等字节数组。

const concatArray = [
…yr_o,
…yr_u,
…hash,
3,
…sc.map(c => c.charCodeAt(0))
];
字节数组转字符串:使用 String.fromCharCode() 将字节数组转为字符串。

const concatString = String.fromCharCode(…concatArray);
Base64 编码:将字符串转为 Base64 编码,并移除填充 =。

1
const xClientTransactionId = Buffer.from(concatString).toString('base64').replace(/=/g, '');

实现代码(Node.js)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
const crypto = require('crypto');

// 输入参数
const method = "POST";
const endpoint = "/i/api/1.1/jot/error_log.json";
const yr_o = [
39, 54, 185, 185, 21, 119, 54, 200, 58, 152, 93, 231, 27, 248, 40, 145,
226, 205, 178, 9, 181, 30, 91, 248, 37, 27, 71, 55, 89, 15, 9, 197,
42, 182, 81, 240, 111, 220, 135, 157, 167, 129, 114, 128, 131, 230, 75, 46
];
const yr_u = [112, 196, 183, 3];
const sc = ["-", "0"];
const salt = "my_secret_key"; // TODO: 替换为实际盐值(从 Rr[d(0, 0, "$pj6", oW)] 获取)

// 生成 u 和 c
const Wr = 1682924400;
const u = Math.floor((Date.now() - Wr * 1000) / 1000); // 示例:5276520
const c = new Uint8Array(new Uint32Array([u]).buffer); // 示例:[72, 108, 80, 0]

// 生成 e(假设 twitter-site-verification 的 content)
const content = "some_verification_token"; // TODO: 替换为实际值
const e = new Uint8Array([...atob(btoa(content))].map(c => c.charCodeAt(0)));

// 生成 i(假设 SVG 样式计算)
const i = "170a3d70a3d70a0170a3d70a3d70a100"; // TODO: 替换为实际 SVG 计算结果

// 生成 additionalData
const additionalData = `${u}obfiowerehiring${e.slice(0, 8).map(b => b.toString(16).padStart(2, '0')).join('')}${i}`;
console.log("additionalData:", additionalData);

## 1. 哈希前拼接
const input = [method, endpoint, additionalData].join("!") + salt;
console.log("哈希前拼接:", input);

## 2. SHA256 哈希
const hash = crypto.createHash('sha256').update(input).digest();
console.log("SHA256 哈希 (hex):", hash.toString('hex'));

## 3. 哈希后拼接
const concatArray = [
...yr_o, // yr(o) 字节数组 (rand_bytes)
...yr_u, // yr(u) 字节数组 (time_bytes)
...hash, // sha256_bytes
3, // cc (固定值 3)
...sc.map(c => c.charCodeAt(0)) // sc 字节数组 ([45, 48])
];

// === 字节数组转字符串关键代码 ===
const concatString = String.fromCharCode(...concatArray);
// === 字节数组转字符串关键代码结束 ===
console.log("哈希后拼接 (字符串):", JSON.stringify(concatString));
console.log("拼接字符串长度:", concatString.length); // 应为 87

## 4. Base64 编码
const xClientTransactionId = Buffer.from(concatString).toString('base64').replace(/=/g, '');
console.log("x-client-transaction-id:", xClientTransactionId);

调试与验证

1
2
3
4
验证 additionalData:检查 u、c、e 和 i 是否正确拼接。
哈希前拼接:确保拼接的字符串与预期一致。
哈希后拼接:验证字节数组长度和内容是否与预期一致。
Base64 输出:检查生成的 x-client-transaction-id 是否匹配实际值。

下一步

1
2
3
4
提供实际的 twitter-site-verification 的 content 值。
提供 SVG 元素的样式计算结果。
确认盐值的来源。
通过这一步骤,您可以生成符合 X API 要求的 x-client-transaction-id。