Skip to content
Xeron
Go back

Reverse-Engineering Coinglass's Encrypted API

Published:
Suggest Changes

Every public API on Coinglass is encrypted. The response body is ciphertext — but the key is handed to you in the response headers.

Encryption Scheme

Coinglass uses AES-128-ECB plus double gzip; the decryption key arrives via response header:

Request  → GET /api/spot/rsi/list (with header encryption: true)
Response → Headers: { user: <base64 token>, v: "55" }

Decryption:
1. v=55 → look up constant "170b070da9654622"
2. base64(constant)[:16] → Key0
3. AES-ECB-decrypt(user_token, Key0) → gzip(actual_key)
4. Gunzip → 16-char hex key
5. AES-ECB-decrypt(encrypted_body, actual_key) → gzip(JSON)
6. Gunzip → plaintext JSON

No API key, no cookie, no session. Anyone who can make an HTTP request can decrypt any endpoint.

Key Constants

The v header selects which constant derives the first-layer key:

vConstantSource
55170b070da9654622webpack module 12471, Kt[22]
66d6537d845a964081webpack module 12471, Kt[38]
77863f08689c97435bwebpack module 12471, BatcW
1URL pathbtoa(url_path)[:16]

All constants are hardcoded in the frontend webpack bundle. v=55/66/77 are fixed; v=1 is dynamic — derived from the request path.

How It Was Reversed

The logic lives in Next.js webpack module 12471, publicly fetchable from S3:

https://s3.coinglass.com/v1/cg/_next/static/chunks/pages/_app-f75bb33a408a04d3.js

Module 12471 contains:

Key derivation is in function Yt. The three constants map to Kt[22], Kt[38], and BatcW. Obfuscation is shallow — strings are directly visible.

Implementation

# decrypt.py — core logic

_KEY_TABLE = {
    "55": "170b070da9654622",
    "66": "d6537d845a964081",
    "77": "863f08689c97435b",
}

def decrypt(encrypted_body, user_token_b64, v, url=""):
    outer = json.loads(encrypted_body)
    payload = base64.b64decode(outer["data"])
    token = base64.b64decode(user_token_b64)

    # Derive Key0
    constant = _KEY_TABLE.get(v) if v != "1" else urlparse(url).path
    key0 = base64.b64encode(constant.encode()).decode()[:16]

    # Layer 1: decrypt user token → actual key
    step1 = unpad(AES.new(key0.encode(), AES.MODE_ECB).decrypt(token), 16)
    actual_key = gzip.decompress(step1).decode()

    # Layer 2: decrypt body → plaintext
    step2 = unpad(AES.new(actual_key.encode(), AES.MODE_ECB).decrypt(payload), 16)
    return json.loads(gzip.decompress(step2).decode())

Usage:

from decrypt import fetch_and_decrypt

data = fetch_and_decrypt(
    "https://capi.coinglass.com/api/spot/rsi/list",
    {"pageSize": 500, "pageNum": 1},
)
print(data["list"][0]["rsi4h"])  # → "62.34"

Scale

More than 280 encrypted endpoints were uncovered, covering:

Full list in discovered_endpoints.py

Disclaimer

For education and research only. The scheme was reversed from publicly accessible frontend code. Please respect Coinglass’s Terms of Service.

Repo: github.com/xeronsh/coinglass-decrypt


Suggest Changes
Share this post on:

Previous Post
Just a Tool
Next Post
MiMo Code Free Model Proxy