许多软件都有正版验证功能,它们通常需要联网验证,验证的本质则是加密与解密,本文将使用RSA加密算法实现简易的离线验证功能
RSA加密
设p,q是两个超级大的素数,N=pq,数据通过欧拉函数φ(N)来加密,而欧拉函数φ(N)满足φ(N)=φ(pq)=(p−1)(q−1),如果要算出φ(N),就必须知道p和q.并且这有一点好处,某人可以公开宣布他的密钥e以及N,任何人都可以给他发送密文,但是只有他能看懂.因为想要破解密码,就必须知道那两个超级大素数p,q.
ae≡b(mod N)
p和q的位数决定了密文的安全性.
硬件绑定
由于是离线验证,所以我们能很容易想到可以根据电脑的硬件信息生成一串加密字符串,程序根据加密字符串解密出硬件信息,如果解密出来的信息与实际电脑配置信息相同,则认为验证通过.同时我们还可以在字符串上添加一些额外信息,例如有效期,这样就能做到限定时间的功能.
这里以BIOS为例,使用ManagementClass
和ManagementObjectCollection
读取硬件信息,注意这两个类的命名空间为System.Management
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| ManagementClass mc = null; ManagementObjectCollection moc = null; try { mc = new ManagementClass("Win32_BIOS"); moc = mc.GetInstances(); foreach (ManagementObject mo in moc) { bios = mo.Properties["SerialNumber"].Value.ToString(); break; } } catch(Exception ex) { bios = null; } finally { if (mc != null) mc.Dispose(); if (moc != null) moc.Dispose(); }
|
程序读取了BIOS编号,并保存在bios字符串中
现在根据BIOS编号生成密文,C#提供了RSACryptoServiceProvider
来帮助加密,注意这个类的命名空间为System.Security.Cryptography
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public static string Encrypt(string content) { string publickey = @"<RSAKeyValue><Modulus>5m9m14XH3oqLJ8bNGw9e4rGpXpcktv9MSkHSVFVMjHbfv+SJ5v0ubqQxa5YjLN4vc49z7SVju8s0X4gZ6AzZTn06jzWOgyPRV54Q4I0DCYadWW4Ze3e+BOtwgVU1Og3qHKn8vygoj40J6U85Z/PTJu3hN1m75Zr195ju7g9v4Hk=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>"; RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); byte[] cipherbytes; rsa.FromXmlString(publickey); cipherbytes = rsa.Encrypt(Encoding.UTF8.GetBytes(content), false); return Convert.ToBase64String(cipherbytes); } public static string Decrypt(string content) { string privatekey = @"<RSAKeyValue><Modulus>5m9m14XH3oqLJ8bNGw9e4rGpXpcktv9MSkHSVFVMjHbfv+SJ5v0ubqQxa5YjLN4vc49z7SVju8s0X4gZ6AzZTn06jzWOgyPRV54Q4I0DCYadWW4Ze3e+BOtwgVU1Og3qHKn8vygoj40J6U85Z/PTJu3hN1m75Zr195ju7g9v4Hk=</Modulus><Exponent>AQAB</Exponent><P>/hf2dnK7rNfl3lbqghWcpFdu778hUpIEBixCDL5WiBtpkZdpSw90aERmHJYaW2RGvGRi6zSftLh00KHsPcNUMw==</P><Q>6Cn/jOLrPapDTEp1Fkq+uz++1Do0eeX7HYqi9rY29CqShzCeI7LEYOoSwYuAJ3xA/DuCdQENPSoJ9KFbO4Wsow==</Q><DP>ga1rHIJro8e/yhxjrKYo/nqc5ICQGhrpMNlPkD9n3CjZVPOISkWF7FzUHEzDANeJfkZhcZa21z24aG3rKo5Qnw==</DP><DQ>MNGsCB8rYlMsRZ2ek2pyQwO7h/sZT8y5ilO9wu08Dwnot/7UMiOEQfDWstY3w5XQQHnvC9WFyCfP4h4QBissyw==</DQ><InverseQ>EG02S7SADhH1EVT9DD0Z62Y0uY7gIYvxX/uq+IzKSCwB8M2G7Qv9xgZQaQlLpCaeKbux3Y59hHM+KpamGL19Kg==</InverseQ><D>vmaYHEbPAgOJvaEXQl+t8DQKFT1fudEysTy31LTyXjGu6XiltXXHUuZaa2IPyHgBz0Nd7znwsW/S44iql0Fen1kzKioEL3svANui63O3o5xdDeExVM6zOf1wUUh/oldovPweChyoAdMtUzgvCbJk1sYDJf++Nr0FeNW1RB1XG30=</D></RSAKeyValue>"; RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); byte[] cipherbytes; rsa.FromXmlString(privatekey); cipherbytes = rsa.Decrypt(Convert.FromBase64String(content), false); return Encoding.UTF8.GetString(cipherbytes); }
|
两部分核心代码已经完成,在程序中先获取BIOS编号,然后提示用户输入许可证,这个许可证实际上就是密文,程序解密密文,这里需要注意的是,如果用户输入的密文格式有误,则解密函数会抛出异常,因此需要套上try来执行,如果发生异常,则一律认为验证失败.以下是程序界面
文件读写
验证模块已经完成,但是每次打开都要用户手动输入许可证,及其繁琐,因此我们需要将许可证保存在本地.
首先在D盘创建ducuments目录,在document里创建LICENSE文件,注意需引入命名空间System.IO
定义地址
1 2 3
| private static string dirname = @"D:\documents"; private static string filename = "LICENSE.dx"; private static string path = System.IO.Path.Combine(dirname, filename);
|
创建目录和文件,这里需注意Create后必须dispose,否则接下来读取时会提示被占用
1 2 3 4 5 6
| System.IO.Directory.CreateDirectory(dirname); if (!File.Exists(path)) { FileStream fileStream = System.IO.File.Create(path); fileStream.Dispose(); }
|
读取和写入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| public static string ReadFile() { string key = ""; string line; StreamReader sr = null; try { sr = new StreamReader(path); while ((line = sr.ReadLine()) != null) { key += line; } } catch(Exception ex) { key = null; } finally { if(sr != null) { sr.Dispose(); } } return key; } public static void WriteFile(string key) { StreamWriter sw = null; try { sw = new StreamWriter(path); sw.Write(key); } catch (Exception ex) { } finally { if (sw != null) { sw.Dispose(); } } }
|
StreamReader
和StreamWriter
可以放在using
里面执行,这样就会自动销毁,但是为了显示dispose
的重要性,本程序中手动dispose
StreamReader
将许可证以文本形式写入文件里,而读取也是文本形式读取,所以文件的后缀名可以随便取.