因为有些客户尾款到账不及时,因此研究了一套授权系统,当授权到期后,系统就提示软件授权已到期,不能继续使用云云,这样方便尾款的收回。
Unity InstallPath/Editor/Data/MonoBleedingEdge/lib/mono/2.0-api/System.Management.dll
文件引入Unity工程,也会报“未实现”的错。所以最终,我的解决方案是写了一个额外的控制台程序,在这个控制台程序中读取硬件信息,然后输出,在Unity中调用这个控制台程序,在后台去获取硬件信息。这就又引发了一系列其他的问题,比如:假设客户知道了原理,如果他伪造一个读取硬件信息的程序,当我unity去调用时,他就可以返回给我假的硬件特征码,比如无论什么机器都返回一个固定的值,这样他就可以只用一台机器的授权,好几台机器共用了。所以,我Unity在调用之前,必须判断好这个读取硬件特征码的软件,是我的软件,而不是他伪造的。还有一个问题,在unity中,调用的时候,必须是后台调用,不能出现任何界面。MemoryMappedFile
,它虽然叫做File,但其实它只需要在内存中,实际并不需要磁盘IO),这个内存映射文件,是可以跨进程共享数据的。读硬件特征码的程序,读到硬件信息后,加密后写到这个内容映射文件中,而不是在控制台输出,这样,这个读取硬件特征的软件,客户就很难替换,因为:①客户不知道内存映射文件对象的名称;②客户不知道加密方式,或者说即便知道加密方式,不知道加密的key,无法向内存映射文件中写如正确数值。这样就避免了客户自行替换读硬件信息的程序。// 读取机器码的协程
private static IEnumerator ReadMachineCode()
{
// 启动读取硬件特征码的进程
string exePath = Path.Combine(Application.streamingAssetsPath, "MachineCode/GetMachineCode.exe");
if (!File.Exists(exePath))
{
OnMachineCodeReaded?.Invoke(null);
yield break;
}
Process process = new Process();
process.StartInfo.FileName = exePath;
process.StartInfo.Arguments = "Your Some Arguments"; // 据实际情况写
process.StartInfo.UseShellExecute = false;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
process.StartInfo.WorkingDirectory = Path.Combine(Application.streamingAssetsPath, "MachineCode");
process.StartInfo.RedirectStandardOutput = true;
process.Start();
// 等待进程结束
while (!process.HasExited)
yield return null;
// 获取进程运行结果,以控制台为例,而不是内存文件映射,生产环境应读取内存文件映射中的结果
string result = process.StandardOutput.ReadToEnd();
OnMachineCodeReaded?.Invoke(result);
}
[Serializable]
internal class MachineLicense
{
public string code; // 目标机器码
public string desc; // 说明
public int year; // 授权到期年
public int month; // 授权到期月
public int day; // 授权到期日
}
[Serializable]
internal class LicenseData
{
public string projectName; // 项目名称
public string description; // 说明
public string check; // 校验字符串
public List<MachineLicense> data; // 授权数据
}
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
public static class AESCryptography
{
// AES加密
public static byte[] Encrypt(byte[] rgbKey, byte[] rgbIV, string sourceText)
{
using MemoryStream memoryStream = new MemoryStream();
using (Aes aes = Aes.Create())
using (ICryptoTransform transform = aes.CreateEncryptor(rgbKey, rgbIV))
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, transform, CryptoStreamMode.Write))
using (StreamWriter streamWriter = new StreamWriter(cryptoStream))
{
streamWriter.Write(sourceText);
streamWriter.Flush();
}
return memoryStream.ToArray();
}
// AES解密
public static string Decrypt(byte[] rgbKey, byte[] rgbIV, byte[] cipherBuffer)
{
using MemoryStream stream = new MemoryStream(cipherBuffer);
using Aes aes = Aes.Create();
using ICryptoTransform transform = aes.CreateDecryptor(rgbKey, rgbIV);
using CryptoStream cryptoStream = new CryptoStream(stream, transform, CryptoStreamMode.Read);
using StreamReader streamReader = new StreamReader(cryptoStream);
return streamReader.ReadToEnd();
}
// 字符串MD5加密
public static byte[] Md5Encrypt(string tex)
{
var md5 = MD5.Create();
return md5.ComputeHash(Encoding.UTF8.GetBytes(tex));
}
// 将byte[]数据输出为HEX字符串
public static string ByteArrayToString(byte [] data)
{
StringBuilder sb = new StringBuilder();
int index = 0;
foreach( var d in data )
{
if (index > 0 && index % 4 == 0)
sb.Append('-');
sb.Append(d.ToString("X2"));
++index;
}
return sb.ToString();
}
// 将HEX字符串转换为byte[16]
public static bool HexStringToByte16(string hex, out byte[] data)
{
data = new byte[16];
int index = 0;
bool half = true;
foreach (var ch in hex)
{
byte d;
switch (ch)
{
case >= '0' and <= '9':
d = (byte)(ch - '0');
break;
case >= 'A' and <= 'F':
d = (byte)(10 + (ch - 'A'));
break;
case >= 'a' and <= 'f':
d = (byte)(10 + (ch - 'a'));
break;
case ' ':
case '-':
case ':':
continue;
default:
return false;
}
if (half)
data[index] = (byte) (d << 4);
else
{
data[index] = (byte) ( data[index] | d );
++index;
if (index > 15)
break;
}
half = !half;
}
return true;
}
}
LicneseData
数据,如果校验码等信息全部通过后,再判定当前设备机器码是否在授权文件中,并且授权日期是否到期。完成授权验证。foreach (var date in from target in licenseData.data where string.CompareOrdinal(target.code, MachineCode) == 0 select new DateTime(target.year, target.month, target.day).AddDays(1))
{
OnLicenseChecked?.Invoke(DateTime.Now < date);
yield break;
}
OnLicenseChecked?.Invoke(false);