C++开发的桌面软件,在正式发布销售时往往会面临授权管理的问题。用户买了软件怎么激活?如何防止一份授权被多台机器使用?授权到期后如何自动失效?
本文介绍一种基于HTTP API的轻量级网络验证方案,使用卡密通提供的云端服务来实现软件的授权管理。全程不需要自己搭建服务器,通过API调用即可完成卡密验证、设备绑定、心跳保活等功能。
一、为什么选择HTTP API方案?
对于C++开发者来说,网络验证的实现方式通常有两种:
| 方案 | 实现方式 | 适用场景 |
|---|---|---|
| 使用现成SDK | 直接集成官方提供的C++库 | 平台官方提供了C++SDK |
| HTTP API对接 | 自己用libcurl或WinHTTP封装请求 | 通用方案,任何C++项目都能用 |
卡密通目前提供的是HTTP API方案,开发者可以使用libcurl、WinHTTP、cpprestsdk等库自行封装HTTP请求。这种方式的好处是灵活、可控,不依赖特定的第三方库版本。
二、验证流程
C++软件接入网络验证后,整个流程是这样的:
软件启动,弹出卡密输入框
用户输入卡密,软件获取本机机器码
软件构造HTTP请求,将卡密和机器码发送到云端验证接口
云端接口返回验证结果(成功/失败/错误原因)
验证通过后,软件启动心跳线程,每隔一段时间自动验证一次
心跳验证连续失败达到阈值,软件自动退出
验证接口返回的数据包含签名校验字段,可以有效防止中间人篡改。
三、核心代码实现
以下是一个完整的C++网络验证类实现,使用libcurl库进行HTTP请求。编译时需要链接libcurl库。
cpp
/** * 卡密验证系统 - C++ HTTP API对接 * 使用libcurl实现网络请求 * * 编译: g++ auth.cpp -lcurl -o auth */ #include <iostream> #include <string> #include <thread> #include <chrono> #include <sstream> #include <iomanip> #include <random> #include <functional> #include <curl/curl.h> // ============================================================ // 配置区(改成你自己的) // ============================================================ #define API_URL "https://www.keyt.cn/kami/你的账号/check.php" #define APP_NAME "你的应用名" #define SIGN_KEY "你的签名密钥" // 用于MD5签名验证 #define HEARTBEAT_INTERVAL 55 // 心跳间隔(秒) #define MAX_HEARTBEAT_FAILS 3 // 最大心跳失败次数 #define TIMESTAMP_TOLERANCE 120 // 时间戳容差(秒) #define REQUEST_TIMEOUT 10 // 请求超时(秒) // ============================================================ // ==================== 工具函数 ==================== // MD5计算(纯C++实现) std::string md5(const std::string& input) { // 实际项目建议使用OpenSSL或系统API // 这里为示例代码占位,使用时请替换为实际的MD5实现 // 可以引入 <openssl/md5.h> 或使用Windows的CryptAcquireContext return "md5_placeholder"; } // 获取当前UTC时间戳 long get_timestamp() { return std::chrono::duration_cast<std::chrono::seconds>( std::chrono::system_clock::now().time_since_epoch() ).count(); } // 获取机器码(设备指纹) std::string get_machine_code() { #ifdef _WIN32 // Windows: 可以通过GetVolumeInformation或注册表获取 // 示例:使用Volume Serial Number return "WIN_" + std::to_string(rand()); #else // Linux: 可以读取 /etc/machine-id return "LINUX_" + std::to_string(rand()); #endif } // 格式化时间为字符串 std::string format_time(const std::string& fmt) { auto now = std::chrono::system_clock::now(); auto time_t = std::chrono::system_clock::to_time_t(now); std::stringstream ss; ss << std::put_time(std::localtime(&time_t), fmt.c_str()); return ss.str(); } // ==================== HTTP请求 ==================== // libcurl 响应回调 size_t write_callback(void* contents, size_t size, size_t nmemb, std::string* output) { size_t total = size * nmemb; output->append((char*)contents, total); return total; } // 发送GET请求 std::string http_get(const std::string& url) { CURL* curl = curl_easy_init(); if (!curl) return ""; std::string response; curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); curl_easy_setopt(curl, CURLOPT_TIMEOUT, REQUEST_TIMEOUT); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); curl_easy_setopt(curl, CURLOPT_USERAGENT, "C++Client/1.0"); CURLcode res = curl_easy_perform(curl); curl_easy_cleanup(curl); return (res == CURLE_OK) ? response : ""; } // ==================== 签名验证 ==================== // 提取MD5签名 std::string extract_sign(const std::string& raw) { size_t pos = raw.find("|sign="); if (pos == std::string::npos) return ""; return raw.substr(pos + 6); } // 验证响应签名 bool verify_response(const std::string& raw_body) { size_t sign_pos = raw_body.find("|sign="); if (sign_pos == std::string::npos) return false; std::string body = raw_body.substr(0, sign_pos); std::string sign = raw_body.substr(sign_pos + 6); std::string local_sign = md5(body + SIGN_KEY); return local_sign == sign; } // 验证时间戳(防重放) bool verify_timestamp(const std::string& body) { size_t last_pipe = body.rfind("|"); if (last_pipe == std::string::npos) return false; std::string ts_str = body.substr(last_pipe + 1); long ts = std::stol(ts_str); long diff = std::abs(get_timestamp() - ts); return diff <= TIMESTAMP_TOLERANCE; } // ==================== 核心验证函数 ==================== struct VerifyResult { bool success; std::string message; std::string remaining; std::string card; }; VerifyResult verify_card(const std::string& card) { std::string mac = get_machine_code(); // 构造请求URL std::string url = std::string(API_URL) + "?card=" + card + "&mac=" + mac + "&app=" + APP_NAME + "&_t=" + std::to_string(get_timestamp()); // 发送请求 std::string response = http_get(url); if (response.empty()) { return {false, "网络请求失败,请检查网络连接", "", card}; } // 提取业务数据(去掉签名部分) size_t sign_pos = response.find("|sign="); std::string biz = (sign_pos != std::string::npos) ? response.substr(0, sign_pos) : response; // 验证签名 if (!verify_response(response)) { return {false, "签名校验失败", "", card}; } // 验证时间戳 if (!verify_timestamp(biz)) { return {false, "请求已过期,请重新验证", "", card}; } // 检查系统锁死 if (biz.find("system_locked") != std::string::npos) { return {false, "系统已被管理员锁死", "", card}; } // 解析返回结果 if (biz.find("ok|") == 0 || biz.find("bypass") != std::string::npos) { // 解析剩余时间 std::string remaining; if (biz.find("permanent") != std::string::npos) { remaining = "永久有效"; } else { size_t pos = biz.rfind("|"); if (pos != std::string::npos) { std::string parts = biz.substr(pos + 1); int mins = std::stoi(parts); int days = mins / 1440; int hours = (mins % 1440) / 60; int minutes = mins % 60; if (days > 0) remaining += std::to_string(days) + "天"; if (hours > 0) remaining += std::to_string(hours) + "小时"; if (minutes > 0) remaining += std::to_string(minutes) + "分钟"; if (remaining.empty()) remaining = "即将过期"; } } return {true, "验证成功", remaining, card}; } // 处理错误 if (biz.find("error|") != std::string::npos) { size_t pos = biz.find("|"); std::string err = (pos != std::string::npos) ? biz.substr(pos + 1) : "未知错误"; return {false, err, "", card}; } return {false, "验证失败,请重试", "", card}; } // ==================== 心跳 ==================== class HeartbeatManager { public: using Callback = std::function<void(const std::string&)>; void start(const std::string& card, Callback on_fail) { running_ = true; fail_count_ = 0; thread_ = std::thread([this, card, on_fail]() { // 首次延迟55秒 std::this_thread::sleep_for(std::chrono::seconds(HEARTBEAT_INTERVAL)); while (running_) { std::this_thread::sleep_for(std::chrono::seconds(HEARTBEAT_INTERVAL)); auto result = verify_card(card); if (result.success) { fail_count_ = 0; std::cout << "💓 心跳成功" << std::endl; } else { fail_count_++; std::cout << "❌ 心跳失败 (" << fail_count_ << "/" << MAX_HEARTBEAT_FAILS << "): " << result.message << std::endl; if (fail_count_ >= MAX_HEARTBEAT_FAILS) { std::cout << "💀 心跳连续失败" << MAX_HEARTBEAT_FAILS << "次,程序退出" << std::endl; if (on_fail) { on_fail(result.message); } std::exit(0); } } } }); } void stop() { running_ = false; if (thread_.joinable()) { thread_.join(); } } ~HeartbeatManager() { stop(); } private: std::thread thread_; bool running_ = false; int fail_count_ = 0; }; // ==================== 验证窗口(控制台示例) ==================== int main() { std::cout << "🔐 软件验证" << std::endl; std::cout << "请输入卡密: "; std::string card; std::getline(std::cin, card); if (card.empty()) { std::cout << "❌ 卡密不能为空" << std::endl; return 1; } std::cout << "验证中..." << std::endl; auto result = verify_card(card); if (!result.success) { std::cout << "❌ " << result.message << std::endl; return 1; } std::cout << "✅ 验证成功!"; if (!result.remaining.empty()) { std::cout << " 剩余时间: " << result.remaining; } std::cout << std::endl; // 启动心跳 HeartbeatManager heartbeat; heartbeat.start(card, [](const std::string& reason) { std::cout << "💀 心跳失败: " << reason << std::endl; }); // 主程序逻辑 std::cout << "========================================" << std::endl; std::cout << "软件主程序已启动,心跳在后台运行" << std::endl; std::cout << "按 Ctrl+C 退出" << std::endl; std::cout << "========================================" << std::endl; while (true) { std::this_thread::sleep_for(std::chrono::seconds(1)); } return 0; }四、接入步骤
第一步:获取API信息
在卡密通平台注册账号,创建应用后获取以下信息:
API地址(
https://www.keyt.cn/kami/你的账号/check.php)应用名称(APP_NAME)
签名密钥(SIGN_KEY)
第二步:编写验证代码
根据上面的示例代码,在项目中实现验证逻辑。
第三步:在软件启动时调用验证
cpp
int main() { // 卡密验证 std::string card = get_card_from_user(); auto result = verify_card(card); if (!result.success) { show_error(result.message); return 1; } // 启动心跳 start_heartbeat(card); // 进入主程序 run_main_program(); return 0; }第四步:生成卡密
在平台后台批量生成卡密,分发给用户。
五、功能说明
| 功能 | 说明 |
|---|---|
| 卡密验证 | 用户输入卡密,调用云端API验证 |
| 一机一码 | 首次激活绑定设备,换设备无法使用 |
| MD5签名校验 | 防止中间人篡改API返回数据 |
| 时间戳防重放 | 超过120秒的请求自动拒绝 |
| 心跳保活 | 每55秒验证一次,连续失败3次自动退出 |
六、后台管理
登录卡密通平台后台可以:
批量生成卡密(天卡/周卡/月卡/季卡/年卡/终身卡)
查看卡密使用状态
禁用/启用卡密
查看在线设备和使用记录
七、常见问题
需要自己搭服务器吗?
不需要。验证服务由卡密通云端提供。
C++项目需要什么额外依赖?
示例代码使用libcurl进行HTTP请求,需要安装libcurl库并链接。也可以用WinHTTP或其他库替代。
MD5签名验证的目的是什么?
防止网络传输过程中数据被篡改,确保收到的验证结果是真实的。
心跳机制的作用?
防止用户卡密过期后继续离线使用。软件定期验证,过期后自动退出。
支持哪些编译环境?
支持Visual Studio、MinGW、Clang等主流C++编译器,跨Windows、Linux、macOS平台。
八、总结
C++桌面软件通过HTTP API对接网络验证,不需要自己搭服务器,只需要在代码中调用云端验证接口即可实现完整的软件授权管理。签名校验和心跳机制保障了验证过程的安全性,一机一码绑定有效防止授权被分享扩散。
详细的接入文档和更多语言示例,可以在网上搜索相关平台获取。