以下是完整的 C# 验证客户端实现,无需第三方依赖,使用 .NET 内置库。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
namespace AuthSDK
{
public class AuthClient : IDisposable
{
private readonly string _appKey;
private readonly string _appSecret;
private readonly string _transferKey;
private readonly string _apiUrl;
private readonly HttpClient _httpClient;
public string Token { get; private set; } = "";
public string ExecToken { get; private set; } = "";
public string Username { get; private set; } = "";
public string ExpireTime { get; private set; } = "";
public int Points { get; private set; } = 0;
public int HeartbeatInterval { get; private set; } = 30;
public string LastError { get; private set; } = "";
public int LastCode { get; private set; } = 0;
public AuthClient(string appKey, string appSecret, string transferKey, string apiUrl)
{
_appKey = appKey;
_appSecret = appSecret;
_transferKey = transferKey;
_apiUrl = apiUrl;
_httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
}
#region 加密算法
/// <summary>RC4 加密/解密</summary>
private static byte[] RC4(byte[] data, byte[] key)
{
byte[] S = new byte[256];
for (int i = 0; i < 256; i++) S[i] = (byte)i;
int j = 0;
for (int i = 0; i < 256; i++)
{
j = (j + S[i] + key[i % key.Length]) % 256;
(S[i], S[j]) = (S[j], S[i]);
}
byte[] result = new byte[data.Length];
int x = 0, y = 0;
for (int k = 0; k < data.Length; k++)
{
x = (x + 1) % 256;
y = (y + S[x]) % 256;
(S[x], S[y]) = (S[y], S[x]);
result[k] = (byte)(data[k] ^ S[(S[x] + S[y]) % 256]);
}
return result;
}
/// <summary>HMAC-SHA256 签名</summary>
private static string HmacSha256(string data, string key)
{
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key));
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(data));
return BitConverter.ToString(hash).Replace("-", "").ToLower();
}
/// <summary>字节数组转HEX字符串</summary>
private static string BytesToHex(byte[] bytes)
{
return BitConverter.ToString(bytes).Replace("-", "").ToLower();
}
/// <summary>HEX字符串转字节数组</summary>
private static byte[] HexToBytes(string hex)
{
byte[] bytes = new byte[hex.Length / 2];
for (int i = 0; i < bytes.Length; i++)
{
bytes[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16);
}
return bytes;
}
/// <summary>生成随机字符串</summary>
private static string GenerateNonce(int length = 16)
{
const string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
var random = new Random();
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[random.Next(s.Length)]).ToArray());
}
/// <summary>获取Unix时间戳</summary>
private static long GetTimestamp()
{
return DateTimeOffset.UtcNow.ToUnixTimeSeconds();
}
#endregion
#region 请求处理
/// <summary>构建加密请求</summary>
private string BuildRequest(SortedDictionary<string, string> parameters)
{
// 拼接参数
var data = string.Join("&", parameters.Select(p => $"{p.Key}={p.Value}"));
// 计算签名
var sign = HmacSha256(data, _appSecret);
data += "&sign=" + sign;
// RC4加密
var encrypted = RC4(Encoding.UTF8.GetBytes(data), Encoding.UTF8.GetBytes(_transferKey));
return BytesToHex(encrypted);
}
/// <summary>解密响应</summary>
private string DecryptResponse(string response)
{
if (string.IsNullOrEmpty(response) || response.StartsWith("{"))
{
return response; // 明文响应
}
// HEX解码并RC4解密
var bytes = HexToBytes(response);
var decrypted = RC4(bytes, Encoding.UTF8.GetBytes(_transferKey));
return Encoding.UTF8.GetString(decrypted);
}
/// <summary>发送请求</summary>
private async Task<JsonDocument?> SendRequestAsync(SortedDictionary<string, string> parameters)
{
try
{
var encData = BuildRequest(parameters);
var url = $"{_apiUrl}?app_key={_appKey}&data={encData}";
var response = await _httpClient.GetStringAsync(url);
var decrypted = DecryptResponse(response);
var json = JsonDocument.Parse(decrypted);
LastCode = json.RootElement.GetProperty("code").GetInt32();
LastError = json.RootElement.TryGetProperty("msg", out var msg) ? msg.GetString() ?? "" : "";
return json;
}
catch (Exception ex)
{
LastError = ex.Message;
LastCode = -1;
return null;
}
}
#endregion
#region API方法
/// <summary>初始化</summary>
public async Task<bool> InitAsync(string machineCode, string version = "")
{
var parameters = new SortedDictionary<string, string>
{
["action"] = "init",
["machine_code"] = machineCode,
["nonce"] = GenerateNonce(),
["timestamp"] = GetTimestamp().ToString()
};
if (!string.IsNullOrEmpty(version))
parameters["version"] = version;
var json = await SendRequestAsync(parameters);
if (json == null || LastCode != 1) return false;
var root = json.RootElement;
HeartbeatInterval = root.TryGetProperty("heartbeat_interval", out var hb) ? hb.GetInt32() : 30;
if (HeartbeatInterval <= 0) HeartbeatInterval = 30;
return true;
}
/// <summary>登录(统一接口)</summary>
/// <param name="machineCode">机器码</param>
/// <param name="username">用户名或卡密</param>
/// <param name="password">密码(卡密登录传"0")</param>
public async Task<bool> LoginAsync(string machineCode, string username, string password = "0")
{
var parameters = new SortedDictionary<string, string>
{
["action"] = "login",
["machine_code"] = machineCode,
["nonce"] = GenerateNonce(),
["password"] = password,
["timestamp"] = GetTimestamp().ToString(),
["username"] = username
};
var json = await SendRequestAsync(parameters);
if (json == null || LastCode != 2) return false;
var root = json.RootElement;
Token = root.GetProperty("token").GetString() ?? "";
ExecToken = root.TryGetProperty("exec_token", out var et) ? et.GetString() ?? "" : "";
Username = root.TryGetProperty("username", out var un) ? un.GetString() ?? "" : "";
ExpireTime = root.TryGetProperty("expire_time", out var exp) ? exp.GetString() ?? "" : "";
Points = root.TryGetProperty("points", out var pts) ? pts.GetInt32() : 0;
return true;
}
/// <summary>心跳</summary>
public async Task<bool> HeartbeatAsync(string machineCode = "")
{
var parameters = new SortedDictionary<string, string>
{
["action"] = "heartbeat",
["timestamp"] = GetTimestamp().ToString(),
["token"] = Token
};
if (!string.IsNullOrEmpty(machineCode))
parameters["machine_code"] = machineCode;
var json = await SendRequestAsync(parameters);
if (json == null || LastCode != 3) return false;
var root = json.RootElement;
ExpireTime = root.TryGetProperty("expire_time", out var exp) ? exp.GetString() ?? "" : ExpireTime;
Points = root.TryGetProperty("points", out var pts) ? pts.GetInt32() : Points;
ExecToken = root.TryGetProperty("exec_token", out var et) ? et.GetString() ?? "" : ExecToken;
return true;
}
/// <summary>登出</summary>
public async Task<bool> LogoutAsync(string machineCode = "")
{
var parameters = new SortedDictionary<string, string>
{
["action"] = "logout",
["timestamp"] = GetTimestamp().ToString(),
["token"] = Token
};
if (!string.IsNullOrEmpty(machineCode))
parameters["machine_code"] = machineCode;
await SendRequestAsync(parameters);
return LastCode == 4;
}
/// <summary>扣点</summary>
public async Task<bool> DeductAsync(int points, string requestId = "", string machineCode = "")
{
var parameters = new SortedDictionary<string, string>
{
["action"] = "deduct",
["points"] = points.ToString(),
["timestamp"] = GetTimestamp().ToString(),
["token"] = Token
};
if (!string.IsNullOrEmpty(requestId))
parameters["request_id"] = requestId;
if (!string.IsNullOrEmpty(machineCode))
parameters["machine_code"] = machineCode;
var json = await SendRequestAsync(parameters);
if (json == null || LastCode != 8) return false;
Points = json.RootElement.TryGetProperty("points", out var pts) ? pts.GetInt32() : Points;
return true;
}
/// <summary>充值</summary>
public async Task<bool> RechargeAsync(string cardNo, string username = "")
{
var parameters = new SortedDictionary<string, string>
{
["action"] = "recharge",
["card_no"] = cardNo,
["timestamp"] = GetTimestamp().ToString()
};
if (!string.IsNullOrEmpty(Token))
parameters["token"] = Token;
else if (!string.IsNullOrEmpty(username))
parameters["username"] = username;
var json = await SendRequestAsync(parameters);
if (json == null || LastCode != 5) return false;
var root = json.RootElement;
ExpireTime = root.TryGetProperty("expire_time", out var exp) ? exp.GetString() ?? "" : ExpireTime;
Points = root.TryGetProperty("points", out var pts) ? pts.GetInt32() : Points;
return true;
}
/// <summary>验证Token</summary>
public async Task<bool> VerifyAsync(string machineCode = "")
{
var parameters = new SortedDictionary<string, string>
{
["action"] = "verify",
["timestamp"] = GetTimestamp().ToString(),
["token"] = Token
};
if (!string.IsNullOrEmpty(machineCode))
parameters["machine_code"] = machineCode;
var json = await SendRequestAsync(parameters);
return json != null && LastCode == 9;
}
#endregion
public void Dispose()
{
_httpClient?.Dispose();
}
}
}
using System;
using System.Management;
using System.Threading;
using System.Threading.Tasks;
using AuthSDK;
class Program
{
// 获取机器码(示例)
static string GetMachineCode()
{
try
{
// 获取CPU ID
using var mc = new ManagementClass("Win32_Processor");
foreach (ManagementObject mo in mc.GetInstances())
{
return mo["ProcessorId"]?.ToString() ?? "DEFAULT_MACHINE_CODE";
}
}
catch { }
return "DEFAULT_MACHINE_CODE";
}
static async Task Main(string[] args)
{
// 配置
const string APP_KEY = "your_app_key";
const string APP_SECRET = "your_app_secret";
const string TRANSFER_KEY = "your_transfer_key";
const string API_URL = "https://your-server.com/api/v1/client/enc";
using var client = new AuthClient(APP_KEY, APP_SECRET, TRANSFER_KEY, API_URL);
var machineCode = GetMachineCode();
var cts = new CancellationTokenSource();
// 1. 初始化
Console.WriteLine("正在初始化...");
if (!await client.InitAsync(machineCode, "1.0.0"))
{
Console.WriteLine($"初始化失败: {client.LastError}");
return;
}
Console.WriteLine($"初始化成功,心跳间隔: {client.HeartbeatInterval}秒");
// 2. 登录
Console.Write("请输入卡密: ");
var cardNo = Console.ReadLine() ?? "";
Console.WriteLine("正在登录...");
if (!await client.LoginAsync(machineCode, cardNo, "0"))
{
Console.WriteLine($"登录失败: {client.LastError} (code: {client.LastCode})");
return;
}
Console.WriteLine("登录成功!");
Console.WriteLine($"用户名: {client.Username}");
Console.WriteLine($"到期时间: {client.ExpireTime}");
Console.WriteLine($"剩余点数: {client.Points}");
// 3. 启动心跳线程
_ = Task.Run(async () =>
{
while (!cts.Token.IsCancellationRequested)
{
await Task.Delay(client.HeartbeatInterval * 1000, cts.Token);
if (cts.Token.IsCancellationRequested) break;
if (!await client.HeartbeatAsync(machineCode))
{
Console.WriteLine($"\n心跳失败: {client.LastError}");
cts.Cancel();
}
}
}, cts.Token);
// 4. 主循环
Console.WriteLine("\n按 q 退出,按 d 扣点...");
while (!cts.Token.IsCancellationRequested)
{
if (Console.KeyAvailable)
{
var key = Console.ReadKey(true).KeyChar;
if (key == 'q' || key == 'Q') break;
if (key == 'd' || key == 'D')
{
if (await client.DeductAsync(1, Guid.NewGuid().ToString()))
{
Console.WriteLine($"扣点成功,剩余: {client.Points}");
}
else
{
Console.WriteLine($"扣点失败: {client.LastError}");
}
}
}
await Task.Delay(100);
}
// 5. 退出
cts.Cancel();
await client.LogoutAsync(machineCode);
Console.WriteLine("已退出登录");
}
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>