工控软件保卫战:WPF上位机授权方案解析
WPF这类本地部署的应用非常容易被复制传播,必要的授权机制可以很大程度上防止盗版和未授权的传播,从而保护开发者的合法权益。一个合格的WPF应用软件授权方案,应该具备:
- 完整性 – 授权文件不能被伪造或修改
- 唯一性 – 绑定硬件,保证一机一码
- 时效性 – 支持有效期、按功能授权
这里我们介绍一种解决方案,架构设计如下:
1.机器码生成 : 收集 CPU ID、主板序列号、硬盘序列号,组合Hash生成唯一机器码。
2.非对称加密 :
-
私钥 : 保存在开发者手中,用于生成授权文件。
-
公钥 : WPF客户端中,用于验证授权文件。
3.时间篡改检测:在离线环境下,要具备时间篡改检测功能,检测用户是否修改了系统时间。
4.授权方式: 使用Json格式,包含机器码、过期时间、功能标志等,需要对Json进行数字签名。
核心模块代码
1.机器码生成
这里要先安装“ System.Management” NuGet包,获取硬件信息需要用到。
public static class DeviceFingerprint{// 使用 Lazy 缓存机器码,避免频繁调用 WMI 导致程序卡顿private static readonly Lazy<string> _machineCode = new Lazy<string>(GenerateMachineCode);public static string Value => _machineCode.Value;privatestaticstringGenerateMachineCode(){string cpu = GetWmiInfo("Win32_Processor", "ProcessorId");string hdd = GetWmiInfo("Win32_DiskDrive", "SerialNumber");string board = GetWmiInfo("Win32_BaseBoard", "SerialNumber");string rawFingerprint = $"CPU>>{cpu}||HDD>>{hdd}||BOARD>>{board}";byte[] hash = MD5.HashData(Encoding.UTF8.GetBytes(rawFingerprint));return BitConverter.ToString(hash).Replace("-", "");}privatestaticstringGetWmiInfo(string table, string prop){using (var searcher = new ManagementObjectSearcher($"SELECT {prop} FROM {table}")){foreach (var obj in searcher.Get()){var val = obj[prop]?.ToString();// 排除空值和"To be filled by O.E"垃圾数据if (!string.IsNullOrWhiteSpace(val) && !val.Contains("O.E.M"))return val.Trim();}}throw new Exception($"从{table}获取WMI属性 {prop}失败!");}}
2.基于RSA的签名与验证
public static class SecurityGuard{// 生成密钥对:privateKey 开发者保存(不能公开),publicKey 硬编码到 WPF 客户端(可公开)publicstaticvoidGenerateKeys(outstring publicKey, outstring privateKey){using (var rsa = RSA.Create(2048)){privateKey = Convert.ToBase64String(rsa.ExportPkcs8PrivateKey());publicKey = Convert.ToBase64String(rsa.ExportSubjectPublicKeyInfo());}}// 用私钥签名数据 - 开发者使用publicstaticstringSignData(string data, string privateKey){using (var rsa = RSA.Create()){rsa.ImportPkcs8PrivateKey(Convert.FromBase64String(privateKey), out _);byte[] dataBytes = Encoding.UTF8.GetBytes(data);// 使用 SHA256 + Pkcs1 填充进行签名byte[] signature = rsa.SignData(dataBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);return Convert.ToBase64String(signature);}}// 用公钥验证签名 - 客户端使用publicstaticboolVerifyData(string data, string signature, string publicKey){try{using (var rsa = RSA.Create()){rsa.ImportSubjectPublicKeyInfo(Convert.FromBase64String(publicKey), out _);byte[] dataBytes = Encoding.UTF8.GetBytes(data);byte[] signBytes = Convert.FromBase64String(signature);return rsa.VerifyData(dataBytes, signBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);}}catch{return false;}}}
3.整合模块,提供统一接口
// 授权数据public class LicenseModel{public string MachineCode { get; set; } // 绑定的机器码public DateTime ExpireDate { get; set; } // 过期时间public string[] Features { get; set; } // 开启的功能模块}// 授权文件结构public class LicenseContainer{public string Payload { get; set; } // LicenseModel 的 JSON (明文,Base64格式)public string Signature { get; set; } // Payload 的 RSA 签名}public static class LicenseManager{// 替换为上面 GenerateKeys 生成的实际公钥private const string PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...";// 验证授权文件publicstaticboolValidate(string licenseFileContent, out LicenseModel? model){model = null;message = string.Empty;try{// 提取授权文件var container = JsonSerializer.Deserialize<LicenseContainer>(licenseFileContent);if (container == null)return false; //授权文件格式损坏// 用公钥验证签名bool isSignatureValid = SecurityGuard.VerifyData(container.Payload, container.Signature, PUBLIC_KEY);if (!isSignatureValid)return false;//授权文件被篡改// 提取授权文件内部数据string jsonPayload = Encoding.UTF8.GetString(Convert.FromBase64String(container.Payload));model = JsonSerializer.Deserialize<LicenseModel>(jsonPayload);// 验证授权硬件string currentMachine = DeviceFingerprint.Value;if (model?.MachineCode != currentMachine)return false;//硬件不匹配// 验证授权是否过期if (DateTime.Now > model.ExpireDate)return false;//授权过期return true;//授权有效}catch{return false;//验证过程发生异常}}}
4.时间篡改检测
防止“时间倒流”:C# 上位机中的时间篡改检测这篇文章中有完整的解决方案,这里不再提供。
5.WPF 客户端用法
这里假设在程序启动时进行授权检查,可根据实际需求更改检查时机。
public partial class App : Application{protectedoverridevoidOnStartup(StartupEventArgs e){base.OnStartup(e);//授权文件路径string licensePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "license.lic");bool isSuccess = false;if (File.Exists(licensePath)){string content = File.ReadAllText(licensePath);// 调用校验逻辑if (LicenseManager.Validate(content, outvar model))isSuccess = true;// 授权通过}if (!isSuccess){// 获取本机机器码,方便用户复制发给开发者string machineCode = DeviceFingerprint.Value;string prompt = $"验证失败: {failReason}\n\n本机机器码: {machineCode}\n\n请联系供应商获取有效授权文件。";// 也可以在这里弹出一个简单的注册窗口,让用户导入文件MessageBox.Show(prompt, "系统授权", MessageBoxButton.OK, MessageBoxImage.Error);Shutdown();}else{// 正常启动主窗口new MainWindow().Show();}}}
6.授权文件生成器
需要一个小工具用于给客户生成授权文件,这里使用控制台举例。
// 生成密钥对 (仅运行一次)// SecurityGuard.GenerateKeys(out string pub, out string pri);// Console.WriteLine("Private Key (存好!): " + pri);// Console.WriteLine("Public Key (放入WPF代码): " + pub);// ----------------------------------------------------// 导入私钥 (从安全的地方读取)string privateKey = "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoI...";// 填写授权信息var model = new LicenseModel{MachineCode = "E32F5A1189B2CC01", // 客户发来的机器码ExpireDate = new DateTime(2026, 12, 31),//有效时间Features = new[] { "MotionControl", "VisionInspection" }//需要解锁的功能};// 使用私钥进行签名string jsonPayload = JsonSerializer.Serialize(model);string payloadBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(jsonPayload));string signature = SecurityGuard.SignData(payloadBase64, privateKey);// 生成授权文件var container = new LicenseContainer{Payload = payloadBase64,Signature = signature};string finalFileParams = JsonSerializer.Serialize(container);File.WriteAllText("license.lic", finalFileParams);Console.WriteLine("授权文件 license.lic 生成成功!");

详解工业自动化中的NTP时间同步:从IPC到PLC的全链路打通

夜雨聆风
