File size: 3,813 Bytes
5ef6e9d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
/**
 * geminigen.ai x-guard-id header generator
 *
 * Reverse-engineered from geminigen.ai/_nuxt/nBh81x6X.js
 * Constants extracted from: window.__NUXT__.config.public.antibot
 *
 * Algorithm:
 *   stableId   = 22-char alphanumeric string (persistent per server instance)
 *   timeBucket = Math.floor(Date.now() / 60000)  β€” changes every 60 seconds
 *   domFp      = "0".repeat(32)                  β€” server has no DOM
 *
 *   hmacKey    = SHA256(`${SECRET_KEY}:${stableId}`).slice(0, 32)
 *   signHash   = SHA256(`${path}:${METHOD}:${hmacKey}:${timeBucket}:${SECRET_KEY}`)
 *
 *   payload    = [0x01, ...hexBytes(hmacKey),   // 16 bytes
 *                       ...uint32BE(timeBucket), //  4 bytes
 *                       ...hexBytes(signHash),   // 32 bytes
 *                       ...hexBytes(domFp)]      // 16 bytes  β†’ 69 bytes total
 *
 *   x-guard-id = base64url(payload)  (no padding)
 */

import { createHash, randomBytes } from "crypto";

const SECRET_KEY      = "45NPBH$&";
const TIME_BUCKET_MS  = 60_000;
const STABLE_ID_LEN   = 22;
const DOM_FP          = "0".repeat(32); // 32 hex zeros β†’ 16 zero bytes

// ── One stable-id per server instance ────────────────────────────────────────
function makeStableId(): string {
  const rand  = randomBytes(16).toString("hex");
  const hash  = createHash("sha256").update(`server:${rand}`).digest("hex");
  // Take alphanumeric chars from the hex (all are [0-9a-f] so fine for [A-Za-z0-9])
  return hash.slice(0, STABLE_ID_LEN);
}

let _stableId: string = makeStableId();

/** Replace the stable-id (e.g., if you want to rotate it). */
export function rotateStableId(): void { _stableId = makeStableId(); }

// ── Helpers ───────────────────────────────────────────────────────────────────

/** SHA-256 of a UTF-8 string β†’ lowercase hex (64 chars). */
function sha256(str: string): string {
  return createHash("sha256").update(str, "utf8").digest("hex");
}

/** Convert a hex string to a byte array (every 2 hex chars β†’ 1 byte). */
function hexToBytes(hex: string): number[] {
  const bytes: number[] = [];
  for (let i = 0; i < hex.length; i += 2) {
    bytes.push(parseInt(hex.substring(i, 2 + i), 16));
  }
  return bytes;
}

/** Encode uint32 as 4 big-endian bytes. */
function uint32BE(n: number): number[] {
  return [(n >>> 24) & 255, (n >>> 16) & 255, (n >>> 8) & 255, n & 255];
}

// ── Public API ─────────────────────────────────────────────────────────────────

/**
 * Generate the `x-guard-id` header value for a given API path + HTTP method.
 *
 * @param path   e.g. "/api/video-gen/grok-stream"
 * @param method e.g. "post"
 */
export function generateGuardId(path: string, method: string): string {
  const stableId   = _stableId;
  const timeBucket = Math.floor(Date.now() / TIME_BUCKET_MS);

  // hmacKey: first 32 hex chars of SHA256("${SECRET_KEY}:${stableId}") β†’ 16 bytes
  const hmacKey  = sha256(`${SECRET_KEY}:${stableId}`).slice(0, 32);

  // signHash: full 64-hex SHA256 β†’ 32 bytes
  const signHash = sha256(`${path}:${method.toUpperCase()}:${hmacKey}:${timeBucket}:${SECRET_KEY}`);

  const payload: number[] = [
    1,                              // version byte (LK = 1)
    ...hexToBytes(hmacKey),         // 16 bytes
    ...uint32BE(timeBucket),        // 4 bytes
    ...hexToBytes(signHash),        // 32 bytes
    ...hexToBytes(DOM_FP),          // 16 bytes
  ];

  return Buffer.from(payload).toString("base64url"); // Node β‰₯ 16, no trailing =
}