什么是RSA?

RSA是一种非对称加密,通常用来加密、签名等场景。详细介绍可以参考

【深度知识】RSA加密、解密、签名、验签的原理及方法

RSA加密,签名,证书

简要介绍一下:

加密:私钥不公开,用于解密数据。公钥用于加密数据,加密后的数据只能被对应私钥界面,保证了加密后的数据安全性。

签名:私钥不公开,用于加密数据。公钥用于解密数据,由于只能解密对应私钥加密过的数据,防止了消息被篡改。

利用加密实现License校验

定义License类

确定验证信息包含软件名称、机器标识、有效期限

    public class LicenseInfo
    {
        public string AppName { get; set; }
        public string ExpireTime { get; set; }
        public string MachineID { get; set; }

        public static LicenseInfo FromString(string license)
        {
            if (string.IsNullOrEmpty(license))
            {
                return null;
            }
            LicenseInfo info = new LicenseInfo();
            var infos = license.Split(';');
            info.AppName = infos[0].Split(':')[1];
            info.ExpireTime = infos[1].Split(':')[1];
            info.MachineID = infos[2].Split(':')[1];
            return info;
        }
    }

获取当前计算机的硬件信息

硬件信息包含CPU标识、网卡地址

获取硬件信息

 private static string GetCpuID()
        {
            try
            {
                string cpuInfo = "";//cpu序列号
                ManagementClass mc = new ManagementClass("Win32_Processor");
                ManagementObjectCollection moc = mc.GetInstances();
                foreach (ManagementObject mo in moc)
                {
                    cpuInfo = mo.Properties["ProcessorId"].Value.ToString();
                }
                moc = null;
                mc = null;
                return cpuInfo;
            }
            catch
            {
                return "unknow";
            }

            finally
            { }
        }

        /// <summary>
        /// 获取网卡地址
        /// </summary>
        /// <returns>网卡地址</returns>
        private static string GetMacAddressNew()
        {
            const int MIN_MAC_ADDR_LENGTH = 12;
            string macAddress = string.Empty;
            long maxSpeed = -1;

            foreach (NetworkInterface nic in NetworkInterface.GetAllNetworkInterfaces())
            {
                string tempMac = nic.GetPhysicalAddress().ToString();
                if (nic.Speed > maxSpeed &&
                    !string.IsNullOrEmpty(tempMac) &&
                    tempMac.Length >= MIN_MAC_ADDR_LENGTH)
                {
                    maxSpeed = nic.Speed;
                    macAddress = tempMac;
                }
            }

            return macAddress;
        }

计算MD5作为标识符

        public static string GetLocalUniqueID()
        {
            if(!string.IsNullOrEmpty(localUniqueId))
            {
                return localUniqueId;
            }
            string id = APP_NAME;
            id = GetCpuID();
            id += GetMacAddressNew();
            
            // Use input string to calculate MD5 hash
            using (System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create())
            {
                byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(id);
                byte[] hashBytes = md5.ComputeHash(inputBytes);

                // Convert the byte array to hexadecimal string
                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < hashBytes.Length; i++)
                {
                    sb.Append(hashBytes[i].ToString("X2"));
                }
                localUniqueId = sb.ToString();
                return localUniqueId;
            } 
        }

获取到硬件信息的字符串后,将信息内容的字符串的MD5作为硬件唯一标识符。

生成License

生成时需要指定APP的名称,硬件标识符、公钥路径、以及有效天数。

public static void CreateLicense(string app_name, string uid, string publicKey, string licenseFile, int days = 3)
        {
            var now = DateTime.Now;
            var expire = now.AddDays(days);
            var expire_str = expire.ToString("yyyy-MM-dd");
            string publicContent = File.ReadAllText(publicKey);
            RSA rsa = new RSA(publicContent, true);
            LicenseInfo license = new MakeInstall.License.LicenseInfo();
            license.AppName = app_name;
            license.ExpireTime = expire_str;
            license.MachineID = uid;

            var info = LicToString(license);

            var license_pub = rsa.Encode(info);

            int len = license_pub.Length;
            int step = 25;
            int index = 0;
            StringBuilder sb = new StringBuilder("-------- license --------\n");
            while (index < len)
            {
                if (step + index > len)
                {
                    step = len - index;
                }
                sb.Append(license_pub.Substring(index, step));
                sb.Append("\n");
                index += step;
            }
            sb.Append("-------- license --------");            
            File.WriteAllText( $"{licenseFile}-{expire_str}.txt", sb.ToString());
        }

验证License

验证license的流程为:

  1. 获取本地硬件信息标识符

  2. 加载私钥文件解密license文件

  3. 判断license文件里面的硬件标识符是否与本地标识符一致

  4. 判断license文件里面的有效日期是否到期

任何一个步骤不通过则验证license失败则停止程序正常运行

        public static LicenseInfo LoadLicense(string privateContent, bool force=false)
        {
            if (license != null && !force)
            {
                return license;
            }
            license = null;
            RSA rsa = new RSA(privateContent, true);
            var licensePath = LicensePath;
            if (string.IsNullOrEmpty(licensePath))
            {
                return license;
            }
            var licenseContent = File.ReadAllText(licensePath);

            licenseContent= licenseContent.Replace("--------", "");
            licenseContent = licenseContent.Replace(" ", "");
            licenseContent = licenseContent.Replace("\n", "");
            licenseContent = licenseContent.Replace("license", "");

            var licenseInfo = rsa.DecodeOrNull(licenseContent);
            license = LicenseInfo.FromString(licenseInfo);
            return license; 
        }

因为验证license需要用到私钥文件解密数据,所以私钥文件一般与软件打包在一起发布给客户,公钥文件自己保密。

完整的RSA加解密代码

由于完整的rsa加解密代码比较长没有贴出来,上传到git仓库了 SharpRSA

公告
本博客基于TinyBlog搭建,关注公众号CoderThing