https://blog.csdn.net/Sagittarius_Warrior/article/details/53501282
本系列文章主要介绍我近期设计的一个软件License系统。
一、软件需求
假设M公司要发布一款软硬件一体的产品,名为“OfficeDevice”。这个OfficeDevice的上层是一个PC,下层带一些嵌入式设备,而PC上运行一个叫“Office Kits”的软件,这个软件包含三个功能模块:Word、PPT和Excel。
现在要设计一个License系统,需满足以下两点需求:
1,机器绑定
Office Kits在启动后,需要验证License,这个License的某个信息字段要与机器的序列号匹配,如果不匹配,则判License无效。机器的序列号可以是主板编号,当然,OfficeDevice的序列号,来自与它的某个嵌入式设备。
2,模块权限
M公司需要将Word、PPT和Excel三个模块分开卖,License的某个信息字段要包含模块权限的信息,Office Kits在启动后,需要验证这个信息,验证后只激活符合权限的模块。
3,试用期
M公司会发布试用License,试用期一到,License失效。Office Kits的Word、PPT和Excel三个模块不能工作。
二、产品设计
从上面的软件需求可知,这个License系统可以分成两个部分,一个是License文件生成软件,一个是解密模块。
1,License Generator
License Generator提供一GUI,根据M公司的配置,生成一个License文件。界面设计如下:
如上图,左边是配置项,右边第一组控件,主要是预览待加密的字符串,第二组控件执行加密过程,并生成加密文件,文件名是“序列号+后缀”,第三组控件,主要是对比,看解密后的字符串是否与明文一致。
注:关于序列号,有很多产品直接选用主机的硬盘序列号。这里我通过google搜到了一个不错的获取硬盘序列号的源码,分享如下:
http://blog.163.com/jinfd@126/blog/static/6233227720133218314327/
这个需要在管理员模式下才能读硬盘序列号。
http://www.cnblogs.com/xiongpq/p/3953694.html
这个在非管理员模式下也可以读硬盘序列号。
2,解密模块
解密模块我们以导出库的形式,嵌入到Office Kits软件中,Office Kits软件启动后,会加License文件读入,并解密成字符串,然后校验相应的信息字段。最后,根据校验结果,关闭或激活相应的功能。
解密模块最好以“.lib”静态库的方式嵌入,在Office Kits软件目录下,并不能直接看到解密模块文件,这样更加难以破解。不过,我后面设计的还是采用“.DLL”的方式嵌入的,主要是为了复用。但是,我还是采用了一些技巧,防止API拦截的破解方式。
三、加解密模块
后面开始从代码实现的角度进行讲解。首先需要介绍的就是加解密功能,采用的是Crypto++开源加密算法库,并将它分装成一个DLL。
关于Crypto++库的用法,可以参考我的上一篇博文:
http://blog.csdn.net/sagittarius_warrior/article/details/53408803
关于封装,我主要参考了这篇博文:https://my.oschina.net/u/566591/blog/168421
封装过程:
1,VS新建win32 DLL工程:AES_DLL.sln
2,添加导出函数和接口类
#ifndef AES_DLL_H
#define AES_DLL_H
#ifdef AES_IMPORT
#define AES_API extern "C" __declspec(dllexport)
#else
#define AES_API extern "C" __declspec(dllimport)
#endif
#include <string>
using std::string;
class IEncryptor
{
public:
virtual void SetKey(unsigned char * key, unsigned char * iv, int length) = 0;
// encrypt the plainText and generate a file, the plainText should be a string with '\0' end
virtual bool EncryptString2File(const char * plainText, const char * outFilename) = 0;
// decyrpt the file and output the recover, you may calculate the size of the file and allocate the space for recover
virtual bool DecryptFile2String(const char * decFilename, char * recoverText) = 0;
};
AES_API IEncryptor* CreateEncryptor();
AES_API void ReleaseEncryptor(IEncryptor* pEncryptor);
typedef IEncryptor* (*pfnCreateEncryptor)();
typedef void (*pfnReleaseEncryptor)(IEncryptor*);
#endif // AES_DLL_H
2,导出函数的实现
#include "stdafx.h"
#include "easyaes.h"
#include "encryptor.h"
#define AES_IMPORT
IEncryptor* CreateEncryptor()
{
return static_cast<IEncryptor *>(new EasyAES());
}
void ReleaseEncryptor(IEncryptor* pEncryptor)
{
EasyAES * pEasyAES = dynamic_cast<EasyAES *>(pEncryptor);
if (pEasyAES)
{
delete pEasyAES;
pEasyAES = NULL;
}
}
3,实现类
class EasyAES : public IEncryptor
{
public: // for export interface class
void SetKey(byte * key, byte * iv, int length);
// encrypt the plainText and generate a file, the plainText should be a string with '\0' end
bool EncryptString2File(const char * plainText, const char * outFilename);
// decyrpt the file and output the recover, you may calculate the size of the file and allocate the space for recover
bool DecryptFile2String(const char * decFilename, char * recoverText);
鉴于篇幅,这里只列出部分
4,具体实现
bool EasyAES::EncryptString2File(const char * plainText, const char * outFilename)
{
// check if the string 'plainText' is empty.
if (0 == strlen(plainText))
{
cout << "The input string is empty ! " << endl;
return false;
}
// string is not empty, the encrypt the file
CBC_Mode<AES>::Encryption aesEncryptor(key, key_length, iv);
StringSource(plainText, true,
new StreamTransformationFilter(aesEncryptor,
new FileSink(outFilename)));
return true;
}
bool EasyAES::DecryptFile2String(const char * decFilename, char * recoverText)
{
// check if the file 'decFilename' exists!
if (_access(decFilename, 0) == -1)
{
cout << "The file " << decFilename << " is not exist! " << endl;
return false;
}
// exist , then decrypt the file
string buffer;
CBC_Mode<AES>::Decryption aesDecryptor(key, key_length, iv);
FileSource(decFilename, true,
new StreamTransformationFilter(aesDecryptor,
new StringSink(buffer)));
memcpy(recoverText, buffer.c_str(), buffer.size());
return true;
}
具体实现部分,都是crypto++库的内容,有有兴趣的直接研究它就行。
---------------------
作者:Sagittarius_Warrior
来源:CSDN
原文:https://blog.csdn.net/Sagittarius_Warrior/article/details/53501282
版权声明:本文为博主原创文章,转载请附上博文链接!