通用软件注册功能之建立有效的软件保护机制
众所周知,一些共享软件往往提供给使用者的是一个功能不受限制的限时使用版,在试用期内使用者可以无限制的使用软件的全部功能(只是可能会出现提示使用者注册的窗口),试用期一过部分(或全部)功能失效,要想继续使用只能向作者索取注册码(或注册文件)完成对软件的合法注册,注册后的软件将解除一切使用限制。如果您也开发出一个有价值的作品,是否也希望为自己的软件增加一个这样的功能呢?当前对于.NET反编译的问题不在本文讨论之内,相关文章已经很多!本文我们就一起探讨软件注册功能的实现。
实现软件的注册功能方法很多,最需要考虑的就是不能轻易的让使用者破解,在这里,我就谈谈“.NET快速开发整合框架(RDIFramework.NET)”中平台注册功能的实现方法。在RDIFramework.NET中,注册功能主要方法就是对计算机唯一硬件信息进行RSA数字签名达到软件注册和保护的功能,该方法实现简单,安全性相应较高。
计算机唯一硬件信息(我们知道计算机中的关键部件如CPU,主板等在全球范围内都有一个独一无二的产品序列号,用户通过注册模块获取这些产品序列号(即传统所说的:机器吗)并将它发送给软件开发商要求进行RSA数据签名,软件开发商获得这些机器码后利用手中的私钥对这些信息进行RSA数字签名,生成的签名信息(即注册码)发回给用户,用户将收到的注册码输入注册模块的注册码框,软件即可利用公钥执行签名验证,如果输入的注册码被证明就是经过开发商数字签名的机器码,则完成注册过程。
注册功能项目结构图如下所示:
图1 注册功能项目结构
平台服务端注册码生成主界面如下所示:
图2 注册文件管理器
通过“注册文件管理器”,我们就可以根据用户提供的信息来生成软件的注册文件。
客户端的注册主要就是根据我们提供的注册文件与公钥,来验证注册文件是否为当前客户的有效注册文件,如果有效,注册成功,无效则注册失败!客户端注册功能设计参考如下所示:
图3 平台注册
用户单击“注册”按钮,成功注册提示:
图4注册成功
服务端注册码生成核心代码:
一、 生成公/私钥文件:
1 private void btnGenerateKey_Click(object sender, EventArgs e)
2 {
3 if (MessageBox.Show("确定生成生成公/私钥对吗(是/否)?", "询问信息", MessageBoxButtons.OKCancel, MessageBoxIcon.Question)
4 == System.Windows.Forms.DialogResult.Cancel)
5 {
6 return;
7 }
8
9 RSACryptoServiceProvider crypt = new RSACryptoServiceProvider();
10
11 string publicKey = crypt.ToXmlString(true);
12 string privateKey = crypt.ToXmlString(false);
13 crypt.Clear();
14
15 //生成公钥
16 using (StreamWriter sw = new StreamWriter(KeyPath + "RDIFrameworkkey.key", false, UTF8Encoding.UTF8))
17 {
18 sw.Write(SecretHelper.AESEncrypt(publicKey));
19 sw.Flush();
20 }
21
22 //生成私钥
23 using (StreamWriter sw = new StreamWriter(KeyPath + "RDIFrameworkPrivateKey.key", false, UTF8Encoding.UTF8))
24 {
25 sw.Write(SecretHelper.AESEncrypt(privateKey));
26 sw.Flush();
27 }
28
29 MessageBox.Show("成功生成公/私钥对!","提示信息",MessageBoxButtons.OK,MessageBoxIcon.Information);
30 }
二、 生成注册文件:
1 private void btnGenerateRegisterFile_Click(object sender, EventArgs e)
2 {
3 if (string.IsNullOrEmpty(txtUserEmail.Text.Trim()))
4 {
5 MessageBox.Show("用户邮箱不能为空!", "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Warning);
6 txtUserEmail.Focus();
7 return;
8 }
9 else
10 {
11 if (!RegexValidatorHelper.IsMatch(txtUserEmail.Text.Trim(), Pattern.EMAIL))
12 {
13 MessageBox.Show("邮箱格式不正确!", "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Warning);
14 txtUserEmail.SelectAll();
15 return;
16 }
17 }
18
19 if (string.IsNullOrEmpty(txtCPUSerialNo.Text.Trim()))
20 {
21 MessageBox.Show("CPU序列号不能为空!", "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Warning);
22 return;
23 }
24
25 if (!string.IsNullOrEmpty(txtUseLimited.Text.Trim()))
26 {
27 if (!RegexValidatorHelper.IsMatch(txtUseLimited.Text.Trim(), Pattern.INTEGER))
28 {
29 MessageBox.Show("使用次数应该为数值型!", "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Warning);
30 txtUseLimited.SelectAll();
31 return;
32 }
33 }
34
35 //读取私钥
36 StreamReader sr = new StreamReader(KeyPath + "RDIFrameworkPrivateKey.key", UTF8Encoding.UTF8);
37 string keypair = sr.ReadToEnd();
38 sr.Close();
39
40 //用私钥参数初始化RSACryptoServiceProvider类的实例crypt。
41 RSACryptoServiceProvider crypt = new RSACryptoServiceProvider();
42
43 crypt.FromXmlString(SecretHelper.AESDecrypt(keypair));
44
45 UTF8Encoding enc = new UTF8Encoding();
46
47 string trialTime = "30";//试用次数(默认:30数,0:表示永久)
48 if (!string.IsNullOrEmpty(txtUseLimited.Text.Trim()))
49 {
50 trialTime = txtUseLimited.Text.Trim();
51 }
52 string regInfo = txtUserEmail.Text.Trim() + ";" + txtMAC.Text.Trim() + ";" + txtCPUSerialNo.Text.Trim() + ";" + trialTime;
53
54 byte[] bytes = enc.GetBytes(regInfo);//格式:邮箱地址;MAC;CPU序列号;试用时间
55 //对用户信息加密
56 bytes = crypt.Encrypt(bytes, false);
57
58 //生成注册数据,对二进制字节进行Base64编码,但采用注册文件的形式的进修也可以不做此转化。
59 string encrytText = System.Convert.ToBase64String(bytes, 0, bytes.Length);
60
61 //将注册码写入文件
62 using (StreamWriter sw = new StreamWriter(KeyPath + "RDIFramework_reg_file.lic", false, UTF8Encoding.UTF8))
63 {
64 sw.Write(encrytText);
65 sw.Flush();
66 }
67
68 MessageBox.Show("注册文件:RDIFramework_reg_file.lic生成成功!", "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
69 }
三、 验证注册文件:
1 private void btnCheckRegistr_Click(object sender, EventArgs e)
2 {
3 //读取注册数据文件
4 StreamReader sr = new StreamReader(KeyPath + "RDIFramework_reg_file.lic", UTF8Encoding.UTF8);
5 string encrytText = sr.ReadToEnd();
6 sr.Close();
7
8
9 //读取公钥
10 StreamReader srPublickey = new StreamReader(KeyPath + "RDIFrameworkkey.key", UTF8Encoding.UTF8);
11 string publicKey = srPublickey.ReadToEnd();
12 srPublickey.Close();
13
14 //用公钥初化始RSACryptoServiceProvider类实例crypt。
15 RSACryptoServiceProvider crypt = new RSACryptoServiceProvider();
16 crypt.FromXmlString(SecretHelper.AESDecrypt(publicKey));
17 UTF8Encoding enc = new UTF8Encoding();
18 byte[] decryptByte;
19 try
20 {
21 byte[] newBytes;
22 newBytes = System.Convert.FromBase64CharArray(encrytText.ToCharArray(), 0, encrytText.Length);
23 decryptByte = crypt.Decrypt(newBytes, false);
24 string decrypttext = enc.GetString(decryptByte);
25 //
26 //TODO:在此处添加验证逻辑
27 //
28 MessageBox.Show(decrypttext);
29 }
30 catch(Exception ex)
31 {
32 MessageBox.Show(ex.Message);
33 }
34 }
至此,软件的注册功能就完成了,当然还有其他很多方法,比如:
一、 采用加密狗的方式(最安全的方式)。
二、 在线验证注册信息(用户需能上网),这种方式也比较可靠。
三、 其他方法,欢迎大家讨论。
四、 ......
作者: EricHu
关于作者:高级工程师、信息系统项目管理师、DBA。专注于微软平台项目架构、管理和企业解决方案,多年项目开发与管理经验,曾多次组织并开发多个大型项目,精通DotNet,DB(SqlServer、Oracle等)技术。熟悉Java、Delhpi及Linux操作系统,有扎实的网络知识。在面向对象、面向服务以及数据库领域有一定的造诣。现从事DB管理与开发、WinForm、WCF、WebService、网页数据抓取以及ASP.NET等项目管理、开发、架构等工作。
如有问题或建议,请多多赐教!
本文版权归作者有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,如有问题,可以通过邮箱或QQ 联系我,非常感谢。