API 同时支持:
GET + MD5 签名POST + HMAC-SHA256 + 时间戳/随机数 防重放生产环境域名: https://commu.fun/
通用返回格式:
| 字段名 | 说明 |
|---|---|
| code | 错误码,请求成功为 0 |
| msg | 返回的消息内容,一般为错误信息 |
| data | 接口返回数据 |
| encrypted_data | (可选) 加密后的数据,如果开启了加密模式 |
enc_ver=2:使用 AES-GCM 解密 encrypted_dataenc_ver:视为 legacy AES-CBC(仅用于兼容旧客户端)系统支持 Lua 脚本分发(普通模式/安全模式)。完整对接教程提供下载:
POST /api/v1/activateGET /api/v2/secure/public_key、POST /api/v2/secure/verify、GET /api/v2/secure/downloadPOST /api/v2/check(推荐)
GET /check
这个API 用于验证卡密状态、激活新卡密以及绑定机器码。
| 参数名 | 说明 |
|---|---|
| key | 卡密字符串 (例如: KEY-XXXX) |
| hwid | 机器码 (设备唯一标识) |
| instance_id | 软件实例ID (Software Instance ID) |
| sign | 签名 (算法见下文) |
v2 版本改为 JSON Body:
{"key":"KEY-XXXX","hwid":"HWID-...","instance_id":"12345678"}
并携带请求头:
X-Timestamp: 秒级时间戳X-Nonce: 随机字符串(一次性)X-Signature: HMAC-SHA256 签名{
"code": 0,
"msg": "success",
"data": {
"status": "valid",
"expire_date": "2026-12-31 23:59:59",
"hwid": "HWID-ABC-123",
"announcement": {
"title": "系统公告",
"content": "欢迎使用本系统!",
"time": "2026-01-01 12:00:00"
}
}
}
公告不需要单独调用接口获取:系统会在以下接口的成功响应中自动携带 announcement 字段(可能为 null):
/api/v2/check、/check/api/v2/unbind、/unbind/api/v2/info、/info/cloudvar/log进入后台管理面板 → 公告管理:
announcement.titleannouncement.content服务端会从公告表中取出最近一条启用公告作为下发内容:
is_active = true 且(software_id = 当前实例ID 或 software_id is null)created_at 倒序,取最新 1 条也就是说:如果你后创建了一条全局公告,它会覆盖旧的实例公告(因为更“新”)。
POST /api/v2/heartbeat(推荐)
GET /heartbeat
客户端激活卡密后,定期调用此接口检测卡密是否仍然有效(过期/封禁/机器码不匹配等)。
与 /check 不同,心跳接口不会触发激活、不会绑定/换绑机器码、不写入事件日志,适合高频轮询。
| 参数名 | 说明 |
|---|---|
| key | 卡密字符串 (例如: KEY-XXXX) |
| hwid | 机器码 (设备唯一标识) |
| instance_id | 软件实例ID (Software Instance ID) |
| sign | 签名 (算法见下文) |
v2 版本改为 JSON Body:
{"key":"KEY-XXXX","hwid":"HWID-...","instance_id":"12345678"}
并携带请求头:
X-Timestamp: 秒级时间戳X-Nonce: 随机字符串(一次性)X-Signature: HMAC-SHA256 签名卡密有效:
{
"code": 0,
"msg": "success",
"data": {
"status": "valid",
"expire_date": "2026-12-31 23:59:59",
"remaining_seconds": 86400,
"announcement": null
}
}
永久卡密:
{
"code": 0,
"msg": "success",
"data": {
"status": "valid",
"expire_date": "永久",
"remaining_seconds": null,
"announcement": null
}
}
卡密已过期:
{"code": 4, "msg": "卡密已过期"}
/check 的区别| 特性 | /check(/v2/check) |
/heartbeat(/v2/heartbeat) |
|---|---|---|
| 激活未使用卡密 | ✅ 会激活 | ❌ 返回 code=7 |
| HWID 绑定 | ✅ 自动绑定/换绑 | ❌ 仅校验是否匹配 |
| 事件日志 | ✅ 写入 EventLog | ❌ 不写入 |
| 更新 last_seen | ✅ | ✅ |
| 返回剩余秒数 | ❌ | ✅ remaining_seconds |
| 适用场景 | 首次激活 / 启动校验 | 运行中定期心跳 |
| code | 说明 |
|---|---|
| 7 | 卡密未激活 |
其余错误码与 /check 一致(见下方错误码说明)。
POST /api/v2/unbind(推荐)
GET /unbind
用于解绑卡密的机器码绑定。
| 参数名 | 说明 |
|---|---|
| key | 卡密字符串 |
| hwid | 当前绑定的机器码 |
| instance_id | 软件实例ID |
| sign | 签名 |
{
"code": 0,
"msg": "success",
"data": {
"status": "unbound_success"
}
}
当卡密已激活且新设备上的 hwid 与已绑定的不同:
unbind_limit,且尚未用尽:hwid)unbind_count 加 1code=6,msg="当前卡密已绑定,无可换绑次数"说明:
unbind_limit 或设置为负数,视为不限制;建议在后台为新卡密设置明确上限。POST /api/v2/info(推荐)
GET /info
获取软件的最新版本号和更新内容。
| 参数名 | 说明 |
|---|---|
| instance_id | 软件实例ID |
| sign | 签名 |
{
"code": 0,
"msg": "success",
"data": {
"version": "1.0.2",
"update_content": "1. 修复了已知BUG\n2. 优化了性能"
}
}
GET /cloudvar
获取软件配置的云端变量(配置项)。
| 参数名 | 说明 |
|---|---|
| key | 变量名 (Key) |
| instance_id | 软件实例ID |
| sign | 签名 |
{
"code": 0,
"msg": "success",
"data": {
"value": "这里是云变量的值",
"announcement": null
}
}
GET /log
客户端上报自定义事件日志到后台。
| 参数名 | 说明 |
|---|---|
| message | 日志内容 (需 URL 编码) |
| instance_id | 软件实例ID |
| sign | 签名 |
{
"code": 0,
"msg": "success",
"data": {
"status": "logged",
"announcement": null
}
}
用于后台“卡密管理”页面对选中卡密进行批量停用/启用。该接口依赖后台登录的 Session Cookie,不用于客户端 SDK 对接。
POST /admin/card/command
停用卡密:
{"action":"deactivate","keys":["KEY-1","KEY-2"]}
启用卡密:
{"action":"activate","keys":["KEY-1","KEY-2"]}
{
"code": 0,
"msg": "success",
"data": {
"action": "deactivate",
"affected_count": 2,
"requested_count": 2,
"missing_count": 0,
"results": [
{"key":"KEY-1","ok":true,"before":"unused","after":"banned"},
{"key":"KEY-2","ok":true,"before":"unused","after":"banned"}
]
}
}
| code | 说明 |
|---|---|
| 0 | success |
| 1 | 验证失败(或“卡密不存在”,当启用详细错误时) |
| 2 | 卡密不属于该实例(启用详细错误时) |
| 3 | 机器码不匹配(主动解绑接口) |
| 4 | 卡密已过期 |
| 5 | 卡密已封禁 |
| 6 | 当前卡密已绑定,无可换绑次数(自动重绑失败,需要人工处理) |
| 7 | 卡密未激活(心跳接口专用) |
separators=(',', ':'))body_sha256 = sha256(body_bytes)METHOD\nPATH\nTIMESTAMP\nNONCE\nBODY_SHA256
其中 PATH 为请求路径(不含域名,不含 query),例如:/api/v2/check。
signature = hmac_sha256(secret_key, message)(十六进制小写)X-Timestamp/X-Nonce/X-Signature所有 legacy 接口都需要 sign 参数,计算方法如下:
sign 本身)按照参数名的 ASCII 码从小到大排序。key1=value1&key2=value2...Secret Key。示例:
假设参数为 a=1, b=2,密钥为 secret。
a=1&b=2a=1&b=2secretmd5("a=1&b=2secret") -> signwhisper_config.py 中的 API_BASE_URL/INSTANCE_ID/SECRET_KEY/ENCRYPT_KEY 替换为你的真实值模块 文件夹置于你的项目根目录或包路径下pip install pycryptodome certififrom 模块 import get_default_client
client = get_default_client(verify_ssl=True)
res = client.check("KEY-XXXX...", "HWID-...")
print(res.ok, res.message, res.payload)
从响应中读取公告(若存在):
if res.ok:
data = (res.payload or {}).get("data") or {}
anno = data.get("announcement") or None
if anno:
print("公告标题:", anno.get("title"))
print("公告内容:", anno.get("content"))
读取云变量与上报日志:
v = client.cloudvar("some_key")
print(v.ok, v.payload)
r = client.log("client started")
print(r.ok, r.payload)
.版本 2
.支持库 spec
.子程序 生成签名, 文本型
.参数 参数表, 文本型
.局部变量 待签文本, 文本型
.局部变量 通信密钥, 文本型
通信密钥 = “YOUR_SECRET_KEY”
待签文本 = 参数表 + 通信密钥
返回 (取数据摘要 (到字节集 (待签文本)))
import hashlib
def get_sign(params, secret_key):
sorted_keys = sorted([k for k in params.keys() if k != 'sign'])
param_str = "&".join([f"{k}={params[k]}" for k in sorted_keys])
raw = param_str + secret_key
return hashlib.md5(raw.encode('utf-8')).hexdigest()
如果您使用了系统的 Lua 脚本分发功能,请参考独立文档: Lua脚本分发系统说明 (LUA_SYSTEM_README.md)
Whisper 提供了一套高安全性的文件下发机制,支持 RSA+AES 双重加密传输、一次性 Token 与内存流式解密,有效防御抓包、重放与中间人攻击。
以下代码展示了如何进行安全握手、验证卡密并流式下载解密文件。
import requests
import base64
import json
import time
import os
from Crypto.Cipher import AES, PKCS1_OAEP
from Crypto.PublicKey import RSA
from Crypto.Random import get_random_bytes
from Crypto.Hash import SHA256
SERVER_URL = "http://localhost:8000"
# 请替换为您的真实卡密
CARD_KEY = "YOUR_CARD_KEY"
GCM_NONCE_SIZE = 12
GCM_TAG_SIZE = 16
def get_server_public_key():
resp = requests.get(f"{SERVER_URL}/api/v2/secure/public_key")
resp.raise_for_status()
return RSA.import_key(resp.json()["public_key"])
def encrypt_aes_gcm(key, plaintext):
cipher = AES.new(key, AES.MODE_GCM)
ciphertext, tag = cipher.encrypt_and_digest(plaintext)
return ciphertext, cipher.nonce, tag
def decrypt_aes_gcm(key, ciphertext, nonce, tag):
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
return cipher.decrypt_and_verify(ciphertext, tag)
def main():
# 1. 获取公钥
pub_key = get_server_public_key()
# 2. 准备加密请求
aes_key = get_random_bytes(32)
payload = {
"card_key": CARD_KEY,
"timestamp": time.time(),
"nonce": os.urandom(8).hex()
}
# AES 加密数据
data_bytes = json.dumps(payload).encode()
ciphertext, nonce, tag = encrypt_aes_gcm(aes_key, data_bytes)
encrypted_data = base64.b64encode(nonce + tag + ciphertext).decode()
# RSA 加密 AES 密钥
cipher_rsa = PKCS1_OAEP.new(pub_key, hashAlgo=SHA256)
encrypted_key = base64.b64encode(cipher_rsa.encrypt(aes_key)).decode()
# 3. 发送验证请求
resp = requests.post(f"{SERVER_URL}/api/v2/secure/verify", json={
"encrypted_key": encrypted_key,
"encrypted_data": encrypted_data
})
if resp.status_code != 200:
print(f"[-] 验证失败: {resp.text}")
return
# 4. 解密响应
resp_json = resp.json()
enc_payload = base64.b64decode(resp_json["payload"])
nonce_resp = enc_payload[:GCM_NONCE_SIZE]
tag_resp = enc_payload[GCM_NONCE_SIZE:GCM_NONCE_SIZE+GCM_TAG_SIZE]
ciphertext_resp = enc_payload[GCM_NONCE_SIZE+GCM_TAG_SIZE:]
plaintext_resp = decrypt_aes_gcm(aes_key, ciphertext_resp, nonce_resp, tag_resp)
data = json.loads(plaintext_resp)
print(f"[+] 验证成功,文件版本: {data['file_info']['version']}")
# 5. 下载文件
token = data["token"]
file_key = bytes.fromhex(data["file_key"])
download_url = f"{SERVER_URL}{data['download_url'].split('?')[0]}"
print("[*] 开始安全下载...")
with requests.get(download_url, params={"token": token}, stream=True) as r:
# 流式解密逻辑...
pass