我的软件

已售授权

总收入

¥

可提现

¥

快速开始

我的软件

管理您发布的所有软件

软件 状态 分类 销量 操作

暂无软件

基本信息

(不可修改)

编辑应用信息

修改提示

每次修改应用信息后,应用状态将自动变更为「待审核」,需要管理员重新审核后才能上架。

仅支持英文字母、数字、横线和下划线

(由发布版本自动更新)
支持 Markdown 语法

用户可通过此链接联系您

用户可通过此链接加入应用交流群

支持 Markdown 语法

此协议将在应用详情页和购买对话框中显示。支持 Markdown 格式,如 **粗体**、*斜体*、# 标题、- 列表 等。

应用凭证

用户可通过此链接访问您的应用详情页

销售通知

当有用户购买此应用时,通过 Bark 推送通知到您的手机

启用 Bark 通知

已开启,新订单支付成功时会推送通知 未开启,开启后每次有新订单支付成功时会推送通知

Bark 是一款 iOS 推送通知应用,请在手机上安装 Bark 后获取推送地址

授权套餐

管理应用的授权价格方案

暂无授权套餐

¥
¥

隐藏后不在商店显示

自定义 JSON 参数,将在授权验证回调时传递给您的服务器。最大 4KB。

SDK 下载

下载适合您开发语言的授权验证 SDK

PHP

PHP SDK

适用于 PHP 7.4+

PY

Python SDK

适用于 Python 3.6+

JV

Java SDK

适用于 Java 8+

JS

Node.js SDK

适用于 Node.js 14+

GO

Go SDK

适用于 Go 1.16+

C

C SDK

适用于 C99+

SDK 使用说明

  • 点击语言卡片可查看集成代码,点击下载按钮可下载SDK文件
  • 下载的 SDK 已包含您应用的公钥,可直接使用
  • SDK 支持在线验证和离线验证两种模式
  • 详细集成文档请查看

复制代码到您的项目中使用

代码已包含您应用的公钥和API端点

上传参数备案

配置验证请求中允许的自定义上传参数

暂无上传参数备案

点击"添加参数"按钮添加允许的上传参数

上传参数说明

  • 参数名只能包含字母、数字和下划线,且必须以字母或下划线开头
  • 参数名长度不能超过18个字符
  • 参数名不能使用系统保留关键字
  • 只有备案的参数才能在验证请求中使用
  • 必填参数在验证时必须提供,否则验证将失败
  • 新添加的参数需要管理员审核通过后才能生效
  • 审核状态:待审核已通过已拒绝

自定义回调参数

配置授权验证成功后返回的自定义数据

暂无回调参数

点击"添加参数"按钮添加自定义参数

回调参数说明

  • 参数名只能包含字母、数字和下划线,且必须以字母或下划线开头
  • 参数名和参数值都不能为空
  • 这些参数将在授权验证成功后返回给客户端

密钥管理

管理应用的 RSA 密钥对,用于离线授权验证

公钥用于客户端验证离线授权文件的签名,请将此公钥嵌入到您的应用中

密钥生成时间

重新生成密钥

此操作不可撤销

警告:重新生成 RSA 密钥后,所有已下载的离线授权文件将失效,用户需要重新获取授权。请谨慎操作!

版本管理

管理应用的版本发布和更新

绑定授权

暂无版本记录

选择一个目标版本,所有使用该版本及更低版本的用户将被强制更新到此版本

修改状态为"已发布"将使此版本成为应用的当前版本

总数

已使用

未使用

已过期

兑换码批次

管理应用的兑换码批次

暂无兑换码批次

关于完整包

完整包用于上传应用的最新安装程序包,仅支持 ZIP 格式。上传后会自动覆盖之前的文件,无版本管理。

完整包管理

上传应用的安装程序包(ZIP格式,最大100MB)

更新时间:
文件哈希:

仅支持 ZIP 格式,最大 100MB

正在上传...

关于代理商

代理商可以为您的软件创建授权,帮助您销售和管理授权。代理商只能查看自己创建的授权,无法访问请求记录等敏感信息。

代理商列表

管理此软件的代理商

代理商 联系方式 添加时间 操作

暂无代理商

警告模式

配置盗版检测后的响应策略

正在保存警告模式...

展示页面配置

配置警告展示页面的样式和联系信息

预览展示页面

跳转页面配置

配置警告跳转页面的样式和目标地址

预览跳转页面

跳转页面倒计时结束后将跳转到此URL

自定义消息

配置警告页面显示的自定义消息

盗版记录警告管理

管理单个盗版记录的警告状态(部分警告模式下生效)

已选择 条记录
IP地址 域名 检测原因 次数 警告状态 操作

暂无盗版记录

此应用目前没有检测到未授权访问

代码迭代

管理应用各版本的代码文件,支持增量更新

总文件数

新增文件

更新文件

删除文件

代码列表

管理此版本的代码

此版本暂无代码文件

请选择一个版本

选择版本后可管理该版本的代码文件

为此版本添加或修改代码文件

相对于项目根目录的文件路径

已上传文件,保存时将使用上传的文件内容覆盖到指定路径

删除操作说明

标记为删除的文件将在客户端更新时被移除。此操作仅记录删除指令,不需要提供文件内容。

查看文件内容


            

此文件标记为删除

客户端更新时将移除此文件

导入结果

ZIP文件批量导入完成

成功

失败

跳过

处理详情

资源数量

总下载次数

已用空间

剩余空间

资源列表

管理应用的静态资源文件,通过API获取下载地址

暂无资源

上传资源

上传静态资源文件(最大50MB)

点击或拖拽文件

zip, json, xml, txt, dat, db, xdb

上传中...

替换资源文件

上传新文件

点击选择文件

上传中...

编辑资源

创建兑换码批次

为应用生成一批兑换码

可选,用于标识批次用途

最多100个

留空表示永不过期

授权记录

查看所有已售出的授权

授权码 软件 用户 套餐 创建者 状态 创建时间 操作

条记录, 第 /

暂无授权记录

您还没有任何授权记录,点击下方按钮添加第一个授权

全部应用
全部版本
官方
盗版
全部结果
成功
失败
请求ID 应用 授权码 结果 请求次数 日期 最近请求 操作

暂无请求记录

授权验证请求将记录在这里

条记录
/
可提现余额

¥

冻结金额

¥

累计提现

¥

申请提现

手续费: 1%,实际到账: ¥

提现记录

金额 手续费 实际到账 方式 状态 申请时间

暂无提现记录

总检测次数

独立IP数

独立域名数

已拉黑

盗版检测记录

追踪未授权访问尝试

软件 类型 IP地址 域名 检测原因 次数 最后检测 操作

暂无盗版检测记录

您的软件目前没有检测到未授权访问

待处理

处理中

等待回复

已解决

工单列表

管理用户提交的工单

暂无工单

用户提交的工单将显示在这里

/

开发文档

完整的集成指南和代码示例

快速开始

按照以下步骤快速集成 XingQingChuang 授权验证系统:

1

创建应用并获取密钥

在「软件管理」中创建您的应用,选择验证方式(域名/IP/文件/设备码),审核通过后获取公钥和私钥。

2

创建授权套餐

在应用详情的「授权套餐」中创建不同的套餐,设置价格、有效期和授权限制。

3

集成 SDK 代码

在应用详情的「检测代码」中选择您的开发语言,复制或下载 SDK 代码集成到项目中。

4

调用验证接口

在您的应用启动时调用 SDK 的验证方法,验证用户的授权码是否有效。

5

测试并上线

使用兑换码生成测试授权进行测试,确认无误后发布您的应用。

SDK 集成

选择您的开发语言,按照以下步骤集成 SDK:

1. 获取 SDK 和公钥文件

在应用详情的「检测代码」标签页中,您需要下载两个文件:

  • SDK 文件:选择对应语言下载验证器代码
  • 公钥文件:下载 publickey.pem 文件

2. 初始化 SDK

<?php
require_once 'LicenseValidator.php';

// 创建验证器实例
$validator = new LicenseValidator([
    'mode' => 'both',    // 验证模式: online, offline, both
    'timeout' => 10      // 请求超时时间(秒)
]);
from license_validator import LicenseValidator

# 创建验证器实例
validator = LicenseValidator(
    mode='both',    # 验证模式: online, offline, both
    timeout=10      # 请求超时时间(秒)
)
import com.xingqingchuang.LicenseValidator;

// 创建验证器实例
LicenseValidator validator = new LicenseValidator.Builder()
    .setMode("both")      // 验证模式: online, offline, both
    .setTimeout(10000)    // 请求超时时间(毫秒)
    .build();
const LicenseValidator = require('./license-validator');

// 创建验证器实例
const validator = new LicenseValidator({
    mode: 'both',    // 验证模式: online, offline, both
    timeout: 10000   // 请求超时时间(毫秒)
});
import "xingqingchuang/license"

// 创建验证器实例
validator := license.NewValidator(license.Config{
    Mode:    "both",           // 验证模式: online, offline, both
    Timeout: 10 * time.Second, // 请求超时时间
})
#include "license_validator.h"

// 创建验证器实例
LicenseValidator* validator = license_validator_create();
license_validator_set_mode(validator, "both");    // 验证模式
license_validator_set_timeout(validator, 10);     // 超时时间(秒)

3. 文件结构

将下载的 SDK 文件和公钥文件放置到您的项目中:

your-project/
├── 
├── publickey.pem          ← 公钥文件(必需)
└── your-app-files...

重要提示:公钥文件需单独放置

SDK 文件不再内嵌公钥内容,您需要从应用详情的「检测代码」标签页中单独下载 publickey.pem 文件,并将其放置在与 SDK 文件相同的目录下。

如果公钥文件不存在或路径不正确,SDK 将返回错误码 3010(公钥文件不存在)。

授权验证

调用验证方法检查用户的授权码是否有效,并通过回调参数实现服务端与客户端的数据交互。

验证方式说明

系统支持四种验证方式,创建应用时选择后不可更改。不同验证方式对应不同的套餐参数。

域名验证

适用于 Web 应用,绑定指定域名

套餐参数:最大域名数量

IP 验证

适用于服务器应用,绑定指定 IP

套餐参数:最大 IP 数量

文件验证

适用于离线软件,通过授权文件验证

套餐参数:最大激活次数

设备码验证

适用于桌面/移动应用,绑定设备标识

套餐参数:最大设备数量

API 端点

POST /api/v1/license/verify-encrypted

请求参数

参数名 类型 必填 说明
app_id string 应用 ID(18位字母数字)
encrypted_payload string RSA 加密的载荷(Base64 编码),包含必填参数:license_keyverify_typeverify_value,详见下方载荷结构

载荷结构(加密前)

以下为 RSA 加密前的 JSON 载荷参数,其中 红色背景行 为核心必填参数。

参数名 类型 必填 说明
license_key string 授权码,格式:XXXX-XXXX-XXXX-XXXX(16位字母数字,含3个连字符)
verify_type string 验证类型,可选值:domain | ip | device | file
verify_value string 验证值,与 verify_type 配合:域名(如 example.com)、IP地址、设备ID 或 文件哈希
upload_params object 自定义上传参数(需预先在开发者后台备案),JSON对象格式
info string 可选:与 upload_params 并列,不需要备案;加密的 app/.info 内容字符串(RSA 公钥加密后得到的 Base64 字符串)。传与不传不影响验证流程;无大小限制(客户端/SDK 需确保能被 RSA 分段加密承载)。注意:info 不能放在 upload_params 内;若误放入 upload_params,服务端会忽略该键。 服务端会解析并写入请求记录的「被授权用户信息」
current_version string 当前客户端版本号(如 1.2.3),传入后响应会包含 force_update 字段

请求示例

{
    "app_id": "YOUR_APP_ID",
    "encrypted_payload": "Base64(RSA加密({
        \"license_key\": \"XXXX-XXXX-XXXX-XXXX\",
        \"verify_type\": \"domain\",
        \"verify_value\": \"example.com\",
        \"info\": \"Base64(RSA加密(app/.info内容字符串))\"
    }))"
}

自动绑定功能

当授权码有效但尚未绑定验证值时,系统会自动将验证值(域名/IP/设备码/文件哈希)绑定到授权码,实现「首次验证即激活」的流程。

SDK 调用示例

使用 SDK 可以简化请求流程,SDK 会自动处理加密签名等细节:

// 发送验证请求
$result = $validator->verify('XXXX-XXXX-XXXX-XXXX', [
    'info' => 'Base64(RSA加密(app/.info内容字符串))'
]);

// $result 包含服务器返回的验证结果
// 详见「接受回调」部分了解返回数据结构
# 发送验证请求
result = validator.verify('XXXX-XXXX-XXXX-XXXX', {
    'info': 'Base64(RSA加密(app/.info内容字符串))'
})

# result 包含服务器返回的验证结果
# 详见「接受回调」部分了解返回数据结构
// 发送验证请求
VerifyResult result = validator.verify("XXXX-XXXX-XXXX-XXXX", Map.of(
    "info", "Base64(RSA加密(app/.info内容字符串))"
));

// result 包含服务器返回的验证结果
// 详见「接受回调」部分了解返回数据结构
// 发送验证请求
const result = await validator.verify('XXXX-XXXX-XXXX-XXXX', {
    info: 'Base64(RSA加密(app/.info内容字符串))'
});

// result 包含服务器返回的验证结果
// 详见「接受回调」部分了解返回数据结构
// 发送验证请求
result, err := validator.Verify("XXXX-XXXX-XXXX-XXXX", map[string]interface{}{
    "info": "Base64(RSA加密(app/.info内容字符串))",
})
if err != nil {
    log.Fatal(err)
}

// result 包含服务器返回的验证结果
// 详见「接受回调」部分了解返回数据结构
// 发送验证请求
VerifyResult* result = license_validator_verify(validator, "XXXX-XXXX-XXXX-XXXX");

// result 包含服务器返回的验证结果
// 详见「接受回调」部分了解返回数据结构

// 使用完毕后释放内存
verify_result_free(result);

处理验证结果

验证请求完成后,根据返回的 result 对象处理验证结果:

if ($result['valid']) {
    echo "授权验证成功!";
    
    // 获取授权信息
    $data = $result['data'];
    echo "到期时间: " . $data['expires_at'];
    echo "剩余天数: " . $data['remaining_days'];
    echo "最新版本: " . $data['latest_version'];
    echo "绑定渠道: " . $data['channel']; // green=新绑定, veteran=已有绑定
    
    // 获取功能限制
    $features = $result['features'];
    echo "剩余可绑定域名: " . ($features['remain_domain'] ?? 0);
    
    // 获取自定义回调参数
    if (isset($result['callback_params'])) {
        $params = $result['callback_params'];
        $maxUsers = $params['max_users'] ?? 10;
    }
} else {
    echo "授权验证失败: " . $result['message'];
    $code = $result['code'] ?? 0;
}
if result['valid']:
    print("授权验证成功!")
    
    # 获取授权信息
    data = result['data']
    print(f"到期时间: {data['expires_at']}")
    print(f"剩余天数: {data['remaining_days']}")
    print(f"最新版本: {data['latest_version']}")
    print(f"绑定渠道: {data['channel']}")  # green=新绑定, veteran=已有绑定
    
    # 获取功能限制
    features = result['features']
    print(f"剩余可绑定域名: {features.get('remain_domain', 0)}")
    
    # 获取自定义回调参数
    if 'callback_params' in result:
        params = result['callback_params']
        max_users = params.get('max_users', 10)
else:
    print(f"授权验证失败: {result['message']}")
    code = result.get('code', 0)
if (result.isValid()) {
    System.out.println("授权验证成功!");
    
    // 获取授权信息
    VerifyData data = result.getData();
    System.out.println("到期时间: " + data.getExpiresAt());
    System.out.println("剩余天数: " + data.getRemainingDays());
    System.out.println("最新版本: " + data.getLatestVersion());
    System.out.println("绑定渠道: " + data.getChannel()); // green=新绑定, veteran=已有绑定
    
    // 获取功能限制
    Map<String, Object> features = result.getFeatures();
    System.out.println("剩余可绑定域名: " + features.getOrDefault("remain_domain", 0));
    
    // 获取自定义回调参数
    Map<String, Object> params = result.getCallbackParams();
    if (params != null) {
        int maxUsers = (int) params.getOrDefault("max_users", 10);
    }
} else {
    System.out.println("授权验证失败: " + result.getMessage());
    int code = result.getCode();
}
if (result.valid) {
    console.log('授权验证成功!');
    
    // 获取授权信息
    const { data } = result;
    console.log('到期时间:', data.expires_at);
    console.log('剩余天数:', data.remaining_days);
    console.log('最新版本:', data.latest_version);
    console.log('绑定渠道:', data.channel); // green=新绑定, veteran=已有绑定
    
    // 获取功能限制
    const { features } = result;
    console.log('剩余可绑定域名:', features.remain_domain || 0);
    
    // 获取自定义回调参数
    if (result.callback_params) {
        const params = result.callback_params;
        const maxUsers = params.max_users || 10;
    }
} else {
    console.log('授权验证失败:', result.message);
    const code = result.code || 0;
}
if result.Valid {
    fmt.Println("授权验证成功!")
    
    // 获取授权信息
    data := result.Data
    fmt.Println("到期时间:", data.ExpiresAt)
    fmt.Println("剩余天数:", data.RemainingDays)
    fmt.Println("最新版本:", data.LatestVersion)
    fmt.Println("绑定渠道:", data.Channel) // green=新绑定, veteran=已有绑定
    
    // 获取功能限制
    features := result.Features
    fmt.Println("剩余可绑定域名:", features["remain_domain"])
    
    // 获取自定义回调参数
    if result.CallbackParams != nil {
        params := result.CallbackParams
        maxUsers := params["max_users"].(int)
    }
} else {
    fmt.Println("授权验证失败:", result.Message)
    code := result.Code
}
if (result->valid) {
    printf("授权验证成功!\n");
    
    // 获取授权信息
    printf("到期时间: %s\n", result->data->expires_at);
    printf("剩余天数: %d\n", result->data->remaining_days);
    printf("最新版本: %s\n", result->data->latest_version);
    printf("绑定渠道: %s\n", result->data->channel); // green=新绑定, veteran=已有绑定
    
    // 获取功能限制
    printf("剩余可绑定域名: %d\n", get_feature_int(result->features, "remain_domain", 0));
    
    // 获取自定义回调参数
    if (result->callback_params) {
        int max_users = get_param_int(result->callback_params, "max_users", 10);
    }
} else {
    printf("授权验证失败: %s\n", result->message);
    int code = result->code;
}

返回数据结构

验证请求完成后,服务器会返回以下数据结构。

验证成功时返回

{
    "code": 0,                        // 状态码 (0=成功)
    "valid": true,                    // 是否有效
    "message": "验证成功",             // 消息
    "features": {                     // 功能限制参数
        "remain_domain": 3,           // 剩余可绑定域名数
        "remain_ip": 5,               // 剩余可绑定IP数
        "remain_device": 2            // 剩余可绑定设备数
    },
    "data": {
        "channel": "green",           // 绑定渠道 (green=新绑定, veteran=已有绑定)
        "activated_at": "2024-01-15 10:30:00", // 激活时间
        "remaining_days": 365,        // 剩余天数
        "expires_at": "2025-12-31 23:59:59", // 到期时间
        "latest_version": "1.2.0",      // 应用最新版本号
        "force_update": false         // 是否需要强制更新(仅当请求中包含 current_version 时返回)
    },
    "callback_params": {              // 回调参数(服务端配置)
        "api_endpoint": "https://...",
        "max_users": 100
    }
}

需要强制更新时返回

force_updatetrue 时,会额外返回 titlelogdownload_code 字段:

{
    "code": 0,
    "valid": true,
    "message": "验证成功",
    "features": { ... },
    "data": {
        "channel": "veteran",
        "activated_at": "2024-01-15 10:30:00",
        "remaining_days": 365,
        "expires_at": "2025-12-31 23:59:59",
        "latest_version": "2.0.0",    // 最新版本号
        "force_update": true,         // 需要强制更新
        "title": "重大更新",           // 最新版本标题(仅当 force_update=true 时返回)
        "log": "1. 新增功能...",       // 更新日志(仅当 force_update=true 时返回)
        "download_code": "ABC123XYZ"  // 一次性下载码(仅当 force_update=true 时返回)
    },
    "callback_params": { ... }
}

参数说明

参数名 类型 说明
code integer 状态码,0 表示成功
valid boolean 验证是否通过
message string 状态消息
features object 功能限制参数,包含剩余可绑定数量及套餐自定义功能
data.channel string 绑定渠道:green(新绑定)、veteran(已有绑定)
data.activated_at string 授权激活时间
data.remaining_days integer 剩余有效天数
data.expires_at string 授权到期时间
data.latest_version string 应用最新版本号(如 1.0.0、1.2.0)
data.force_update boolean 是否需要强制更新(仅当请求载荷中包含 current_version 时返回)
data.title string 最新版本标题(仅当 force_updatetrue 时返回)
data.log string 最新版本更新日志(仅当 force_updatetrue 时返回)
data.download_code string 一次性下载码(9位大写字母和数字),30分钟有效(仅当 force_updatetrue 时返回)
callback_params object 服务端配置的回调参数,验证成功后返回给客户端

验证失败时返回

{
    "code": 1001,
    "valid": false,
    "message": "授权码不存在",
    "warning_url": "https://example.com/warning/display?token=xxx"
}

验证失败参数说明

参数名 类型 说明
code integer 错误码,参见下方错误码说明
valid boolean 验证是否通过,失败时为 false
message string 错误消息
warning_url string 警告页面URL(可选,仅在开启盗版警告时返回)

关于盗版警告参数

  • warning_url:验证失败时返回的警告页面URL,包含一次性 token
  • 仅在应用开启了盗版警告功能时返回该字段
  • 客户端可将用户引导至该 URL 查看警告信息

错误码说明

在线验证错误码 (1001-1005)

错误码 错误消息 处理建议
0 success 验证成功
1001 授权码不存在 检查授权码是否正确输入
1002 授权已被撤销 联系开发者了解撤销原因
1003 授权已过期 续费或购买新授权
1004 域名不匹配 检查域名绑定是否正确
1005 IP不匹配 检查IP绑定是否正确

离线验证错误码 (3000-3010)

错误码 错误消息 处理建议
3000 授权文件不存在 检查 license.json 文件是否存在
3001 授权文件已被篡改 重新下载授权文件
3002 验证出错 检查公钥文件是否正确
3003 授权已过期(离线) 续费或购买新授权
3004 应用不匹配 检查授权文件是否属于当前应用
3005 域名不匹配(离线) 检查域名绑定是否正确
3006 设备不匹配 检查设备绑定是否正确
3007 小程序AppID不匹配 检查小程序配置是否正确
3008 授权文件格式错误 重新下载授权文件
3009 网络请求失败 检查网络连接
3010 公钥文件不存在 将 publickey.pem 放置到正确位置

系统支持两种自定义参数机制:上传参数(客户端发起)和回调参数(服务端返回),实现客户端与服务器之间的双向数据传递。

特性 上传参数(发起模块) 回调参数(回调模块)
数据流向 客户端 → 服务器 服务器 → 客户端
配置方式 需要预先备案参数名 在应用设置中配置
使用场景 传递客户端环境信息 返回服务器配置数据
返回字段 upload_params callback_params

1 发起模块(上传参数)

客户端在验证请求中传递自定义参数,用于向服务器报告客户端环境信息。上传参数需要预先在后台备案才能使用。

如何传递上传参数

在加密验证载荷中,将上传参数放入 upload_params 字段:

// 加密验证载荷结构
{
    "license_key": "XXXX-XXXX-XXXX-XXXX",
    "verify_type": "domain",
    "verify_value": "example.com",
    "upload_params": {
        "client_version": "1.2.0",    // 客户端版本
        "os_type": "windows",          // 操作系统类型
        "user_tag": "premium"          // 用户标签
    },
    "info": "Base64(RSA加密(app/.info内容字符串))"  // 可选:被授权用户信息
}

命名规则

  • 最大长度:18 个字符
  • 必须以字母或下划线开头
  • 只能包含字母、数字、下划线
  • 不能使用系统保留关键字

类型限制

类型 说明 示例
string 字符串类型,最大 255 字符 "windows"
number 数值类型,整数或浮点数 1024
boolean 布尔类型 true

备案要求

上传参数必须预先备案

  1. 进入「软件管理」→ 选择应用 → 「应用信息」标签
  2. 在「上传参数备案」区域添加参数名称
  3. 保存后,客户端即可使用该参数名传递数据

未备案的参数将被服务器拒绝,返回错误码 1010

保留关键字

以下关键字为系统保留,不能作为上传参数名:

license_key, app_id, app_secret, timestamp, sign, signature, verify_type, verify_value, encrypted_payload, domain, ip, device_id, file_hash, version, callback, token, key, secret, password, auth, session, cookie, header, request, response, error, code, message, data, status, id, user_id, created_at, updated_at, deleted_at

2 回调模块(回调参数)

服务器在验证成功后返回给客户端的自定义数据。回调参数在应用设置中配置,无需客户端传递。

配置方式

在应用设置中配置回调参数

  1. 进入「软件管理」→ 选择应用 → 「应用信息」标签
  2. 在「自定义回调参数」区域添加键值对
  3. 保存后,验证成功时会自动返回这些参数

使用场景

  • 返回 API 端点地址,实现动态配置
  • 返回功能开关状态,控制客户端功能
  • 返回系统公告信息,推送通知给用户
  • 返回资源下载地址,指引客户端获取资源

返回格式

验证成功时,回调参数会包含在响应的 callback_params 字段中:

{
    "code": 0,
    "valid": true,
    "message": "验证成功",
    "features": {
        "remain_domain": 3
    },
    "data": {
        "channel": "green",
        "activated_at": "2024-01-15 10:30:00",
        "remaining_days": 365,
        "expires_at": "2025-12-31 23:59:59",
        "latest_version": "1.2.0"
    },
    "callback_params": {
        "api_endpoint": "https://api.example.com",
        "feature_flags": {
            "new_ui": true,
            "beta_features": false
        },
        "announcement": "系统将于本周六凌晨维护",
        "max_users": 100
    }
}

回调参数支持嵌套对象结构,可以组织复杂的配置数据。客户端收到后可直接解析使用。

3 错误码

上传参数相关的错误码及处理建议:

错误码 错误消息 处理建议
1010 上传参数未备案 在应用设置中备案该参数名称后重试
1011 缺少必传参数 检查是否传递了所有标记为必传的备案参数
1000 参数格式错误 检查参数名是否符合命名规则(字母/下划线开头,仅含字母数字下划线,不超过18字符)

当遇到上传参数错误时,验证请求会被拒绝。请确保所有上传参数都已正确备案,且参数值符合类型要求。

增值品项

增值品API允许第三方应用查询用户可用的增值品列表、生成支付二维码,并在支付完成后自动为用户授权增值品。

功能概述

查询增值品

查询用户可用的增值品列表,包含购买状态和过期时间

生成支付码

为增值品购买生成支付二维码,支持支付宝、微信、QQ支付

自动授权

支付成功后系统自动处理回调并授权增值品

使用流程

1

创建增值品

在「增值品管理」中创建增值品,设置名称、描述、价格和图标

2

绑定到应用

将增值品绑定到您的应用,只有绑定的增值品才能被查询和购买

3

集成API

在您的应用中集成增值品API,调用查询和支付接口

4

用户购买

用户扫码支付后,系统自动授权,应用可通过查询接口检查购买状态

API接口

POST /api/vap/query 查询增值品列表

查询用户可用的增值品列表,包含购买状态和过期时间。使用RSA加密确保请求安全性。

请求参数

app_idstring应用 ID(必填)
encrypted_payloadstringRSA加密的载荷(必填,Base64编码)

加密说明:载荷包含 license_key、verify_type、verify_value、timestamp,使用应用公钥进行RSA PKCS#1 v1.5加密后Base64编码。

POST /api/vap/payment 生成支付二维码

为用户生成增值品购买的支付二维码。使用RSA加密确保请求安全性。

请求参数

app_idstring应用 ID(必填)
encrypted_payloadstringRSA加密的载荷(必填,Base64编码)

注意:载荷包含 license_key、verify_type、verify_value、vap_id、payment_type、timestamp。二维码有效期15-30分钟,支付成功后系统自动授权。

安全机制

RSA加密

所有API请求使用RSA PKCS#1 v1.5加密,确保数据传输安全

时间戳验证

时间戳与服务器时间差不能超过5分钟,防止重放攻击

频率限制

每个应用限制60次/分钟,防止恶意刷接口

缓存优化

查询结果缓存5-10分钟,减少数据库负载

最佳实践

  • 使用HTTPS协议调用API,确保数据传输安全
  • 妥善保管应用私钥,不要在客户端代码中暴露
  • 支付成功后等待1-2秒再查询,给系统处理回调的时间
  • 实现指数退避重试策略,避免频繁请求导致限流
  • 本地缓存增值品列表,减少不必要的API调用

版本管理

通过版本管理功能,您可以发布软件更新、管理代码文件,让用户获取最新版本。

1

创建新版本

在「软件管理」→ 应用详情 → 「版本管理」标签页中,点击「新建版本」按钮创建版本。

字段 说明 示例
version 版本号 1.2.0
log 更新日志 修复已知问题,新增功能...
force_update 是否强制更新 true / false
2

管理代码文件

在「代码迭代」标签页中,为每个版本管理代码文件。支持三种操作类型:

新增 (add)

添加新文件到版本

更新 (update)

更新已有文件内容

删除 (delete)

标记文件为删除

代码文件支持增量更新。客户端检测更新时,系统会自动合并从当前版本到最新版本的所有变更。

3

发布版本

完成代码文件配置后,点击「发布」按钮将版本设为当前版本。发布后:

  • 该版本成为应用的最新版本
  • 客户端验证时会收到新版本号
  • 客户端可通过更新检测 API 获取更新信息
4

客户端检测更新

客户端可通过以下 API 检测并获取更新:

检测更新 API
POST /api/v1/license/check-update

请求参数:
{
    "app_id": "YOUR_APP_ID",
    "encrypted_payload": "Base64编码的RSA加密数据..."
}

载荷结构(加密前):
{
    "license_key": "XXXX-XXXX-XXXX-XXXX",
    "verify_type": "domain",
    "verify_value": "example.com",
    "current_version": "1.0.0"           // 必填:当前客户端版本号
}

响应示例(有更新):
{
    "valid": true,
    "data": {
        "updated": true,
        "latest_version": "1.2.0",
        "force_update": false,
        "log": "1. 修复已知问题\n2. 新增功能...",
        "download_code": "ABC123XYZ"
    }
}

响应示例(无更新):
{
    "valid": true,
    "data": {
        "updated": false
    }
}

说明:download_code 仅在有更新时返回,用于获取代码更新 API。
获取代码更新 API
POST /api/v1/app/code-updates

请求参数:
{
    "app_id": "YOUR_APP_ID",
    "encrypted_payload": "Base64编码的RSA加密数据..."
}

载荷结构(加密前):
{
    "current_version": "1.0.0",          // 可选:当前版本号(用于计算增量更新)
    "license_key": "XXXX-XXXX-XXXX-XXXX",
    "verify_type": "domain",
    "verify_value": "example.com",
    "download_code": "ABC123XYZ"
}

字段说明:
- current_version: 当前客户端版本号,用于计算增量更新文件列表
                   如果不传,系统会使用检查更新时传入的版本号
- download_code: 检测更新返回的一次性下载码,必填,使用后失效

响应示例:
{
    "valid": true,
    "code": 0,
    "data": {
        "latest_version": "1.2.0",
        "files": [
            {
                "path": "src/main.js",
                "action": "update",
                "url": "https://.../api/v1/license/download-file?token=XYZ789ABC",
                "hash": "abc123..."
            },
            {
                "path": "src/old-file.js",
                "action": "delete"
            },
            {
                "path": "src/new-feature.js",
                "action": "add",
                "url": "https://.../api/v1/license/download-file?token=DEF456GHI",
                "hash": "def456..."
            }
        ],
        "codes": {                       // 可选:版本指令(仅当配置时返回)
            "type": "sql",
            "code": "ALTER TABLE users ADD COLUMN avatar VARCHAR(255);"
        }
    }
}

说明:
- files 中的 url 为一次性临时下载链接,30分钟内有效
- delete 操作不返回 url 和 hash
- action 类型:add(新增)、update(更新)、delete(删除)
- codes 字段仅当版本配置了指令时返回,客户端应在更新文件后执行
下载代码文件 API
GET /api/v1/license/download-file?token=XYZ789ABC

参数说明:
- token: 文件下载令牌(9位大写字母数字),从获取代码更新 API 的 url 中获取

响应:
- 成功:直接返回文件内容(二进制流)
- Content-Type 根据文件类型自动设置

使用建议:
1. 下载文件后,使用 SHA256 计算文件哈希
2. 与 API 返回的 hash 值对比,验证文件完整性
3. 验证通过后再写入本地文件

注意事项:
- token 只能使用一次,使用后立即失效
- token 有效期为 30 分钟
- 建议按顺序下载文件,避免并发请求过多

版本状态

状态 说明
草稿 版本创建后的初始状态,可编辑代码文件
已发布 当前生效的版本,客户端可获取此版本
历史版本 曾经发布过但已被新版本替代

增量更新机制

智能合并更新

当客户端从 v1.0 更新到 v1.2 时,系统会自动合并 v1.0 → v1.1 → v1.2 的所有变更,生成最终的文件列表。合并规则:

  • 同一文件的多次更新,只保留最新版本的内容
  • 先添加后删除的文件,最终结果为移除(不返回)
  • 先添加后更新的文件,最终结果为添加(对用户来说是新文件)
  • 删除后又添加的文件,最终结果为更新(文件重新出现)

合并规则详解

前一操作 当前操作 最终结果 说明
add update add 对用户来说是新文件
add delete 移除 文件不存在于用户端
update update update 保留最新内容
update delete delete 文件需要删除
delete add update 文件重新出现

版本指令

版本指令用于在更新时执行额外操作,如数据库迁移或远程回调。在「代码迭代」标签页中,选择一个版本后可以添加或编辑该版本的指令。

指令类型 说明 示例
SQL 数据库迁移语句 ALTER TABLE users ADD COLUMN age INT;
URL 远程回调地址 https://example.com/callback
混合 同时包含 SQL 和 URL 系统自动检测

安全提示

  • SQL 指令禁止使用 DROP、TRUNCATE、DELETE(无 WHERE)等危险操作
  • URL 回调必须使用 HTTPS 协议
  • 指令在客户端更新时执行一次,请确保幂等性
版本指令 API
// 获取版本指令
GET /api/vendor/versions/{id}/command

响应示例:
{
    "success": true,
    "data": {
        "version_id": 2,
        "version": "1.1.0",
        "has_command": true,
        "command": "ALTER TABLE users ADD COLUMN avatar VARCHAR(255);"
    }
}

// 保存版本指令(仅支持 SQL)
POST /api/vendor/versions/{id}/command
Content-Type: application/json

{
    "command_code": "ALTER TABLE users ADD COLUMN avatar VARCHAR(255);"
}

// 删除版本指令
DELETE /api/vendor/versions/{id}/command

最佳实践

使用递增版本号

遵循 major.minor.patch 格式,如 1.0.0、1.1.0、2.0.0

编写清晰的更新日志

让用户了解每个版本的变更内容

谨慎使用强制更新

仅在修复严重安全漏洞或重大兼容性问题时启用

发布前充分测试

版本发布后无法撤回,请确保代码文件正确无误

版本指令保持幂等

确保指令可以安全地重复执行,避免数据不一致

全球转发

全球转发功能允许开发者上传静态资源文件,并通过 API 获取下载地址供授权用户下载。

一、功能说明

资源托管

上传配置文件、数据文件等静态资源,系统会生成唯一的资源ID用于API调用。

动态下载地址

通过 API 获取临时下载地址(非直链),有效期内可下载资源文件。

全球加速

资源文件通过 CDN 分发,用户可从最近的节点下载,提升下载速度。

二、文件限制

支持类型: zip, tar.gz, json, xml, txt, dat, db, xdb
单文件限制: 50MB
应用总存储: 500MB

三、API 接口

下载资源

GET /api/v1/resources/{resource_id}?app_id={app_id}&verify_type={type}&verify_value={value}&license_key={key}

请求参数

参数名 类型 必填 说明
resource_id string 资源ID(路径参数)
app_id string 应用 ID
verify_type string 验证类型: domain/ip/device/file
verify_value string 验证值(与 verify_type 对应)
license_key string 授权码

成功响应

直接返回文件内容(二进制流),响应头包含文件信息。

PHP 示例

// 构建下载URL
$apiUrl = "https://auth.example.com/api/v1/resources/{$resourceId}";
$apiUrl .= "?app_secret=" . urlencode($appSecret);
$apiUrl .= "&verify_type=" . urlencode($verifyType);
$apiUrl .= "&verify_value=" . urlencode($verifyValue);
$apiUrl .= "&license_key=" . urlencode($licenseKey);

// 下载文件
$content = file_get_contents($apiUrl);
file_put_contents('config.json', $content);

四、错误码

错误码 说明
5001 文件大小超过限制
5002 不支持的文件类型
5003 存储空间不足
5004 资源不存在

五、使用步骤

1

上传资源

在应用详情的「全球转发」标签页上传资源文件。

2

获取资源ID

上传成功后,复制资源ID用于API调用。

3

调用API获取下载地址

在软件中调用API获取临时下载地址。

4

下载资源

使用返回的下载地址下载资源文件。

注意事项

  • 下载地址有时效性,过期后需重新获取
  • 资源文件更新后,资源ID保持不变
  • 建议在软件中缓存下载的资源文件

错误处理

通用错误码 (1000-1999)

错误码 说明 处理建议
1000 参数错误 检查请求参数
1001 授权码不存在 检查授权码
1002 授权已被撤销 联系客服
1003 授权已过期 提示续费
1009 验证类型不匹配 检查验证类型
1010 未备案的上传参数 先备案参数

绑定验证错误码 (3000-3999)

3005 域名不匹配
3006 设备不匹配
3007 IP/小程序AppID不匹配

全球转发错误码 (5000-5999)

5001 文件大小超过限制(50MB)
5002 不支持的文件类型
5003 存储空间不足(500MB)
5004 资源不存在

API 参考

完整的 API 接口文档,包含所有授权验证相关的接口信息。

POST /api/v1/license/verify-encrypted 验证授权码

使用 RSA 加密的载荷验证授权码,返回授权状态和详细信息。

请求参数

app_idstring应用 ID(必填)
encrypted_payloadstringRSA 加密载荷(必填)

载荷结构(加密前)

{
  "license_key": "XXXX-XXXX-XXXX-XXXX",  // 必填
  "verify_type": "domain",                // 必填: domain/ip/device/file
  "verify_value": "example.com",          // 必填: 与 verify_type 对应
  "current_version": "1.0.0",             // 可选: 传入后返回 force_update 字段
  "upload_params": {}                     // 可选: 自定义上传参数
}

成功响应

{
  "valid": true,
  "code": 0,
  "message": "验证成功",
  "data": {
    "channel": "veteran",              // green=新绑定, veteran=已有绑定
    "activated_at": "2024-01-01 00:00:00",
    "remaining_days": 365,             // 永久授权返回 999999
    "expires_at": "2025-01-01 00:00:00",
    "latest_version": "1.2.0",
    "force_update": false              // 仅当传入 current_version 时返回
  },
  "callback_params": {}                // 开发者配置的回调参数
}

说明:当 force_update 为 true 时,会额外返回 title、log 和 download_code 字段。

GET /api/v1/app/public-key 获取应用公钥

获取应用的 RSA 公钥,用于加密验证载荷和离线验证授权签名。

请求参数

app_idstring应用 ID(必填,18位字母数字)

成功响应

{
  "valid": true,
  "code": 0,
  "data": {
    "public_key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...\n-----END PUBLIC KEY-----"
  }
}

公钥用途:加密验证载荷(RSA PKCS#1 v1.5)、验证授权文件签名(RSA-SHA256)

POST /api/vap/query 查询增值品列表

查询用户可用的增值品列表,包含购买状态和过期时间。使用RSA加密确保数据安全。

请求参数

app_idstring应用 ID(必填)
encrypted_payloadstringRSA 加密载荷(必填)

载荷结构(加密前)

{
  "license_key": "XXXX-XXXX-XXXX-XXXX",  // 必填
  "verify_type": "domain",                // 必填: domain/ip
  "verify_value": "example.com",          // 必填
  "timestamp": 1234567890                 // 必填: Unix时间戳
}

成功响应

{
  "success": true,
  "data": {
    "products": [
      {
        "id": 1,
        "product": "BAtUXHuHCx",
        "name": "高级功能包",
        "description": "包含所有高级功能",
        "category": "功能扩展",
        "version": "1.0.0",
        "price": 99.00,
        "icon": "https://example.com/icon.png",
        "owned": true,                    // 是否已购买
        "expire_time": "2024-12-31 23:59:59"  // NULL表示永久
      }
    ]
  }
}

说明:时间戳与服务器时间差不能超过5分钟。查询结果会缓存5-10分钟。

POST /api/vap/payment 生成支付二维码

为用户生成增值品购买的支付二维码。使用RSA加密确保请求安全性。

请求参数

app_idstring应用 ID(必填)
encrypted_payloadstringRSA加密的载荷(必填,Base64编码)

载荷结构(加密前)

{
  "license_key": "XXXX-XXXX-XXXX-XXXX",
  "verify_type": "domain",
  "verify_value": "example.com",
  "vap_id": 1,
  "payment_type": "alipay",
  "timestamp": 1704067200
}

成功响应

{
  "success": true,
  "data": {
    "order_id": "ORD202401010001",
    "qrcode": "data:image/png;base64,iVBORw0KGgo...",
    "qrcode_url": "https://qr.alipay.com/xxx",
    "amount": 99.00,
    "expire_time": "2024-01-01 12:30:00"
  }
}

注意:二维码有效期15-30分钟。支付成功后系统自动授权,可通过轮询查询接口检查购买状态。

增值品API错误码

错误码 HTTP 说明
MISSING_PARAMETER 400 缺少必需参数
INVALID_SIGNATURE 401 签名验证失败
INVALID_TIMESTAMP 403 时间戳无效(超过5分钟)
VAP_NOT_FOUND 404 增值品不存在或未上架
VAP_NOT_BOUND 403 增值品未绑定到应用
RATE_LIMIT_EXCEEDED 429 请求过于频繁(60次/分钟)
PAYMENT_GATEWAY_ERROR 502 支付网关调用失败

频率限制:每个应用限制60次/分钟。建议实现指数退避重试策略。

GET /api/v1/resources/{resource_id} 下载资源

下载全球转发资源文件,需要授权验证。

路径参数

resource_idstring资源 ID(必填)

查询参数

app_idstring应用 ID(必填)
verify_typestring验证类型(必填): domain/ip/device/file
verify_valuestring验证值(必填): 与 verify_type 对应
license_keystring授权码(必填)

响应

成功时直接返回文件内容(二进制流),Content-Type 根据文件类型自动设置。

POST /api/v1/license/check-update 检查更新

检查应用是否有新版本可用,返回更新信息和一次性下载码。

请求参数

app_idstring应用 ID(必填)
encrypted_payloadstringRSA 加密载荷(必填)

载荷结构(加密前)

{
  "license_key": "XXXX-XXXX-XXXX-XXXX",  // 必填
  "verify_type": "domain",                // 必填
  "verify_value": "example.com",          // 必填
  "current_version": "1.0.0"              // 必填: 当前客户端版本号
}

成功响应(有更新)

{
  "valid": true,
  "code": 0,
  "data": {
    "updated": true,
    "latest_version": "1.2.0",
    "title": "版本标题",
    "log": "更新日志...",
    "force_update": false,
    "download_code": "ABC123XYZ"   // 一次性下载码,30分钟有效
  }
}

注意:download_code 用于调用获取代码更新 API,使用后失效。current_version 会被缓存,用于计算增量更新。

POST /api/v1/app/code-updates 获取代码更新

使用下载码获取增量更新的文件列表,包含文件路径、操作类型和下载链接。

请求参数

app_idstring应用 ID(必填)
encrypted_payloadstringRSA 加密载荷(必填)

载荷结构(加密前)

{
  "current_version": "1.0.0",             // 可选: 当前版本号(用于计算增量)
  "license_key": "XXXX-XXXX-XXXX-XXXX",   // 必填
  "verify_type": "domain",                 // 必填
  "verify_value": "example.com",           // 必填
  "download_code": "ABC123XYZ"             // 必填: 检查更新返回的下载码
}

成功响应

{
  "valid": true,
  "code": 0,
  "data": {
    "latest_version": "1.2.0",
    "files": [
      {
        "path": "src/main.js",
        "action": "update",
        "url": "https://.../download-file?token=...",
        "hash": "sha256..."
      },
      {
        "path": "src/old.js",
        "action": "delete"
      },
      {
        "path": "src/new.js",
        "action": "add",
        "url": "https://.../download-file?token=...",
        "hash": "sha256..."
      }
    ]
  }
}

文件操作类型

add新增需要下载并创建的新文件
update更新需要下载并覆盖的文件
delete删除需要删除的文件(无 url 和 hash)

说明:current_version 可选,如果不传则使用检查更新时缓存的版本号。url 为一次性临时链接,30分钟内有效。

GET /api/v1/license/download-file 下载代码文件

使用一次性 token 下载单个代码文件。

查询参数

tokenstring文件下载 token(必填,9位大写字母数字)

响应

直接返回文件内容(二进制流),Content-Type 根据文件类型自动设置。

注意:token 只能使用一次,30分钟内有效。下载后请使用 hash 值验证文件完整性。

RSA 加密技术规范

系统使用 PKCS#1 v1.5 填充方式进行加密验证,这是 PHP 原生 OpenSSL 支持的标准加密方式,无需安装额外依赖。

加密流程概述

  1. 获取公钥:从 /api/v1/app/public-key 接口获取应用公钥,或使用本地 publickey.pem 文件
  2. 构建载荷:创建包含 license_key、verify_type、verify_value、timestamp 的 JSON 对象
  3. 序列化:将载荷对象序列化为 UTF-8 编码的 JSON 字符串
  4. 检查长度:判断 JSON 字符串字节长度是否超过 245 字节
  5. 加密处理:≤245 字节直接加密;>245 字节需分段加密
  6. Base64 编码:将加密后的二进制数据转为 Base64 字符串
  7. 拼接结果:分段加密时用 | 分隔符连接各段

加密参数规范

参数 说明
填充方式 PKCS#1 v1.5 PHP 原生 OpenSSL 支持
密钥长度 2048 位 RSA 密钥长度
密钥字节数 256 字节 2048 / 8 = 256
PKCS#1 v1.5 开销 11 字节 固定填充开销
最大明文长度 245 字节 256 - 11 = 245

分段加密详细说明

当验证载荷 JSON 字符串的 UTF-8 字节长度 超过 245 字节时,必须进行分段加密:

分段加密步骤:
  1. 将 JSON 字符串转换为 UTF-8 字节数组
  2. 按 245 字节为单位切分字节数组(最后一段可能不足 245 字节)
  3. 对每一段分别使用 RSA PKCS#1 v1.5 加密
  4. 将每段加密结果分别进行 Base64 编码
  5. 用管道符 | 连接所有 Base64 字符串
示例:

假设 JSON 字符串为 600 字节:

  • 第 1 段:字节 0-244(245 字节)→ 加密 → Base64 → chunk1_base64
  • 第 2 段:字节 245-489(245 字节)→ 加密 → Base64 → chunk2_base64
  • 第 3 段:字节 490-599(110 字节)→ 加密 → Base64 → chunk3_base64
  • 最终结果:chunk1_base64|chunk2_base64|chunk3_base64

各语言完整加密示例

使用 PKCS#1 v1.5 填充,无需额外依赖

✅ 无需额外依赖

使用 PHP 原生 OpenSSL 扩展,无需安装任何第三方库。

📋 实现步骤
  1. 使用 file_get_contents() 读取 PEM 格式公钥
  2. 使用 json_encode() 将载荷数组序列化为 JSON 字符串
  3. 使用 strlen() 检查字节长度是否超过 245
  4. 使用 openssl_public_encrypt() 配合 OPENSSL_PKCS1_PADDING 加密
  5. 使用 base64_encode() 编码加密结果
  6. 分段时使用 substr() 切分,implode('|', ...) 拼接
<?php
/**
 * RSA PKCS#1 v1.5 加密验证载荷
 * 
 * 使用 PHP 原生 OpenSSL 扩展,无需额外依赖
 */

/**
 * RSA 加密验证载荷(支持分段加密)
 * 
 * @param array $payload 验证载荷数组
 * @param string $publicKeyPem PEM 格式公钥字符串
 * @return string Base64 编码的加密数据(分段时用 | 分隔)
 * @throws RuntimeException 加密失败时抛出异常
 */
function encryptPayload(array $payload, string $publicKeyPem): string
{
    // 步骤 1: 将载荷序列化为 JSON 字符串(保留 Unicode 字符)
    $jsonData = json_encode($payload, JSON_UNESCAPED_UNICODE);
    if ($jsonData === false) {
        throw new RuntimeException('JSON 序列化失败: ' . json_last_error_msg());
    }
    
    // 步骤 2: 定义单次加密最大明文长度
    // 2048位密钥 = 256字节,PKCS#1 v1.5 开销 = 11字节,最大明文 = 245字节
    $maxLength = 245;
    $dataLength = strlen($jsonData);
    
    // 步骤 3: 检查是否需要分段加密
    if ($dataLength <= $maxLength) {
        // 情况 A: 数据长度 ≤ 245 字节,单次加密
        $encrypted = '';
        $result = openssl_public_encrypt($jsonData, $encrypted, $publicKeyPem, OPENSSL_PKCS1_PADDING);
        if ($result === false) {
            throw new RuntimeException('加密失败: ' . openssl_error_string());
        }
        return base64_encode($encrypted);
    }
    
    // 情况 B: 数据长度 > 245 字节,需要分段加密
    $encryptedChunks = [];
    $offset = 0;
    
    // 步骤 4: 按 245 字节切分并逐段加密
    while ($offset < $dataLength) {
        // 获取当前段(最后一段可能不足 245 字节)
        $chunk = substr($jsonData, $offset, $maxLength);
        
        // 加密当前段
        $encrypted = '';
        $result = openssl_public_encrypt($chunk, $encrypted, $publicKeyPem, OPENSSL_PKCS1_PADDING);
        if ($result === false) {
            throw new RuntimeException('分段加密失败: ' . openssl_error_string());
        }
        
        // Base64 编码并添加到数组
        $encryptedChunks[] = base64_encode($encrypted);
        
        $offset += $maxLength;
    }
    
    // 步骤 5: 用管道符连接所有加密段
    return implode('|', $encryptedChunks);
}

// ==================== 完整使用示例 ====================

// 1. 读取公钥文件
$publicKeyPath = __DIR__ . '/publickey.pem';
if (!file_exists($publicKeyPath)) {
    die("错误:公钥文件不存在,请从开发者中心下载 publickey.pem");
}
$publicKey = file_get_contents($publicKeyPath);

// 2. 构建验证载荷
$payload = [
    'license_key' => 'XXXX-XXXX-XXXX-XXXX',  // 必填:授权码
    'verify_type' => 'domain',                // 可选:验证类型 domain/ip/file/device
    'verify_value' => 'example.com',          // 可选:验证值(与 verify_type 对应)
    'timestamp' => time(),                    // 可选:当前时间戳(秒)
    'upload_params' => [                      // 可选:自定义上传参数
        'client_version' => '1.0.0',
        'os_type' => 'linux'
    ]
];

// 3. 加密载荷
try {
    $encryptedPayload = encryptPayload($payload, $publicKey);
    echo "加密成功!\n";
    echo "加密结果长度: " . strlen($encryptedPayload) . " 字符\n";
    echo "是否分段: " . (strpos($encryptedPayload, '|') !== false ? '是' : '否') . "\n";
} catch (RuntimeException $e) {
    die("加密失败: " . $e->getMessage());
}

// 4. 发送验证请求
$appId = 'YOUR_APP_ID';  // 替换为您的应用 ID
$apiUrl = 'https://software.xingqingchuang.com/api/v1/license/verify-encrypted';

$ch = curl_init($apiUrl);
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode([
        'app_id' => $appId,
        'encrypted_payload' => $encryptedPayload
    ]),
    CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30
]);

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

// 5. 处理响应
$result = json_decode($response, true);
if ($result['valid']) {
    echo "授权验证成功!\n";
    echo "到期时间: " . $result['expires_at'] . "\n";
} else {
    echo "验证失败: " . $result['message'] . " (错误码: " . $result['code'] . ")\n";
}
📦 依赖安装
pip install cryptography

cryptography 库提供了完整的 RSA PKCS#1 v1.5 支持

📋 实现步骤
  1. 使用 serialization.load_pem_public_key() 加载 PEM 公钥
  2. 使用 json.dumps() 序列化载荷,encode('utf-8') 转字节
  3. 使用 len() 检查字节长度
  4. 使用 public_key.encrypt() 配合 padding.PKCS1v15() 加密
  5. PKCS#1 v1.5 填充不需要任何参数,直接调用 padding.PKCS1v15() 即可
  6. 使用 base64.b64encode() 编码,'|'.join() 拼接
"""
RSA PKCS#1 v1.5 加密验证载荷 - Python 完整示例
依赖: pip install cryptography requests
"""
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.backends import default_backend
import base64
import json
import time
import requests


def encrypt_payload(payload: dict, public_key_pem: str) -> str:
    """
    RSA PKCS#1 v1.5 加密验证载荷
    
    Args:
        payload: 验证载荷字典
        public_key_pem: PEM 格式公钥字符串
        
    Returns:
        Base64 编码的加密数据(分段时用 | 分隔)
        
    Raises:
        ValueError: 加密失败时抛出
    """
    # 步骤 1: 将载荷序列化为 JSON 字符串并转换为 UTF-8 字节
    json_str = json.dumps(payload, ensure_ascii=False)  # 保留中文等 Unicode 字符
    json_data = json_str.encode('utf-8')
    
    # 步骤 2: 定义单次加密最大明文长度
    # 2048位密钥 = 256字节,PKCS#1 v1.5 开销 = 11字节,最大明文 = 245字节
    max_length = 245
    
    # 步骤 3: 加载 PEM 格式公钥
    public_key = serialization.load_pem_public_key(
        public_key_pem.encode('utf-8'),
        backend=default_backend()
    )
    
    # 步骤 4: 定义 PKCS#1 v1.5 填充参数
    # 注意:PKCS#1 v1.5 填充不需要任何参数,直接调用即可
    pkcs1_padding = padding.PKCS1v15()
    
    # 步骤 5: 检查是否需要分段加密
    data_length = len(json_data)
    
    if data_length <= max_length:
        # 情况 A: 数据长度 ≤ 245 字节,单次加密
        encrypted = public_key.encrypt(json_data, pkcs1_padding)
        return base64.b64encode(encrypted).decode('utf-8')
    
    # 情况 B: 数据长度 > 245 字节,需要分段加密
    # 步骤 5.1: 按 245 字节切分数据
    chunks = [json_data[i:i+max_length] for i in range(0, len(json_data), max_length)]
    encrypted_chunks = []
    
    # 步骤 5.2: 逐段加密
    for i, chunk in enumerate(chunks):
        try:
            encrypted = public_key.encrypt(chunk, pkcs1_padding)
            # 步骤 5.3: 每段分别 Base64 编码
            encrypted_chunks.append(base64.b64encode(encrypted).decode('utf-8'))
        except Exception as e:
            raise ValueError(f"第 {i} 段加密失败: {str(e)}")
    
    # 步骤 6: 用管道符连接所有加密段
    return '|'.join(encrypted_chunks)


# ==================== 完整使用示例 ====================

if __name__ == '__main__':
    # 1. 读取公钥文件
    public_key_path = './publickey.pem'
    try:
        with open(public_key_path, 'r') as f:
            public_key = f.read()
    except FileNotFoundError:
        print(f"错误:公钥文件不存在,请从开发者中心下载 publickey.pem")
        exit(1)
    
    # 2. 构建验证载荷
    payload = {
        'license_key': 'XXXX-XXXX-XXXX-XXXX',  # 必填:授权码
        'verify_type': 'domain',                # 可选:验证类型
        'verify_value': 'example.com',          # 可选:验证值
        'timestamp': int(time.time()),          # 可选:当前时间戳(秒)
        'upload_params': {                      # 可选:自定义上传参数
            'client_version': '1.0.0',
            'os_type': 'linux'
        }
    }
    
    # 3. 加密载荷
    try:
        encrypted_payload = encrypt_payload(payload, public_key)
        print(f"加密成功!")
        print(f"加密结果长度: {len(encrypted_payload)} 字符")
        print(f"是否分段: {'是' if '|' in encrypted_payload else '否'}")
    except ValueError as e:
        print(f"加密失败: {e}")
        exit(1)
    
    # 4. 发送验证请求
    app_id = 'YOUR_APP_ID'  # 替换为您的应用 ID
    api_url = 'https://software.xingqingchuang.com/api/v1/license/verify-encrypted'
    
    response = requests.post(api_url, json={
        'app_id': app_id,
        'encrypted_payload': encrypted_payload
    }, timeout=30)
    
    # 5. 处理响应
    result = response.json()
    if result.get('valid'):
        print(f"授权验证成功!")
        print(f"到期时间: {result.get('expires_at')}")
    else:
        print(f"验证失败: {result.get('message')} (错误码: {result.get('code')})")
📦 环境要求
  • Node.js 12.0+ (内置 crypto 模块支持 PKCS#1 v1.5)
  • 可选安装 axios 用于 HTTP 请求:npm install axios
📋 实现步骤
  1. 使用 fs.readFileSync() 读取 PEM 公钥文件
  2. 使用 JSON.stringify() 序列化载荷
  3. 使用 Buffer.from() 转换为 UTF-8 字节
  4. 使用 crypto.publicEncrypt() 加密
  5. 关键参数:padding: RSA_PKCS1_PADDING
  6. 使用 toString('base64') 编码,join('|') 拼接
/**
 * RSA PKCS#1 v1.5 加密验证载荷 - Node.js 完整示例
 * 环境要求: Node.js 12.0+
 */
const crypto = require('crypto');
const fs = require('fs');
const https = require('https');

/**
 * RSA PKCS#1 v1.5 加密验证载荷
 * 
 * @param {Object} payload - 验证载荷对象
 * @param {string} publicKeyPem - PEM 格式公钥字符串
 * @returns {string} Base64 编码的加密数据(分段时用 | 分隔)
 * @throws {Error} 加密失败时抛出错误
 */
function encryptPayload(payload, publicKeyPem) {
    // 步骤 1: 将载荷序列化为 JSON 字符串
    const jsonData = JSON.stringify(payload);
    
    // 步骤 2: 转换为 UTF-8 字节 Buffer
    const buffer = Buffer.from(jsonData, 'utf-8');
    
    // 步骤 3: 定义单次加密最大明文长度
    // 2048位密钥 = 256字节,PKCS#1 v1.5 开销 = 11字节,最大明文 = 245字节
    const maxLength = 245;
    
    // 步骤 4: 定义加密选项
    // 关键:必须指定 PKCS#1 v1.5 填充和 SHA-256 哈希
    const encryptOptions = {
        key: publicKeyPem,
        padding: crypto.constants.RSA_PKCS1_PADDING,  // 使用 PKCS#1 v1.5 填充
        
    };
    
    // 步骤 5: 检查是否需要分段加密
    if (buffer.length <= maxLength) {
        // 情况 A: 数据长度 ≤ 245 字节,单次加密
        try {
            const encrypted = crypto.publicEncrypt(encryptOptions, buffer);
            return encrypted.toString('base64');
        } catch (error) {
            throw new Error(`加密失败: ${error.message}`);
        }
    }
    
    // 情况 B: 数据长度 > 245 字节,需要分段加密
    // 步骤 5.1: 按 245 字节切分数据
    const chunks = [];
    for (let i = 0; i < buffer.length; i += maxLength) {
        chunks.push(buffer.slice(i, i + maxLength));
    }
    
    // 步骤 5.2: 逐段加密
    const encryptedChunks = chunks.map((chunk, index) => {
        try {
            const encrypted = crypto.publicEncrypt(encryptOptions, chunk);
            // 步骤 5.3: 每段分别 Base64 编码
            return encrypted.toString('base64');
        } catch (error) {
            throw new Error(`第 ${index} 段加密失败: ${error.message}`);
        }
    });
    
    // 步骤 6: 用管道符连接所有加密段
    return encryptedChunks.join('|');
}

// ==================== 完整使用示例 ====================

// 1. 读取公钥文件
const publicKeyPath = './publickey.pem';
let publicKey;
try {
    publicKey = fs.readFileSync(publicKeyPath, 'utf-8');
} catch (error) {
    console.error('错误:公钥文件不存在,请从开发者中心下载 publickey.pem');
    process.exit(1);
}

// 2. 构建验证载荷
const payload = {
    license_key: 'XXXX-XXXX-XXXX-XXXX',  // 必填:授权码
    verify_type: 'domain',                // 可选:验证类型
    verify_value: 'example.com',          // 可选:验证值
    timestamp: Math.floor(Date.now() / 1000),  // 可选:当前时间戳(秒)
    upload_params: {                      // 可选:自定义上传参数
        client_version: '1.0.0',
        os_type: 'linux'
    }
};

// 3. 加密载荷
let encryptedPayload;
try {
    encryptedPayload = encryptPayload(payload, publicKey);
    console.log('加密成功!');
    console.log(`加密结果长度: ${encryptedPayload.length} 字符`);
    console.log(`是否分段: ${encryptedPayload.includes('|') ? '是' : '否'}`);
} catch (error) {
    console.error(`加密失败: ${error.message}`);
    process.exit(1);
}

// 4. 发送验证请求
const appId = 'YOUR_APP_ID';  // 替换为您的应用 ID
const requestData = JSON.stringify({
    app_id: appId,
    encrypted_payload: encryptedPayload
});

const options = {
    hostname: 'software.xingqingchuang.com',
    port: 443,
    path: '/api/v1/license/verify-encrypted',
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'Content-Length': Buffer.byteLength(requestData)
    }
};

const req = https.request(options, (res) => {
    let data = '';
    res.on('data', chunk => data += chunk);
    res.on('end', () => {
        // 5. 处理响应
        const result = JSON.parse(data);
        if (result.valid) {
            console.log('授权验证成功!');
            console.log(`到期时间: ${result.expires_at}`);
        } else {
            console.log(`验证失败: ${result.message} (错误码: ${result.code})`);
        }
    });
});

req.on('error', error => console.error(`请求失败: ${error.message}`));
req.write(requestData);
req.end();
📦 环境要求
  • Java 8+ (JCE 支持 PKCS#1 v1.5 填充)
  • 无需额外依赖,使用标准库 javax.crypto
  • 可选:使用 OkHttp 或 HttpClient 发送请求
📋 实现步骤
  1. 读取 PEM 文件,移除头尾标记,Base64 解码得到公钥字节
  2. 使用 X509EncodedKeySpecKeyFactory 构建公钥对象
  3. 使用 Cipher.getInstance("RSA/ECB/PKCS1Padding")
  4. 将 JSON 字符串转为 UTF-8 字节数组
  5. 检查长度,超过 245 字节时分段处理
  6. 使用 Base64.getEncoder() 编码,String.join("|", ...) 拼接
import javax.crypto.Cipher;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;

/**
 * RSA PKCS#1 v1.5 加密验证载荷 - Java 完整示例
 * 环境要求: Java 8+
 */
public class RSAEncryptor {
    
    // 2048位密钥 = 256字节,PKCS#1 v1.5 开销 = 11字节,最大明文 = 245字节
    private static final int MAX_ENCRYPT_BLOCK = 245;
    
    /**
     * RSA PKCS#1 v1.5 加密验证载荷
     * 
     * @param jsonPayload JSON 格式的验证载荷字符串
     * @param publicKeyPem PEM 格式公钥字符串
     * @return Base64 编码的加密数据(分段时用 | 分隔)
     * @throws Exception 加密失败时抛出异常
     */
    public static String encryptPayload(String jsonPayload, String publicKeyPem) throws Exception {
        // 步骤 1: 解析 PEM 格式公钥
        // 移除 PEM 头尾标记和换行符
        String publicKeyContent = publicKeyPem
            .replace("-----BEGIN PUBLIC KEY-----", "")
            .replace("-----END PUBLIC KEY-----", "")
            .replaceAll("\\s", "");  // 移除所有空白字符
        
        // 步骤 2: Base64 解码得到公钥字节
        byte[] keyBytes = Base64.getDecoder().decode(publicKeyContent);
        
        // 步骤 3: 构建公钥对象
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PublicKey publicKey = keyFactory.generatePublic(keySpec);
        
        // 步骤 4: 初始化加密器
        // 关键:使用 PKCS1Padding 指定 PKCS#1 v1.5 填充和 SHA-256 哈希
        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        
        // 步骤 5: 将 JSON 字符串转为 UTF-8 字节数组
        byte[] data = jsonPayload.getBytes(StandardCharsets.UTF_8);
        
        // 步骤 6: 检查是否需要分段加密
        if (data.length <= MAX_ENCRYPT_BLOCK) {
            // 情况 A: 数据长度 ≤ 245 字节,单次加密
            byte[] encrypted = cipher.doFinal(data);
            return Base64.getEncoder().encodeToString(encrypted);
        }
        
        // 情况 B: 数据长度 > 245 字节,需要分段加密
        List<String> encryptedChunks = new ArrayList<>();
        int offset = 0;
        int chunkIndex = 0;
        
        // 步骤 6.1: 按 245 字节切分并逐段加密
        while (offset < data.length) {
            // 计算当前段长度(最后一段可能不足 245 字节)
            int length = Math.min(MAX_ENCRYPT_BLOCK, data.length - offset);
            
            // 复制当前段数据
            byte[] chunk = new byte[length];
            System.arraycopy(data, offset, chunk, 0, length);
            
            // 加密当前段
            byte[] encrypted = cipher.doFinal(chunk);
            
            // 步骤 6.2: Base64 编码并添加到列表
            encryptedChunks.add(Base64.getEncoder().encodeToString(encrypted));
            
            offset += length;
            chunkIndex++;
        }
        
        // 步骤 7: 用管道符连接所有加密段
        return String.join("|", encryptedChunks);
    }
    
    /**
     * 从文件读取公钥
     */
    public static String loadPublicKey(String filePath) throws IOException {
        return new String(Files.readAllBytes(Paths.get(filePath)), StandardCharsets.UTF_8);
    }
    
    // ==================== 完整使用示例 ====================
    
    public static void main(String[] args) {
        try {
            // 1. 读取公钥文件
            String publicKeyPath = "./publickey.pem";
            String publicKey = loadPublicKey(publicKeyPath);
            
            // 2. 构建验证载荷 JSON
            // 注意:实际项目中建议使用 Jackson 或 Gson 库
            long timestamp = System.currentTimeMillis() / 1000;
            String jsonPayload = String.format(
                "{\"license_key\":\"XXXX-XXXX-XXXX-XXXX\"," +
                "\"verify_type\":\"domain\"," +
                "\"verify_value\":\"example.com\"," +
                "\"timestamp\":%d," +
                "\"upload_params\":{\"client_version\":\"1.0.0\",\"os_type\":\"linux\"}}",
                timestamp
            );
            
            // 3. 加密载荷
            String encryptedPayload = encryptPayload(jsonPayload, publicKey);
            System.out.println("加密成功!");
            System.out.println("加密结果长度: " + encryptedPayload.length() + " 字符");
            System.out.println("是否分段: " + (encryptedPayload.contains("|") ? "是" : "否"));
            
            // 4. 发送验证请求(使用 HttpURLConnection 示例)
            String appId = "YOUR_APP_ID";  // 替换为您的应用 ID
            String apiUrl = "https://software.xingqingchuang.com/api/v1/license/verify-encrypted";
            
            // 构建请求体
            String requestBody = String.format(
                "{\"app_id\":\"%s\",\"encrypted_payload\":\"%s\"}",
                appId, encryptedPayload
            );
            
            // 发送 POST 请求
            java.net.URL url = new java.net.URL(apiUrl);
            java.net.HttpURLConnection conn = (java.net.HttpURLConnection) url.openConnection();
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Content-Type", "application/json");
            conn.setDoOutput(true);
            conn.setConnectTimeout(30000);
            conn.setReadTimeout(30000);
            
            try (java.io.OutputStream os = conn.getOutputStream()) {
                os.write(requestBody.getBytes(StandardCharsets.UTF_8));
            }
            
            // 5. 读取响应
            int responseCode = conn.getResponseCode();
            java.io.InputStream is = responseCode == 200 ? conn.getInputStream() : conn.getErrorStream();
            String response = new String(is.readAllBytes(), StandardCharsets.UTF_8);
            
            System.out.println("响应: " + response);
            
        } catch (Exception e) {
            System.err.println("错误: " + e.getMessage());
            e.printStackTrace();
        }
    }
}
📦 环境要求
  • Go 1.16+ (标准库 crypto/rsa 支持 PKCS#1 v1.5)
  • 无需额外依赖,使用标准库 crypto/rsacrypto/sha256
  • 可选:使用 net/http 发送 HTTP 请求
📋 实现步骤
  1. 使用 os.ReadFile() 读取 PEM 公钥文件
  2. 使用 pem.Decode() 解析 PEM 格式,获取 DER 字节
  3. 使用 x509.ParsePKIXPublicKey() 解析公钥,类型断言为 *rsa.PublicKey
  4. 使用 json.Marshal() 序列化载荷为 JSON 字节切片
  5. 使用 len() 检查字节长度是否超过 190
  6. 使用 rsa.EncryptPKCS1v15(rand.Reader, publicKey, data) 加密
  7. 使用 base64.StdEncoding.EncodeToString() 编码
  8. 分段时使用切片操作 data[i:end]strings.Join(chunks, "|") 拼接
⚠️ Go 语言特别注意
  • rsa.EncryptPKCS1v15() 只需要 3 个参数:随机数生成器、公钥、明文数据
  • 第一个参数 rand.Reader 是加密安全随机数生成器,不可省略
  • Go 的字符串是 UTF-8 编码,[]byte(str) 直接得到 UTF-8 字节
package main

import (
    "bytes"
    "crypto/rand"
    "crypto/rsa"
    "crypto/sha256"
    "crypto/x509"
    "encoding/base64"
    "encoding/json"
    "encoding/pem"
    "fmt"
    "io"
    "net/http"
    "os"
    "strings"
    "time"
)

// 2048位密钥 = 256字节,PKCS#1 v1.5 开销 = 11字节,最大明文 = 245字节
const maxEncryptBlock = 245

// VerifyPayload 验证载荷结构
type VerifyPayload struct {
    LicenseKey   string                 `json:"license_key"`            // 必填:授权码
    VerifyType   string                 `json:"verify_type,omitempty"`  // 可选:验证类型
    VerifyValue  string                 `json:"verify_value,omitempty"` // 可选:验证值
    Timestamp    int64                  `json:"timestamp,omitempty"`    // 可选:时间戳
    UploadParams map[string]interface{} `json:"upload_params,omitempty"`// 可选:自定义参数
}

// EncryptPayload RSA PKCS#1 v1.5 加密验证载荷
//
// 参数:
//   - payload: 验证载荷结构体
//   - publicKeyPem: PEM 格式公钥字符串
//
// 返回:
//   - string: Base64 编码的加密数据(分段时用 | 分隔)
//   - error: 加密失败时返回错误
func EncryptPayload(payload VerifyPayload, publicKeyPem string) (string, error) {
    // 步骤 1: 将载荷序列化为 JSON 字节切片
    jsonData, err := json.Marshal(payload)
    if err != nil {
        return "", fmt.Errorf("JSON 序列化失败: %w", err)
    }

    // 步骤 2: 解析 PEM 格式公钥
    // pem.Decode 返回 PEM 块和剩余数据
    block, _ := pem.Decode([]byte(publicKeyPem))
    if block == nil {
        return "", fmt.Errorf("PEM 解析失败: 无效的 PEM 格式")
    }

    // 步骤 3: 解析 PKIX 格式公钥(X.509 SubjectPublicKeyInfo)
    pub, err := x509.ParsePKIXPublicKey(block.Bytes)
    if err != nil {
        return "", fmt.Errorf("公钥解析失败: %w", err)
    }

    // 步骤 4: 类型断言为 RSA 公钥
    publicKey, ok := pub.(*rsa.PublicKey)
    if !ok {
        return "", fmt.Errorf("公钥类型错误: 期望 RSA 公钥")
    }

    // 步骤 5: 检查是否需要分段加密
    dataLen := len(jsonData)

    if dataLen <= maxEncryptBlock {
        // 情况 A: 数据长度 ≤ 245 字节,单次加密
        // rsa.EncryptPKCS1v15 参数说明:
        //   - rand.Reader: 加密安全随机数生成器
        //   - publicKey: RSA 公钥
        //   - jsonData: 待加密的明文
        encrypted, err := rsa.EncryptPKCS1v15(
            rand.Reader,
            publicKey,
            jsonData,
        )
        if err != nil {
            return "", fmt.Errorf("加密失败: %w", err)
        }
        return base64.StdEncoding.EncodeToString(encrypted), nil
    }

    // 情况 B: 数据长度 > 245 字节,需要分段加密
    var encryptedChunks []string

    // 步骤 5.1: 按 245 字节切分并逐段加密
    for i := 0; i < dataLen; i += maxEncryptBlock {
        // 计算当前段结束位置(最后一段可能不足 245 字节)
        end := i + maxEncryptBlock
        if end > dataLen {
            end = dataLen
        }

        // 获取当前段数据
        chunk := jsonData[i:end]

        // 加密当前段
        encrypted, err := rsa.EncryptPKCS1v15(
            rand.Reader,
            publicKey,
            chunk,
        )
        if err != nil {
            return "", fmt.Errorf("第 %d 段加密失败: %w", i/maxEncryptBlock, err)
        }

        // 步骤 5.2: Base64 编码并添加到切片
        encryptedChunks = append(encryptedChunks, base64.StdEncoding.EncodeToString(encrypted))
    }

    // 步骤 6: 用管道符连接所有加密段
    return strings.Join(encryptedChunks, "|"), nil
}

// LoadPublicKey 从文件加载公钥
func LoadPublicKey(filePath string) (string, error) {
    data, err := os.ReadFile(filePath)
    if err != nil {
        return "", fmt.Errorf("读取公钥文件失败: %w", err)
    }
    return string(data), nil
}

// ==================== 完整使用示例 ====================

func main() {
    // 1. 读取公钥文件
    publicKeyPath := "./publickey.pem"
    publicKey, err := LoadPublicKey(publicKeyPath)
    if err != nil {
        fmt.Printf("错误:%v\n请从开发者中心下载 publickey.pem\n", err)
        os.Exit(1)
    }

    // 2. 构建验证载荷
    payload := VerifyPayload{
        LicenseKey:  "XXXX-XXXX-XXXX-XXXX", // 必填:授权码
        VerifyType:  "domain",               // 可选:验证类型
        VerifyValue: "example.com",          // 可选:验证值
        Timestamp:   time.Now().Unix(),      // 可选:当前时间戳(秒)
        UploadParams: map[string]interface{}{ // 可选:自定义上传参数
            "client_version": "1.0.0",
            "os_type":        "linux",
        },
    }

    // 3. 加密载荷
    encryptedPayload, err := EncryptPayload(payload, publicKey)
    if err != nil {
        fmt.Printf("加密失败: %v\n", err)
        os.Exit(1)
    }

    fmt.Println("加密成功!")
    fmt.Printf("加密结果长度: %d 字符\n", len(encryptedPayload))
    if strings.Contains(encryptedPayload, "|") {
        fmt.Println("是否分段: 是")
    } else {
        fmt.Println("是否分段: 否")
    }

    // 4. 发送验证请求
    appID := "YOUR_APP_ID" // 替换为您的应用 ID
    apiURL := "https://software.xingqingchuang.com/api/v1/license/verify-encrypted"

    // 构建请求体
    requestBody := map[string]string{
        "app_id":            appID,
        "encrypted_payload": encryptedPayload,
    }
    jsonBody, _ := json.Marshal(requestBody)

    // 发送 POST 请求
    resp, err := http.Post(apiURL, "application/json", bytes.NewBuffer(jsonBody))
    if err != nil {
        fmt.Printf("请求失败: %v\n", err)
        os.Exit(1)
    }
    defer resp.Body.Close()

    // 5. 读取并处理响应
    body, _ := io.ReadAll(resp.Body)

    var result map[string]interface{}
    json.Unmarshal(body, &result)

    if valid, ok := result["valid"].(bool); ok && valid {
        fmt.Println("授权验证成功!")
        if expiresAt, ok := result["expires_at"].(string); ok {
            fmt.Printf("到期时间: %s\n", expiresAt)
        }
    } else {
        msg := result["message"]
        code := result["code"]
        fmt.Printf("验证失败: %v (错误码: %v)\n", msg, code)
    }
}
📦 依赖安装

Ubuntu/Debian:

sudo apt-get install libssl-dev

macOS (Homebrew):

brew install openssl

编译命令:

gcc -o rsa_encrypt rsa_encrypt.c -lssl -lcrypto
📋 实现步骤
  1. 使用 BIO_new_mem_buf() 创建内存 BIO 读取 PEM 公钥
  2. 使用 PEM_read_bio_PUBKEY() 解析 PEM 格式公钥
  3. 使用 EVP_PKEY_CTX_new() 创建加密上下文
  4. 使用 EVP_PKEY_CTX_set_rsa_padding(RSA_PKCS1_PADDING) 设置 PKCS#1 v1.5 填充
  5. 使用 EVP_PKEY_CTX_set_rsa_padding(EVP_sha256()) 设置 SHA-256 哈希
  6. 使用 EVP_PKEY_CTX_set_rsa_mgf1_md(EVP_sha256()) 设置 MGF1 哈希
  7. 使用 strlen() 检查字节长度,超过 245 字节时分段处理
  8. 使用 EVP_PKEY_encrypt() 执行加密
  9. 使用 EVP_EncodeBlock() 进行 Base64 编码
⚠️ C 语言特别注意
  • OpenSSL 1.1.0+ 推荐使用 EVP_PKEY API,旧版 RSA_public_encrypt 已废弃
  • 必须同时设置 paddingmgf1_md 为 SHA-256
  • 注意内存管理:使用 free() 释放分配的内存,EVP_PKEY_free() 释放密钥
  • Base64 编码后长度约为原始长度的 4/3,分配缓冲区时需预留空间
  • 分段加密时,每段加密结果为 256 字节,Base64 后约 344 字符
/**
 * RSA PKCS#1 v1.5 加密验证载荷 - C 语言完整示例
 * 
 * 依赖: OpenSSL 1.1.0+
 * 编译: gcc -o rsa_encrypt rsa_encrypt.c -lssl -lcrypto
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <openssl/rsa.h>

// 2048位密钥 = 256字节,PKCS#1 v1.5 开销 = 11字节,最大明文 = 245字节
#define MAX_ENCRYPT_BLOCK 245
// RSA 2048 加密后的密文长度
#define RSA_CIPHERTEXT_LEN 256

/**
 * Base64 编码
 * 
 * @param input 输入数据
 * @param input_len 输入长度
 * @param output_len 输出长度指针
 * @return Base64 编码后的字符串(需要调用者 free)
 */
char* base64_encode(const unsigned char* input, int input_len, int* output_len) {
    // Base64 编码后长度 = ceil(input_len / 3) * 4 + 1
    int encoded_len = ((input_len + 2) / 3) * 4 + 1;
    char* output = (char*)malloc(encoded_len);
    if (!output) return NULL;
    
    *output_len = EVP_EncodeBlock((unsigned char*)output, input, input_len);
    output[*output_len] = '\0';
    return output;
}

/**
 * 使用 RSA PKCS#1 v1.5 加密单个数据块
 * 
 * @param pkey EVP_PKEY 公钥对象
 * @param data 待加密数据
 * @param data_len 数据长度
 * @param encrypted_len 加密后长度指针
 * @return 加密后的数据(需要调用者 free),失败返回 NULL
 */
unsigned char* encrypt_block(EVP_PKEY* pkey, const unsigned char* data, 
                             size_t data_len, size_t* encrypted_len) {
    EVP_PKEY_CTX* ctx = NULL;
    unsigned char* encrypted = NULL;
    
    // 步骤 1: 创建加密上下文
    ctx = EVP_PKEY_CTX_new(pkey, NULL);
    if (!ctx) {
        fprintf(stderr, "创建加密上下文失败\n");
        return NULL;
    }
    
    // 步骤 2: 初始化加密操作
    if (EVP_PKEY_encrypt_init(ctx) <= 0) {
        fprintf(stderr, "初始化加密失败\n");
        EVP_PKEY_CTX_free(ctx);
        return NULL;
    }
    
    // 步骤 3: 设置 PKCS#1 v1.5 填充方式
    if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0) {
        fprintf(stderr, "设置 PKCS#1 v1.5 填充失败\n");
        EVP_PKEY_CTX_free(ctx);
        return NULL;
    }
    
    // 步骤 4: 设置 哈希算法为 SHA-256
    if (EVP_PKEY_CTX_set_rsa_padding(ctx, EVP_sha256()) <= 0) {
        fprintf(stderr, "设置 哈希算法失败\n");
        EVP_PKEY_CTX_free(ctx);
        return NULL;
    }
    
    // 步骤 5: 设置 MGF1 哈希算法为 SHA-256
    if (EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, EVP_sha256()) <= 0) {
        fprintf(stderr, "设置 MGF1 哈希算法失败\n");
        EVP_PKEY_CTX_free(ctx);
        return NULL;
    }
    
    // 步骤 6: 获取加密后数据长度
    if (EVP_PKEY_encrypt(ctx, NULL, encrypted_len, data, data_len) <= 0) {
        fprintf(stderr, "获取加密长度失败\n");
        EVP_PKEY_CTX_free(ctx);
        return NULL;
    }
    
    // 步骤 7: 分配加密结果缓冲区
    encrypted = (unsigned char*)malloc(*encrypted_len);
    if (!encrypted) {
        fprintf(stderr, "内存分配失败\n");
        EVP_PKEY_CTX_free(ctx);
        return NULL;
    }
    
    // 步骤 8: 执行加密
    if (EVP_PKEY_encrypt(ctx, encrypted, encrypted_len, data, data_len) <= 0) {
        fprintf(stderr, "加密失败: ");
        ERR_print_errors_fp(stderr);
        free(encrypted);
        EVP_PKEY_CTX_free(ctx);
        return NULL;
    }
    
    EVP_PKEY_CTX_free(ctx);
    return encrypted;
}

/**
 * RSA PKCS#1 v1.5 加密验证载荷(支持分段加密)
 * 
 * @param json_payload JSON 格式的验证载荷字符串
 * @param public_key_pem PEM 格式公钥字符串
 * @return Base64 编码的加密数据(分段时用 | 分隔),失败返回 NULL
 *         需要调用者 free 返回的字符串
 */
char* encrypt_payload(const char* json_payload, const char* public_key_pem) {
    BIO* bio = NULL;
    EVP_PKEY* pkey = NULL;
    char* result = NULL;
    
    // 步骤 1: 创建内存 BIO 读取 PEM 公钥
    bio = BIO_new_mem_buf(public_key_pem, -1);
    if (!bio) {
        fprintf(stderr, "创建 BIO 失败\n");
        return NULL;
    }
    
    // 步骤 2: 解析 PEM 格式公钥
    pkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
    BIO_free(bio);
    if (!pkey) {
        fprintf(stderr, "解析公钥失败: ");
        ERR_print_errors_fp(stderr);
        return NULL;
    }
    
    // 步骤 3: 获取数据长度
    size_t data_len = strlen(json_payload);
    const unsigned char* data = (const unsigned char*)json_payload;
    
    // 步骤 4: 检查是否需要分段加密
    if (data_len <= MAX_ENCRYPT_BLOCK) {
        // 情况 A: 数据长度 ≤ 245 字节,单次加密
        size_t encrypted_len;
        unsigned char* encrypted = encrypt_block(pkey, data, data_len, &encrypted_len);
        if (!encrypted) {
            EVP_PKEY_free(pkey);
            return NULL;
        }
        
        // Base64 编码
        int base64_len;
        result = base64_encode(encrypted, encrypted_len, &base64_len);
        free(encrypted);
        EVP_PKEY_free(pkey);
        return result;
    }
    
    // 情况 B: 数据长度 > 245 字节,需要分段加密
    // 计算分段数量
    size_t num_chunks = (data_len + MAX_ENCRYPT_BLOCK - 1) / MAX_ENCRYPT_BLOCK;
    
    // 预分配结果缓冲区
    // 每段 Base64 约 344 字符 + 分隔符 + 结束符
    size_t result_size = num_chunks * 350 + 1;
    result = (char*)malloc(result_size);
    if (!result) {
        fprintf(stderr, "内存分配失败\n");
        EVP_PKEY_free(pkey);
        return NULL;
    }
    result[0] = '\0';
    
    // 步骤 5: 逐段加密
    size_t offset = 0;
    int chunk_index = 0;
    
    while (offset < data_len) {
        // 计算当前段长度
        size_t chunk_len = data_len - offset;
        if (chunk_len > MAX_ENCRYPT_BLOCK) {
            chunk_len = MAX_ENCRYPT_BLOCK;
        }
        
        // 加密当前段
        size_t encrypted_len;
        unsigned char* encrypted = encrypt_block(pkey, data + offset, chunk_len, &encrypted_len);
        if (!encrypted) {
            fprintf(stderr, "第 %d 段加密失败\n", chunk_index);
            free(result);
            EVP_PKEY_free(pkey);
            return NULL;
        }
        
        // Base64 编码
        int base64_len;
        char* base64_chunk = base64_encode(encrypted, encrypted_len, &base64_len);
        free(encrypted);
        
        if (!base64_chunk) {
            fprintf(stderr, "第 %d 段 Base64 编码失败\n", chunk_index);
            free(result);
            EVP_PKEY_free(pkey);
            return NULL;
        }
        
        // 拼接结果(第一段不加分隔符)
        if (chunk_index > 0) {
            strcat(result, "|");
        }
        strcat(result, base64_chunk);
        free(base64_chunk);
        
        offset += chunk_len;
        chunk_index++;
    }
    
    EVP_PKEY_free(pkey);
    return result;
}

/**
 * 从文件读取公钥
 */
char* load_public_key(const char* file_path) {
    FILE* fp = fopen(file_path, "r");
    if (!fp) {
        fprintf(stderr, "无法打开公钥文件: %s\n", file_path);
        return NULL;
    }
    
    fseek(fp, 0, SEEK_END);
    long file_size = ftell(fp);
    fseek(fp, 0, SEEK_SET);
    
    char* content = (char*)malloc(file_size + 1);
    if (!content) {
        fclose(fp);
        return NULL;
    }
    
    fread(content, 1, file_size, fp);
    content[file_size] = '\0';
    fclose(fp);
    
    return content;
}

// ==================== 完整使用示例 ====================

int main() {
    // 初始化 OpenSSL
    OpenSSL_add_all_algorithms();
    ERR_load_crypto_strings();
    
    // 1. 读取公钥文件
    const char* public_key_path = "./publickey.pem";
    char* public_key = load_public_key(public_key_path);
    if (!public_key) {
        fprintf(stderr, "错误:公钥文件不存在,请从开发者中心下载 publickey.pem\n");
        return 1;
    }
    
    // 2. 构建验证载荷 JSON
    // 注意:实际项目中建议使用 cJSON 等 JSON 库
    time_t timestamp = time(NULL);
    char json_payload[1024];
    snprintf(json_payload, sizeof(json_payload),
        "{\"license_key\":\"XXXX-XXXX-XXXX-XXXX\","
        "\"verify_type\":\"domain\","
        "\"verify_value\":\"example.com\","
        "\"timestamp\":%ld,"
        "\"upload_params\":{\"client_version\":\"1.0.0\",\"os_type\":\"linux\"}}",
        timestamp
    );
    
    printf("原始载荷长度: %zu 字节\n", strlen(json_payload));
    
    // 3. 加密载荷
    char* encrypted_payload = encrypt_payload(json_payload, public_key);
    free(public_key);
    
    if (!encrypted_payload) {
        fprintf(stderr, "加密失败\n");
        return 1;
    }
    
    printf("加密成功!\n");
    printf("加密结果长度: %zu 字符\n", strlen(encrypted_payload));
    printf("是否分段: %s\n", strchr(encrypted_payload, '|') ? "是" : "否");
    
    // 4. 打印加密结果(实际使用时发送到 API)
    printf("\n加密结果:\n%s\n", encrypted_payload);
    
    // 5. 清理
    free(encrypted_payload);
    
    // 清理 OpenSSL
    EVP_cleanup();
    ERR_free_strings();
    
    printf("\n提示: 请将加密结果发送到 /api/v1/license/verify-encrypted 接口\n");
    printf("请求体格式: {\"app_id\":\"YOUR_APP_ID\",\"encrypted_payload\":\"...\"}\n");
    
    return 0;
}

注意事项

  • 保持载荷简洁:建议验证载荷保持在 245 字节以内,避免分段加密的复杂性
  • 时间戳可选:载荷中可包含 timestamp 字段,用于防止重放攻击(有效期 5 分钟)
  • 编码一致性:JSON 序列化时使用 UTF-8 编码
  • 公钥获取:可通过 /api/v1/app/public-key 接口获取应用公钥
全部优惠券

已启用

已使用次数

已禁用

优惠码 名称 折扣 适用范围 使用情况 有效期 状态 操作

条记录

我的增值品

管理您发布的所有增值品

增值品 价格 状态 绑定应用 创建时间 操作

暂无增值品

个增值品

/
测试模式

自我检测功能

此功能用于测试验证流程,测试请求不会影响生产数据。您可以模拟不同验证场景,查看完整的回调数据结构。

测试历史

最近 5 条

暂无测试记录

测试配置

选择应用并填写测试参数

填写后响应会包含 force_update 字段

注意:info 需要与 upload_params 并列(不要放在 upload_params 里)

info 文件解析

上传 info 内容并使用私钥解密查看内容

从应用安装包中提取的 app/.info 内容

解析结果

JSON 格式

                                            

解析失败

测试结果将在此处显示

填写左侧表单并点击"发送测试请求"按钮,即可查看验证结果

请求数据结构示例 (Request)

{
  "app_id": "abc123def456",           // 应用ID (必填)
  "license_key": "XXXX-XXXX-XXXX-XXXX", // 授权码 (必填)
  "verify_type": "domain",            // 验证类型: domain/ip/device/file (必填)
  "verify_value": "example.com",      // 验证值 (必填)
  "upload_params": {                  // 上传参数 (可选)
    "machine_code": "ABC123",
    "os_version": "Windows 11"
  }
}

verify_type 可选值: domain 域名验证 | ip IP验证 | device 设备验证 | file 文件哈希验证

回调数据结构示例 (Response)

{
  "valid": true,                      // 验证结果: true/false
  "code": 0,                          // 响应码: 0=成功, 其他=错误码
  "message": "验证成功",               // 响应消息
  "license_key": "XXXX-XXXX-XXXX-XXXX", // 授权码
  "activated_at": "2024-01-15 10:30:00", // 激活时间
  "expires_at": "2025-01-15 10:30:00", // 过期时间 (永久授权为 "2099-12-31 24:00:00")
  "remaining_days": 365,              // 剩余天数 (永久授权为 999999)
  "latest_version": "2.1.0",          // 应用最新版本号
  "channel": "green",                 // 绑定渠道: "green"=新绑定, "veteran"=已有绑定, null=无绑定操作
  "features": {                       // 套餐限制参数
    "remain_domain": 2,               // 剩余可绑定域名数 (域名验证时)
    "remain_ip": 3,                   // 剩余可绑定IP数 (IP验证时)
    "remain_device": 1,               // 剩余可绑定设备数 (设备验证时)
    "remain_file": 5                  // 剩余可绑定文件数 (文件验证时)
  },
  "callback_params": {                // 回调参数 (开发者配置)
    "api_endpoint": "https://api.example.com",
    "custom_key": "custom_value"
  }
}

说明: features 中的字段根据验证类型动态返回; channel 字段始终返回,标识绑定状态。

错误码说明

0 验证成功
1001 授权码不存在
1002 授权已被撤销
1003 授权已过期
1004 域名不匹配
1005 IP不匹配
1006 设备不匹配
1007 文件哈希不匹配
1008 绑定数量已达上限
1009 应用不存在
1010 应用已下架
9999 系统错误

网络连接失败

请求失败

请求内容 (Request Content)

授权码
验证类型
验证值
当前版本
上传参数 (upload_params)

                                        

回调内容 (Response Content)

响应码:
过期时间
剩余天数
最新版本

套餐限制参数 (features)

回调参数 (callback_params)


                                        

下载测试

下载码:

使用下载码调用下载接口,测试资源下载功能

完整响应数据


                                        

设置增值品信息和绑定应用

建议尺寸: 256x256px

选择可以使用此增值品的应用

设置优惠券信息和使用规则

基本信息

适用范围

¥

使用限制

有效期

使用记录 -

条记录

请求详情

基本信息

请求ID

应用名称

授权码

验证类型

验证值

IP地址

创建时间

最近请求

验证结果

错误码: |

完整请求参数 黄色为系统参数)


                        

提取参数信息

从请求记录中提取指定参数导出为CSV

参数将从请求记录的 all_params 字段中提取,支持嵌套参数如 extra.user_id

当前筛选条件

应用: 版本: 结果: 开始: 结束:

条记录符合条件(最多导出1000条)

正在提取数据...

数据预览 (共 条)

已去重 条旧记录 按授权信息去重,保留最新日期
请求ID 授权码 验证值 日期

没有找到符合条件的数据

创建新软件

填写软件信息,提交后等待审核

支持 JPG、PNG、GIF、WebP

最大 2MB,建议 200×200

(不可更改)

启用后,授权将基于根域名进行验证,所有子域名共享同一授权

仅支持字母、数字和连字符

验证方式创建后不可修改,请谨慎选择

支持 Markdown 语法

添加授权

为用户手动创建授权

搜索并选择要授权的用户,支持批量选择

未找到匹配用户

搜索并选择用户

该应用暂无可用套餐

授权预览

将为 个用户创建授权, 每个用户将获得独立的授权码。

上传参数历史

加载中...

暂无上传参数记录

添加代理商

搜索并添加用户作为代理商

未找到匹配的用户

输入用户名或邮箱开始搜索

确认移除代理商

您确定要移除以下代理商吗?

移除后,该用户将无法再为此软件创建授权。