using System; using System.Collections.Generic; using System.Globalization; using System.Net; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Web.Script.Serialization; namespace AyueSdk { /// /// 阿越云验证 - C# SDK /// /// 支持:卡密登录、心跳保活、公告获取、版本检查(含强制更新)、 /// 卡密解绑、获取应用配置、获取外链文件、应用账号登录 /// /// 环境:.NET Framework 4.5+ / .NET Core 3.1+ / .NET 5+ /// 依赖:System.Web.Extensions(.NET Framework)或 System.Text.Json(.NET Core,见注释) /// /// @version 1.0.0 /// public class AyueVerify { private readonly string _baseUrl; private readonly int _appId; private readonly string _appKey; private readonly string _rc4Key; private readonly int _miType; private readonly int _miTime; private readonly bool _signEnabled; private readonly int _timeoutMs; public string LastError { get; private set; } = ""; public int LastCode { get; private set; } = 0; /// /// 初始化 /// /// 验证服务器地址,如 https://yz.example.com /// 应用 ID /// 应用密钥 /// 通信密钥 /// 加密类型 0=明文 1=RC4 2=Base64 /// 是否签名 /// 超时毫秒 public AyueVerify(string baseUrl, int appId, string appKey = "", string rc4Key = "", int miType = 0, bool sign = false, int timeoutMs = 10000, int miTime = 10) { _baseUrl = (baseUrl ?? "").TrimEnd('/') + "/"; _appId = appId; _appKey = appKey ?? ""; _rc4Key = rc4Key ?? ""; _miType = miType; _miTime = miTime; _signEnabled = sign; _timeoutMs = timeoutMs > 0 ? timeoutMs : 10000; } // ==================== 业务接口 ==================== /// 卡密登录 public Result Login(string kami, string markcode) { var p = new Dictionary { { "kami", kami }, { "markcode", markcode } }; return Normalize(Request("kmlogon", p), true); } /// 心跳保活 public Result Heartbeat(string kami, string markcode) { var p = new Dictionary { { "kami", kami }, { "markcode", markcode } }; return Normalize(Request("heartbeat", p), true); } /// 退出心跳 public Result HeartbeatQuit(string kami) => Heartbeat(kami, "quit"); /// 获取公告 public string Notice() { var r = Request("notice", new Dictionary()); if (CodeOf(r) == 200 && r.ContainsKey("msg") && r["msg"] is Dictionary d) { return d.ContainsKey("app_gg") && d["app_gg"] != null ? d["app_gg"].ToString() : ""; } return ""; } /// 获取应用配置 public Config GetConfig(bool withPay = false) { var p = new Dictionary(); if (withPay) p["pay"] = "1"; var r = Request("ini", p); var c = new Config { Ok = CodeOf(r) == 200, Code = LastCode }; if (c.Ok && r.ContainsKey("msg") && r["msg"] is Dictionary d) { c.Version = Str(d, "version"); c.VersionInfo = Str(d, "version_info"); c.UpdateUrl = Str(d, "app_update_url"); c.UpdateShow = Str(d, "app_update_show"); c.MustUpdate = Str(d, "app_update_must") == "y"; } else { c.Msg = LastError; } return c; } /// 检查更新 public UpdateInfo CheckUpdate(string currentVersion) { var cfg = GetConfig(); var u = new UpdateInfo(); if (!cfg.Ok) { u.Msg = cfg.Msg; return u; } u.Ok = true; u.HasUpdate = VersionCompare(cfg.Version, currentVersion) > 0; u.MustUpdate = u.HasUpdate && cfg.MustUpdate; u.Latest = cfg.Version; u.Url = cfg.UpdateUrl; u.Desc = string.IsNullOrEmpty(cfg.UpdateShow) ? cfg.VersionInfo : cfg.UpdateShow; return u; } /// 卡密解绑 public Result Unbind(string kami, string markcode) { var p = new Dictionary { { "kami", kami }, { "markcode", markcode } }; return Normalize(Request("kmunmachine", p), false); } /// 获取外链文件(原始 JSON) public string GetFilesRaw() { return RequestRaw("getfile", new Dictionary()); } /// 应用账号登录 public Result UserLogin(string user, string password) { var p = new Dictionary { { "user", user }, { "password", password } }; return Normalize(Request("userlogon", p), false); } /// 应用账号注册 public Result UserRegister(string user, string password) { var p = new Dictionary { { "user", user }, { "password", password } }; return Normalize(Request("userreg", p), false); } /// 应用账号心跳 public Result UserHeartbeat(string user, string markcode) { var p = new Dictionary { { "user", user }, { "markcode", markcode } }; return Normalize(Request("userheartbeat", p), false); } // ==================== 底层通信 ==================== private string RequestRaw(string api, Dictionary fields) { LastError = ""; LastCode = 0; string value = RandomStr(16); fields["value"] = value; // 关键:服务端 mi_time > 0 时强制要求 t 参数,缺失返回 108 if (_miTime > 0 && !fields.ContainsKey("t")) { fields["t"] = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(); } string url = _baseUrl + "api.php?api=" + Uri.EscapeDataString(api) + "&app=" + _appId; var post = new Dictionary(); if (_miType == 0) { foreach (var kv in fields) post[kv.Key] = kv.Value; } else { string plain = BuildQuery(fields); post["data"] = Encrypt(plain); post["value"] = value; } if (_signEnabled) { var src = new Dictionary(fields); if (_miType != 0) src["data"] = post["data"]; post["sign"] = MakeSign(src); } var sb = new StringBuilder(); foreach (var kv in post) { if (sb.Length > 0) sb.Append('&'); sb.Append(Uri.EscapeDataString(kv.Key)).Append('=').Append(Uri.EscapeDataString(kv.Value ?? "")); } byte[] body = Encoding.UTF8.GetBytes(sb.ToString()); try { ServicePointManager.ServerCertificateValidationCallback = delegate { return true; }; var req = (HttpWebRequest)WebRequest.Create(url); req.Method = "POST"; req.ContentType = "application/x-www-form-urlencoded"; req.UserAgent = "AyueVerify-CSharp-SDK/1.0.0"; req.Timeout = _timeoutMs; req.ReadWriteTimeout = _timeoutMs; req.ContentLength = body.Length; using (var s = req.GetRequestStream()) s.Write(body, 0, body.Length); using (var resp = (HttpWebResponse)req.GetResponse()) using (var rs = resp.GetResponseStream()) using (var reader = new System.IO.StreamReader(rs, Encoding.UTF8)) { return reader.ReadToEnd(); } } catch (Exception ex) { LastError = "网络请求失败: " + ex.Message; LastCode = -1; return "{\"code\":-1,\"msg\":\"网络请求失败\"}"; } } private Dictionary Request(string api, Dictionary fields) { string raw = RequestRaw(api, fields); try { var ser = new JavaScriptSerializer(); // 去除 UTF-8 BOM raw = raw?.TrimStart('\uFEFF')?.Trim() ?? ""; var obj = ser.Deserialize>(raw); if (obj == null) throw new Exception("empty"); LastCode = CodeOf(obj); if (LastCode != 200 && obj.ContainsKey("msg")) { LastError = obj["msg"] == null ? "" : obj["msg"].ToString(); } return obj; } catch (Exception e) { LastError = "返回格式错误: " + (raw.Length > 120 ? raw.Substring(0, 120) : raw); LastCode = -1; return new Dictionary { { "code", -1 }, { "msg", LastError } }; } } private Result Normalize(Dictionary r, bool wantExpires) { var res = new Result { Code = CodeOf(r), Msg = r.ContainsKey("msg") && r["msg"] != null ? r["msg"].ToString() : "" }; res.Ok = res.Code == 200; if (r.ContainsKey("msg") && r["msg"] is Dictionary d) { res.Data = d; if (wantExpires && res.Ok && d.ContainsKey("expires_ts") && d["expires_ts"] != null) { long.TryParse(d["expires_ts"].ToString(), out long ts); res.ExpiresTs = ts; } } return res; } private static int CodeOf(Dictionary r) { if (r == null || !r.ContainsKey("code") || r["code"] == null) return -1; return int.TryParse(r["code"].ToString(), out int c) ? c : -1; } private static string Str(Dictionary d, string key) { return d.ContainsKey(key) && d[key] != null ? d[key].ToString() : ""; } private string BuildQuery(Dictionary f) { var sb = new StringBuilder(); foreach (var kv in f) { if (sb.Length > 0) sb.Append('&'); sb.Append(kv.Key).Append('=').Append(kv.Value); } return sb.ToString(); } private string MakeSign(Dictionary fields) { var ignore = new HashSet { "sign", "app", "api", "value", "PHPSESSID", "sec_defend", "sidenav-state" }; var sb = new StringBuilder(); foreach (var kv in fields) { if (ignore.Contains(kv.Key)) continue; sb.Append(kv.Key).Append('=').Append(kv.Value).Append('&'); } sb.Append(_appKey); return Md5(sb.ToString()); } private string Encrypt(string plain) { if (_miType == 1) { // 关键:服务端 mi_rc4 会先做 UTF-8 -> GBK 转换,客户端必须保持一致。 string d = plain, k = _rc4Key; try { var gbk = Encoding.GetEncoding("GBK"); d = Encoding.GetEncoding("ISO-8859-1").GetString(gbk.GetBytes(plain)); k = Encoding.GetEncoding("ISO-8859-1").GetString(gbk.GetBytes(_rc4Key)); } catch (Exception) { // 目标框架未注册 GBK 时(.NET Core 需注册 CodePagesEncodingProvider) // 退化为原串,含中文参数可能与服务端不一致 } return Rc4Hex(d, k); } return Convert.ToBase64String(Encoding.UTF8.GetBytes(plain)); } private static string Rc4Hex(string data, string key) { if (string.IsNullOrEmpty(key)) return ""; var s = new int[256]; for (int i = 0; i < 256; i++) s[i] = i; int j = 0, kl = key.Length; for (int i = 0; i < 256; i++) { j = (j + s[i] + key[i % kl]) % 256; int t = s[i]; s[i] = s[j]; s[j] = t; } int x = 0, y = 0; var sb = new StringBuilder(); foreach (char ch in data) { x = (x + 1) % 256; y = (y + s[x]) % 256; int t = s[x]; s[x] = s[y]; s[y] = t; int c = (ch ^ s[(s[x] + s[y]) % 256]) & 0xFF; sb.Append(c.ToString("x2")); } return sb.ToString(); } private static string Md5(string s) { using (var md5 = MD5.Create()) { byte[] d = md5.ComputeHash(Encoding.UTF8.GetBytes(s)); var sb = new StringBuilder(); foreach (byte b in d) sb.Append(b.ToString("x2")); return sb.ToString(); } } private static string RandomStr(int n) { const string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; var rnd = new Random(Guid.NewGuid().GetHashCode()); var sb = new StringBuilder(); for (int i = 0; i < n; i++) sb.Append(chars[rnd.Next(chars.Length)]); return sb.ToString(); } /// 版本比较:v1 > v2 返回 1 public static int VersionCompare(string v1, string v2) { var a = (v1 ?? "").Trim().Split(new[] { '.', '-', '+' }); var b = (v2 ?? "").Trim().Split(new[] { '.', '-', '+' }); int n = Math.Max(a.Length, b.Length); for (int i = 0; i < n; i++) { string x = i < a.Length ? a[i] : "0"; string y = i < b.Length ? b[i] : "0"; if (int.TryParse(x, out int xi) && int.TryParse(y, out int yi)) { if (xi > yi) return 1; if (xi < yi) return -1; } else { int c = string.CompareOrdinal(x, y); if (c > 0) return 1; if (c < 0) return -1; } } return 0; } /// 生成设备码(首次运行应持久化) public static string GenerateMarkcode() { string seed = Environment.MachineName + "|" + Environment.OSVersion + "|" + Environment.ProcessorCount + "|" + Environment.UserName; return Md5(seed).Substring(0, 24).ToUpperInvariant(); } // ==================== 数据结构 ==================== public class Result { public bool Ok; public int Code; public string Msg = ""; public Dictionary Data; public long ExpiresTs; } public class Config { public bool Ok; public int Code; public string Msg = ""; public string Version = ""; public string VersionInfo = ""; public string UpdateUrl = ""; public string UpdateShow = ""; public bool MustUpdate; } public class UpdateInfo { public bool Ok; public bool HasUpdate; public bool MustUpdate; public string Latest = ""; public string Url = ""; public string Desc = ""; public string Msg = ""; } } }