KT验证系统 KT验证系统 Docs
DEVELOPER DOCUMENTATION

接入 KT验证系统

用一次 HTTP 请求完成卡密验证。本页包含接口字段、响应格式、设备码规范与可运行示例。

API v1 最后更新 2026-10-24 适用于所有语言
QUICKSTART

快速开始

从生成第一张卡密,到客户端成功验证,只需要三个步骤。

01

登录后台生成卡密

在「生成卡密」页选择应用、有效期和设备上限。完整卡密只显示一次,请复制或下载 CSV。

02

客户端提交验证

把卡密、应用标识和稳定设备码提交到 /api/public/verify。

03

根据 valid 放行

响应中 data.valid === true 才进入主功能,否则提示原因并退出。

在线验证

本系统采用在线卡密模式,客户端每次启动都应请求服务器验证,不能仅凭本地缓存永久放行。

CONCEPTS

核心概念

卡密

随机生成的授权凭证,保存时只存 HMAC 摘要和尾号。首次验证即激活,并开始计算有效期。

应用标识

app_code 用于隔离不同软件。卡密只能通过生成时所属应用的验证。

设备码

device_id 是客户端生成的稳定标识。系统只保存其 HMAC 摘要,用于限制一码多用。

授权状态

未使用、使用中、已过期、已停用。停用或到期后,下一次验证会立即返回失败。

API REFERENCE

验证接口

该接口无需登录或 API Key,卡密本身就是凭证。生产环境必须使用 HTTPS。

POST /api/public/verify
业务失败也返回 HTTP 200

卡密过期、设备超限等结果使用 data.valid = false 表示。不要只检查 HTTP 状态码,必须读取响应体中的 valid 字段。

cURL
curl -X POST "https://your-domain.com/api/public/verify" \
  -H "Content-Type: application/json" \
  -d '{
    "app_code": "demo-app",
    "card_key": "KT-7F2M-9QXA-B4TN-H8WP-3C6D-2E9F-5J7K-AB",
    "device_id": "web-4a3f0f2e-8b90-4c94-9d2e-8e72f3cda001",
    "device_name": "Windows 11 / 我的电脑"
  }'
REQUEST

请求参数

字段类型必填说明
app_codestring是应用标识,在后台「应用管理」中查看,例如 demo-app。
card_keystring是用户输入的卡密,不区分大小写,可包含横线或空格。
device_idstring是稳定设备码,长度 6-200,建议为 UUID 或硬件指纹哈希。同一设备必须保持不变。
device_namestring否设备显示名,便于后台识别,例如 Windows 11 · 办公室电脑,最长 80 字符。

请求体必须是 application/json,最大 1 MB。接口按 IP 和卡密分别限流,超出后返回 HTTP 429。

RESPONSE

响应格式

验证成功

200 OK · application/json
{
  "ok": true,
  "data": {
    "valid": true,
    "code": "activated",
    "message": "激活成功,授权已生效",
    "activated_now": true,
    "app": { "code": "demo-app", "name": "示例软件" },
    "card": {
      "masked_key": "KT-****-****-****-AB",
      "activated_at": "2026-10-24T05:27:45Z",
      "expires_at": "2026-10-31T05:27:45Z",
      "permanent": false,
      "duration_seconds": 604800,
      "remaining_seconds": 604800,
      "device_limit": 1,
      "devices_bound": 1
    },
    "device": { "name": "Windows 11 / 我的电脑", "current": true }
  }
}

首次成功验证时 code 为 activated;之后为 valid。永久卡密没有 expires_at,并返回 permanent: true。

验证失败

200 OK · 业务失败
{
  "ok": true,
  "data": {
    "valid": false,
    "code": "device_limit",
    "message": "该卡密已绑定 1 台设备,已达到上限",
    "card": {
      "masked_key": "KT-****-****-****-AB",
      "device_limit": 1,
      "devices_bound": 1
    }
  }
}
validboolean,唯一判断依据。为 true 时才能放行。
code机器可读的结果码,见下方错误码表。
message可直接展示给用户的中文说明。
card卡密信息,失败时可能只包含部分字段。
ERRORS

错误码

codeHTTP含义客户端处理建议
invalid_key200卡密不存在、格式错误或不属于该应用。提示检查卡密,不要自动重试。
expired200卡密有效期已结束。提示续费或获取新卡密。
disabled200管理员已停用卡密。提示联系发卡方。
device_limit200已达到设备绑定上限。提示联系管理员解绑。
app_disabled200应用被管理员暂停验证。提示稍后再试或联系管理员。
invalid_device400设备码为空或格式不正确。检查客户端设备码生成逻辑。
rate_limited429请求过于频繁。读取 Retry-After,指数退避。
internal_error500服务器内部错误。延迟重试,并通知管理员。
不要在网络错误时放行

无法连接验证服务器时,应提示用户检查网络或稍后再试;不要在异常分支默认解锁。

DEVICE ID

设备码规范

设备码决定了「一码一机」是否可靠。它必须在同一台机器上保持稳定,并且不要包含可直接识别个人身份的信息。

✓ 推荐做法

  • 桌面端组合主板序列号、系统盘序列号、CPU ID 后做 SHA-256。
  • Web 端生成随机 UUID 并保存在 localStorage。
  • 首次生成后写入配置文件或系统注册表,后续复用。
  • 只提交哈希值或随机 ID,不提交原始硬件序列号。

× 避免做法

  • 每次启动随机生成,导致每次都变成新设备。
  • 直接使用 IP 地址或 MAC 地址(容易变化)。
  • 直接提交用户名、手机号等个人信息。
  • 把卡密写入可被轻易复制的明文配置后不再验证。
PYTHON

Python 示例

使用 requests 验证卡密。设备码会持久化到用户目录,避免每次启动都变化。

verify.py
import hashlib
import json
import platform
import subprocess
import uuid
from pathlib import Path

import requests

API = "https://your-domain.com/api/public/verify"
APP_CODE = "demo-app"
DEVICE_FILE = Path.home() / ".myapp" / "device_id"


def get_device_id() -> str:
    """首次启动生成并保存,之后保持不变。"""
    if DEVICE_FILE.exists():
        return DEVICE_FILE.read_text(encoding="utf-8").strip()
    seed = f"{platform.node()}-{uuid.getnode()}-{uuid.uuid4()}"
    device_id = "pc-" + hashlib.sha256(seed.encode()).hexdigest()[:32]
    DEVICE_FILE.parent.mkdir(parents=True, exist_ok=True)
    DEVICE_FILE.write_text(device_id, encoding="utf-8")
    return device_id


def verify(card_key: str) -> dict:
    response = requests.post(
        API,
        json={
            "app_code": APP_CODE,
            "card_key": card_key.strip(),
            "device_id": get_device_id(),
            "device_name": f"{platform.system()} {platform.release()}",
        },
        timeout=8,
    )
    response.raise_for_status()
    result = response.json()["data"]
    if not result["valid"]:
        raise RuntimeError(result["message"])
    return result


if __name__ == "__main__":
    try:
        info = verify(input("请输入卡密: "))
        print(f"授权有效,到期时间:{info['card'].get('expires_at') or '永久'}")
        # 在这里启动你的主程序
    except Exception as exc:
        print(f"授权失败:{exc}")
        input("按回车退出...")
        raise SystemExit(1)
C# / .NET

C# 示例

LicenseService.cs
using System.Net.Http.Json;

public record VerifyRequest(
    string app_code, string card_key, string device_id, string device_name);

public sealed class LicenseService
{
    private static readonly HttpClient Http = new()
    {
        Timeout = TimeSpan.FromSeconds(8)
    };

    private const string Api =
        "https://your-domain.com/api/public/verify";

    public static async Task<bool> VerifyAsync(string cardKey)
    {
        var payload = new VerifyRequest(
            "demo-app",
            cardKey.Trim(),
            DeviceId.Current,      // 稳定的设备码
            "Windows · 我的电脑");

        var response = await Http.PostAsJsonAsync(Api, payload);
        response.EnsureSuccessStatusCode();

        var envelope = await response.Content
            .ReadFromJsonAsync<ApiEnvelope>();

        if (envelope?.Data is null || !envelope.Data.Valid)
        {
            MessageBox.Show(envelope?.Data?.Message ?? "验证失败");
            return false;
        }

        return true;
    }
}

C# 示例省略了 ApiEnvelope 数据模型定义,按上方 JSON 结构创建对应属性即可。

GATING

正确的放行逻辑

授权验证不是一个「提示框」,而是主功能的前置条件。所有受保护入口都应经过同一处判断。

1启动软件

读取本地设备码

→
2请求验证

提交卡密和设备码

→
3检查 valid

仅 true 时继续

→
4进入主功能

false 或异常则退出

  • 01

    只认 data.valid。不要因为 HTTP 200、响应存在或字段缺失就放行。

  • 02

    失败分支必须终止。验证失败后禁用主功能,而不是只弹一个可以关闭的提示。

  • 03

    保护关键逻辑。前端隐藏按钮不等于安全;服务端接口和核心数据也要校验授权。

  • 04

    定期复验。长时间运行的程序建议每隔数小时重新验证一次,以便停用即时生效。

DEPLOYMENT

部署说明

本机运行

Windows / 命令行
# 双击 start.bat,或手动运行
python app.py --port 8765

# 允许局域网访问(注意 Windows 防火墙)
python app.py --lan --port 8765

# 首次启动默认管理员
# 账号:admin
# 密码:admin123456

环境变量

变量默认值说明
KEYGUARD_HOST127.0.0.1监听地址。对外服务可设为 0.0.0.0。
KEYGUARD_PORT8765监听端口。
KEYGUARD_DATA_DIR./data数据库与密钥目录。
KEYGUARD_ADMIN_USERadmin首次初始化时的管理员账号。
KEYGUARD_ADMIN_PASSWORDadmin123456首次初始化时的管理员密码。
KEYGUARD_TRUST_PROXY0位于反向代理后时设为 1,用于读取真实 IP。
KEYGUARD_SECURE_COOKIES0HTTPS 部署时设为 1,Cookie 仅通过 HTTPS 发送。
KEYGUARD_CORS_ORIGIN空仅当浏览器端跨域调用验证接口时设置精确来源。

对外部署建议

  • 01

    使用 Nginx / Caddy 反向代理到 127.0.0.1:8765,并配置 HTTPS 证书。

  • 02

    设置 KEYGUARD_TRUST_PROXY=1 和 KEYGUARD_SECURE_COOKIES=1,否则登录 Cookie 与限流 IP 可能不正确。

  • 03

    备份 data/keyguard.db 和 data/secret.key。丢失密钥会导致所有卡密无法验证。

  • 04

    不要把 data/ 目录暴露为静态文件;建议仅开放 443 端口,后端 8765 不直接对公网开放。

部署前必须修改默认密码

如果沿用初始密码,任何知道默认值的人都能进入后台。登录后请前往「账号安全」修改。

SECURITY

安全建议

  • 01

    始终使用 HTTPS。卡密在请求体中传输,明文 HTTP 可能被截获。

  • 02

    不要在客户端硬编码管理员密码。管理后台只供管理员使用,客户端只调用验证接口。

  • 03

    设备码只做授权用途。保存 HMAC 摘要而非原始硬件信息,避免不必要的隐私风险。

  • 04

    限流与日志。系统默认对验证和登录有限流,建议定期查看验证日志中的异常 IP。

  • 05

    合规使用。仅将本系统用于合法的软件授权和数字内容保护,不得用于恶意软件或规避他人授权。

FAQ

常见问题

关闭生成结果弹窗后,还能找回完整卡密吗?

不能。系统只保存卡密的 HMAC 摘要和尾号,完整卡密仅在生成成功时返回一次。请重新生成,或在关闭前复制 / 下载 CSV。

有效期从什么时候开始计算?

从用户首次成功验证并绑定设备时开始,不是从生成时间开始。未使用的卡密不会消耗时间。

用户换电脑了怎么办?

管理员在「卡密管理」中打开卡密详情,点击「解绑设备」。用户下次验证即可绑定新设备,已使用的有效期不会重置。

可以给不同软件发不同的卡密吗?

可以。在「应用管理」创建多个应用,每个应用有独立 app_code。卡密不能跨应用使用。

支持离线验证吗?

当前版本是在线卡密系统,需要客户端能够访问验证服务器。无法联网时不应默认放行。

怎么判断卡密是不是被多人共用?

把设备上限设为 1,系统会拒绝第二台设备。验证日志中可以看到不同设备尝试被拦截的记录。

准备开始了吗?

生成第一张卡密,三分钟完成接入。

进入管理后台