| name | crypto-recon-debugging |
| description | Verifies reconstructed crypto/signing is correct by testing against live API. Finds exactly what's wrong when signature fails. Extends superpowers systematic-debugging with HTTP-level validation. |
Crypto Recon — Signature Debugging Agent
Purpose
The reconstructed Python function looks right but the API returns 401/403. Find out exactly why and fix it. This is systematic debugging applied to cryptographic reverse engineering — no guessing, no random tweaks.
This skill extends superpowers systematic-debugging. All its iron laws apply here.
Do NOT modify the reconstruction randomly hoping it fixes things.
Do NOT assume the algorithm is wrong before exhausting parameter debugging.
Every fix must have a root cause. Symptom patches are failure.
The Iron Law (from systematic-debugging)
NO FIXES WITHOUT ROOT CAUSE FIRST
Common traps to avoid:
- "Maybe it's SHA256 not MD5" → NO. Verify current algo first.
- "Let me try lowercase" → NO. Capture expected output first.
- "Maybe it needs URL encoding" → NO. Prove this with evidence.
Failure Taxonomy
Before debugging, classify what kind of failure this is:
| Response | Meaning | First check |
|---|
| HTTP 401 | Signature wrong or missing | Check signature field name and placement |
| HTTP 403 | Auth valid but forbidden | Wrong token, expired, or IP-blocked |
| HTTP 400 | Bad request format | Check JSON structure, field names |
HTTP 200 + {"code": -1, "msg": "sign error"} | Signature computed wrong | Debug signing algo |
HTTP 200 + {"code": -1, "msg": "timestamp expired"} | Time sync issue | Check timestamp format |
HTTP 200 + {"code": -1, "msg": "random repeat"} | Nonce collision | Ensure UUID is fresh per request |
Phase 1: Capture Ground Truth
You cannot debug without a known-good reference.
Method A: Browser DevTools (preferred)
Tell user to:
- Open DevTools → Network tab
- Perform the action in browser (login, register, etc.)
- Find the request → Headers + Payload
- Copy:
- Full request body (JSON)
- All request headers (especially
X-Sign, Authorization, X-Token etc.)
- The actual signature value sent
This gives us: input → expected output. We test our function produces the same output.
Method B: Intercept with mitmproxy
pip install mitmproxy
mitmproxy --mode transparent --ssl-insecure
→ Set browser proxy → perform action → copy request in mitmproxy UI.
Method C: From existing reconstructed code
User already has a working generate_sign_params output from prior analysis — use that as reference.
From the captured request, extract:
Input data: {"userId": "12345", "amount": "100", "gameId": "5"}
Expected: signature = "A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6"
random = "a7f3b2c1d4e5f6a7b8c9d0e1f2a3b4c5"
timestamp = 1748123456
Phase 2: Test Our Function Against Reference
import sys
sys.path.insert(0, "output/[domain]/final")
from reconstruct_md5_generateSignParams import generate_sign_params
test_input = {"userId": "12345", "amount": "100", "gameId": "5"}
random_str, signature, timestamp = generate_sign_params(test_input)
expected_sig = "A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6"
print(f"Expected: {expected_sig}")
print(f"Got: {signature}")
print(f"Match: {'YES' if signature == expected_sig else 'NO'}")
If Match: YES → signature logic is correct. Problem is elsewhere (header name, field placement, timestamp format).
If Match: NO → dig into Phase 3.
Phase 3: Binary Search the Discrepancy
If signature doesn't match, isolate which step is wrong. Test each step independently.
Step A: Verify the canonical string
import json, uuid
data = {"userId": "12345", "amount": "100", "gameId": "5"}
data["random"] = "a7f3b2c1d4e5f6a7b8c9d0e1f2a3b4c5"
exclude = {"signature", "track"}
filtered = {k: v for k, v in sorted(data.items())
if k not in exclude and v is not None and v != ""}
canonical = json.dumps(filtered, separators=(",", ":"), ensure_ascii=False)
print(f"Canonical: {repr(canonical)}")
Common canonical string bugs:
| Bug | Symptom | Fix |
|---|
| Wrong sort order | Keys in different order | Check JS: sort() vs sort((a,b) => a.localeCompare(b)) |
| Extra spaces in JSON | ": " vs ":" | Use separators=(",",":") |
| ensure_ascii wrong | Unicode chars mangled | Add ensure_ascii=False |
| Wrong exclude list | Extra/missing keys | Re-read JS exclude array verbatim |
| Null not filtered | None included | Check: val is not None and val != "" |
| Number as string | "100" vs 100 | JS JSON.stringify preserves type — match it |
Step B: Verify the hash
import hashlib
canonical = '{"amount":"100","gameId":"5","random":"a7f3...","userId":"12345"}'
print(hashlib.md5(canonical.encode("utf-8")).hexdigest())
print(hashlib.md5(canonical.encode("utf-8")).hexdigest().upper())
print(hashlib.md5(canonical.encode("utf-8")).hexdigest().upper()[:32])
print(hashlib.md5(canonical.encode("utf-8")).hexdigest()[:16])
print(hashlib.md5(canonical.encode("utf-8")).hexdigest().upper()[:16])
Compare each variant against the expected signature.
Step C: Verify encoding/encoding chain
If it's HMAC, test the key:
import hmac, hashlib
key = "your_extracted_key"
sig = hmac.new(key.encode(), canonical.encode(), hashlib.sha256).hexdigest()
sig_upper = sig.upper()
If it's AES, test the IV handling:
Step D: Verify field placement in request
If signature is correct but request still fails:
import requests
session = requests.Session()
from http.client import HTTPConnection
HTTPConnection.debuglevel = 1
import logging
logging.basicConfig()
logging.getLogger().setLevel(logging.DEBUG)
requests_log = logging.getLogger("requests.packages.urllib3")
requests_log.setLevel(logging.DEBUG)
requests_log.propagate = True
Check: Is signature in body? URL params? Headers? Is field name exactly signature or sign or _sign?
Phase 4: Live API Test
Once canonical string and hash both match reference — test against live API:
import cloudscraper, json, sys
def test_endpoint(url, payload, expected_status=200):
scraper = cloudscraper.create_scraper()
resp = scraper.post(url, json=payload, timeout=30)
print(f"Status: {resp.status_code}")
print(f"Body: {resp.text[:500]}")
return resp.status_code == expected_status
Success criteria:
- HTTP 200 AND response body
code == 0 (or success == true) → DONE
- HTTP 200 AND
code == -1, msg == "sign error" → signature still wrong, back to Phase 3
- HTTP 401 → signature missing from request, check header/param names
- HTTP 403 → IP block, token expired, or account issue — not a crypto problem
Phase 5: Root Cause Documentation
When fixed, document exactly what was wrong in the report:
## Debugging Log — [domain]
Issue: signature mismatch
Root cause: JSON.stringify in JS uses integer type for `amount` field,
but our Python sent it as string. JS: `{"amount": 100}`,
Python: `{"amount": "100"}`. Fixed by casting numeric fields.
Fix applied in: output/[domain]/final/reconstruct_md5_generateSignParams.py
Verified: live API returned HTTP 200, code=0
Append this to output/[domain]/final/[domain].md.
Integration with Superpowers systematic-debugging
Follow superpowers debugging phases:
Phase 1 (Root Cause Investigation) = our Phase 1-3
- Capture reference → test function → binary search
Phase 2 (Fix) = our Phase 4-5
- Only fix AFTER root cause identified
- Document the fix, don't just patch
Never skip Phase 1. Even if you're sure it's an encoding issue — verify it first.
Quick Diagnosis Checklist
When first receiving a "signature wrong" report, run through this list IN ORDER:
[ ] Captured a real request from browser with known input → expected output?
[ ] Tested our function with THAT EXACT input?
[ ] Canonical string matches character-for-character?
[ ] Sort order matches?
[ ] JSON separators correct (no spaces)?
[ ] Exclude list matches JS verbatim?
[ ] Null/empty filtering matches JS behavior?
[ ] Hash algorithm confirmed (md5 not sha256)?
[ ] Output case matches (upper vs lower)?
[ ] Output length matches (full 32 vs truncated)?
[ ] HMAC key confirmed correct?
[ ] Nonce is fresh per request (not reused)?
[ ] Timestamp format correct (ms vs seconds)?
[ ] Field name in request matches JS exactly (signature vs sign vs _sign)?
[ ] Field placement correct (body vs params vs headers)?
Stop at the first [ ] that fails — that's your root cause.