在工业软件或其他领域中,经常会对软件进行授权,软件需要付费进行有期限的使用。以下是我用Qt设计的设置软件使用期限的两种方案。
主体思想:
1.软件需要绑定机器,让用户无法通过复制在另一台机器上运行。
2.由厂家提供激活码供用户激活,每个激活码只对应指定的机器有效,且激活码需包含使用期限。为保证激活码不能重复使用,激活码需包含当天的日期信息,令激活码仅当天有效,软件过期或许可证丢失后无法使用之前激活码进行激活。
3.由于2的限制,因此需要在每台机器上产生机器码,供厂家用来生成激活码。
4.需有校验功能,校验激活码是否有效,校验是否过期。为了避免用户修改系统时间来延长使用期限,需要验证许可证中最后使用日期是否大于当前日期,若是,则说明用户修改了系统时间,校验不通过,此时可以在代码中删除许可证,或直接退出软件。
5.需要保存许可证或注册表。许可证和注册表信息存储的是当前激活的密钥和最后使用日期、有效期限。并在每次开启和关闭软件时对许可证内的最后使用日期进行更新。
一、获取机器码
要绑定机器,就要获取到机器的唯一标识,网卡的MAC地址是个不错的选择,因此可以拿MAC地址当做机器码。
cpp
QString getMachineCode(){
QString text;
//获取非00-00-00-00-00-00的mac地址
QRegExp regmac("00.00.00.00.00.00");
foreach(QNetworkInterface interface,QNetworkInterface::allInterfaces()){
text = interface.hardwareAddress();
if(text.indexOf(regmac)>=0)
continue;
else
break;
}
QString machineCode;
for(int i=0;i<6;i++){
machineCode.append(text.mid(0,2));
text.remove(0,3);
}
return machineCode;
}
二、生成带使用期限的激活码
激活码可以自己编写一些算法,通过各种计算、移位、换位等操作来生成。下面是我的示例:
cpp
QString newFunction(QString mc, int day)//mc为机器码,day为使用期限
{
QString newcode;
for(int i=0;i<6;i++){
int index=mc.mid(i*2,2).toUInt(nullptr,16);
newcode.append(QString::number(pwd[index],16));
}
//激活码中日期信息
QDate date = QDate::currentDate();
int y=date.year()-2000;
int m=date.month();
int d=date.day();
QString dts = intToBin(y,7)+intToBin(m,4)+intToBin(d,5);
qDebug()<<dts;
int dt = dts.toInt(nullptr,2);
QString dthex = QString::number(dt,16);
QString copycode = newcode;
copycode.replace(0,1,newcode[1]);
copycode.replace(1,1,newcode[0]);
copycode.replace(2,1,newcode[4]);
copycode.replace(4,1,newcode[2]);
copycode.replace(7,1,newcode[10]);
copycode.replace(10,1,newcode[7]);
QStringList binlst;
binlst.append(hexToBin(copycode.mid(0,2)));
binlst.append(hexToBin(copycode.mid(2,2)));
binlst.append(hexToBin(copycode.mid(4,2)));
binlst.append(hexToBin(copycode.mid(6,2)));
binlst.append(hexToBin(copycode.mid(8,2)));
binlst.append(hexToBin(copycode.mid(10,2)));
int jy = 0;
for(int i=7;i>=0;i--){
int nums=0;
for(int j=0;j<6;j++){
if(binlst.at(j).at(i)=='1')
nums++;
}
if(nums%2==1)
jy += 1<<i;
}
qDebug()<<jy;
jy += day;
int local = day%6;
copycode.insert(local*2,tenToHexL(jy));
copycode.append(dthex);
qDebug()<<copycode;
return copycode;
}
三、校验激活码
校验的过程其实就是生成激活码(不带期限),与带期限的激活码进行比对和运算,判断其是否有效,若有效则计算使用期限。
cpp
int Widget::getValidity(QString code)
{
QDate date = QDate::currentDate();
int y=date.year()-2000;
int m=date.month();
int d=date.day();
QString dts = intToBin(y,7)+intToBin(m,4)+intToBin(d,5);
int dt = dts.toInt(nullptr,2);
QString dthex = QString::number(dt,16);
if(dthex!=code.mid(16,4)){//激活码内日期与当前日期不符,激活码过期
qDebug()<<"code over time!";
return 0;
}
code.remove(16,4);
QString mc = getMachineCode();
QString newcode;
for(int i=0;i<6;i++){
int index=mc.mid(i*2,2).toUInt(nullptr,16);
newcode.append(QString::number(pwd[index],16));
}
QString copycode = newcode;
copycode.replace(0,1,newcode[1]);
copycode.replace(1,1,newcode[0]);
copycode.replace(2,1,newcode[4]);
copycode.replace(4,1,newcode[2]);
copycode.replace(7,1,newcode[10]);
copycode.replace(10,1,newcode[7]);
QStringList binlst;
binlst.append(hexToBin(copycode.mid(0,2)));
binlst.append(hexToBin(copycode.mid(2,2)));
binlst.append(hexToBin(copycode.mid(4,2)));
binlst.append(hexToBin(copycode.mid(6,2)));
binlst.append(hexToBin(copycode.mid(8,2)));
binlst.append(hexToBin(copycode.mid(10,2)));
int jy = 0;
for(int i=7;i>=0;i--){
int nums=0;
for(int j=0;j<6;j++){
if(binlst.at(j).at(i)=='1')
nums++;
}
if(nums%2==1)
jy += 1<<i;
}
int validity=0;
for(int i=0;i<12;i+=2){
if(copycode.mid(i,2)!=code.mid(i,2)){
validity = code.mid(i,4).toInt(nullptr,16)-jy;
code.remove(i,4);
break;
}
}
if(copycode==code){//激活码有效,生成许可证
writeLicense(copycode,dthex,validity);
return validity;
}
else
return 0;
}
四、生成许可证
为了后续校验方便,许可证内的激活码可以省略最后几个步骤。许可证内容由三部分组成:激活码、最后使用日期、软件有效期。
cpp
void writeLicense(QString code,QString dthex,int days)
{
QDate yxq = QDate::currentDate().addDays(days);
int y=yxq.year()-2000;
int m=yxq.month();
int d=yxq.day();
QString dts = intToBin(y,7)+intToBin(m,4)+intToBin(d,5);
int dt = dts.toInt(nullptr,2);
QString yxqhex = QString::number(dt,16);
QFile file("license.dat");
file.open(QIODevice::WriteOnly);
QDataStream ds(&file);
QByteArray ba;
ba.resize(10);
for(int i=0;i<6;i++){
ba[i]=uchar(code.mid(i*2,2).toUInt(nullptr,16));
}
ba[6]=uchar(dthex.mid(0,2).toUInt(nullptr,16));
ba[7]=uchar(dthex.mid(2,2).toUInt(nullptr,16));
ba[8]=uchar(yxqhex.mid(0,2).toUInt(nullptr,16));
ba[9]=uchar(yxqhex.mid(2,2).toUInt(nullptr,16));
ds<<(QByteArray)ba;
file.flush();
file.close();
}
五、软件每次启动时校验许可证
cpp
void Widget::checkLicense()
{
QFile file("license.dat");
file.open(QIODevice::ReadOnly);
QDataStream ds(&file);
QByteArray ba;
ds>>ba;
QString code = ba.toHex();
QString shortcode = code.mid(0,12);
QString mc = getMachineCode();
QString newcode;
for(int i=0;i<6;i++){
int index=mc.mid(i*2,2).toUInt(nullptr,16);
newcode.append(QString::number(pwd[index],16));
}
QString copycode = newcode;
copycode.replace(0,1,newcode[1]);
copycode.replace(1,1,newcode[0]);
copycode.replace(2,1,newcode[4]);
copycode.replace(4,1,newcode[2]);
copycode.replace(7,1,newcode[10]);
copycode.replace(10,1,newcode[7]);
if(shortcode!=copycode){//激活码错误
qDebug()<<"校验不通过";
return;
}
QDate lastDate;
QString dts = code.mid(12,4);
int idt = dts.toInt(nullptr,16);
int y = idt>>9;
int m = (idt>>5) & ((1<<4)-1);
int d = idt & ((1<<5)-1);
lastDate.setDate(y+2000,m,d);
if(lastDate.daysTo(QDate::currentDate())<0){//用户修改了系统时间
qDebug()<<"时间非法";
return;
}
QDate today = QDate::currentDate();
y=today.year()-2000;
m=today.month();
d=today.day();
QString s_today = intToBin(y,7)+intToBin(m,4)+intToBin(d,5);
int dt = s_today.toInt(nullptr,2);
QString s_yxq = code.mid(16,4);
if(s_yxq.toInt(nullptr,16)<dt){//许可证已过期
qDebug()<<"已过期";
return;
}
}
六、更新许可证
为防止用户篡改系统时间以达到延长使用期限的目的,许可证中加入了最后使用日期进行校验。在每次软件启动校验成功后、软件关闭时,更新许可证中的最后使用日期。
cpp
void updateLicense()
{
QFile file("license.dat");
file.open(QIODevice::ReadWrite);
QDataStream ds(&file);
QByteArray ba;
ds>>ba;
QDate today = QDate::currentDate();
int y=today.year()-2000;
int m=today.month();
int d=today.day();
QString s_today = intToBin(y,7)+intToBin(m,4)+intToBin(d,5);
int dt = s_today.toInt(nullptr,2);
QString hex = QString::number(dt,16);
ba[6]=uchar(hex.mid(0,2).toUInt(nullptr,16));
ba[7]=uchar(hex.mid(2,2).toUInt(nullptr,16));
file.resize(0);
file.seek(0);
ds<<ba;
file.flush();
file.close();
}
以上内容为本地生成许可证的方式,也可以用写注册表的方式来代替,主体思想不变。