# sdtdata-api **Repository Path**: open_soft_bhy/sdtdata-api ## Basic Information - **Project Name**: sdtdata-api - **Description**: https://www.sdtdata.com/ 站点破解api服务,供外部服务调取 - **Primary Language**: JavaScript - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-07-27 - **Last Updated**: 2026-07-28 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # sdtdata-api `https://www.sdtdata.com/` 食典通站点加密接口破解,提供 **HTTP API 服务** + **CLI 客户端** + **可复用模块**。 逆向 `foodcodex.min.js` 内嵌的 JSEncrypt 加密逻辑,把站点原本只能通过浏览器调用的加密接口,包装成标准的 HTTP API,供外部服务直接调取。 ## 逆向过程 整个项目从一个空白仓库到可部署的 Docker 服务,经历了五个阶段: ### 阶段 1:抓包定位加密接口 目标页面 `https://www.sdtdata.com/fx/fmoa/tsLibList`(食典通标准库列表)。用 Playwright 加载页面并监听 `xhr`/`fetch` 请求,捕获到列表数据走的是一个奇怪的接口: ``` POST https://www.sdtdata.com/fx/encx?<一长串 base64>= ``` - **请求体为空**,所有信息编码在 URL query 里 - **响应是明文 JSON**,只有请求被加密 - 同一明文每次加密结果不同 -> 存在随机填充 ### 阶段 2:定位加密 JS 页面加载了 4 个脚本,美化了两个关键 bundle 后 grep `encx`: ``` foodcodex.min.4b8144f8.js (美化后 main.beauty.js) L55: Tools.crypt = new JSEncrypt; Tools.crypt.setPublicKey("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQ...AQAB"); L33: jQuery.post = function(e,...) { url: Tools.encryptUrl(e), ... } L92: Tools.encryptUrl = function(e) { return !this.devMode && ... t.startWith("foodcodex") && (t = e.split("?",2))[1] ? this.contextPath + "/encx?" + this.crypt.encryptLong1(t[1]) + "&" : e }; foodcodex_libs.min.4c2d8172.js (美化后 libs.beauty.js) L7155: function P(t) { ... default_key_size=1024, default_public_exponent="010001" } L7170: P.prototype.encryptLong1 = function(t) { return e(t = this.getKey().encryptLong(t)) } L6674: I.prototype.encryptLong = function(t) { var e = (this.n.bitLength() + 7 >> 3) - 11; // 117 if (t.length > e) { 分段加密, 每段 hex 左补零到 256, 拼接 } return this.encrypt(t) } ``` 结论:**RSA-1024 + PKCS#1 v1.5**,库是 `JSEncrypt 3.0.0-beta.1`,公钥硬编码在 `Tools.crypt.setPublicKey(...)`。 ### 阶段 3:Node 复刻加密 用 Node 内置 `crypto` + `node-forge`(仅用于 DER->PEM 转换)复刻 `encryptLong1`: ```js const KEY_BYTES = 128; // RSA-1024 / 8 const CHUNK = KEY_BYTES - 11; // PKCS#1 v1.5 每块明文上限 = 117 字节 function encryptLong1(plaintext) { const bytes = jsLatin1(plaintext); // 按 charCodeAt 取字节 (与 JSEncrypt 一致) if (bytes.length > CHUNK) { let h = ""; for (let i = 0; i < bytes.length; i += CHUNK) h += padLeft(encChunk(bytes.subarray(i, i + CHUNK))); // 每块 hex 左补零到 256 return hexToB64(h); } return hexToB64(padLeft(encChunk(bytes))); } // 最终请求: POST https://www.sdtdata.com/fx/encx?= ``` 验证:用自己加密的 query 调用上游,返回 `retCode=200, counts=98296`,与浏览器原生请求完全一致 -- **加密破解成功**。 ### 阶段 4:包装为 HTTP API + Docker 把核心逻辑抽到 `lib.cjs`(可 `require` 的模块),上层加: - `server.cjs` -- Node 原生 `http` 实现 HTTP 服务,零额外依赖 - `client.cjs` -- CLI 客户端,支持 `--keyword`、`--card` 等参数 - `test.cjs` -- 端到端自检(66 项断言) - `Dockerfile` + `docker-compose.yml` -- 多阶段构建,`node:22-alpine`,非 root,内置 HEALTHCHECK ### 阶段 5:详情页解析 发现详情页 `https://www.sdtdata.com/fx/fmoa/tsLibCard/.html` 是**普通 GET**(不走加密),返回 HTML。数据散落在: - `` -- 基础字段(tsNo/tsFile/tsAbstract/...) - `
` 下的 `

` -- 摘要 - `

` 下的 `
/
` -- 国别/英文名/分类/发布部门/... - `