目录
软件License简介
我们在使用一些需要购买版权的软件产品时,或者我们做的商业软件需要进行售卖,为了收取费用,一般需要一个软件使用许可证,然后输入这个许可到软件里就能够使用软件。简单的是一串序列码或者一个许可证文件,复杂的是一个定制化插件包。于是有的小伙伴就开始好奇这个许可是怎么实现的,特别是在离线情况下它是怎么给软件授权,同时又能避免被破解的。
License控制内容
本文主要介绍的是许可证形式的授权。
1. 如何控制只在指定服务器上使用
如果不控制指定设备,那么下发了许可证,只要把软件复制多份安装则可到处使用,不利于版权维护,每个设备都有唯一标识:mac地址,ip地址,主板序列号等,在许可证中指定唯一标识则只能指定设备使用。
2. 如何控制软件使用期限
为了版权可持续性收益,对软件使用设置期限,到期续费等,则需要在许可证中配置使用起止日期。
3. 如何控制按收费等级分模块使用功能
售卖的软件产品可能进行分级收费,不同的收费标准可使用的软件功能范围不同。许可证中配置可使用的功能模块节点,前提是软件产品在设计开发时,需要有对应的模块结构代码。
实现方案
一、流程设计
- 形式:许可证以文件形式下发,放在服务器指定位置
- 内容:以上控制内容以dom节点形式放在文件中
- 流程:将控制项加密后写入license文件节点,部署到客户机器,客户机使用时再读取license文件内容与客户机实际参数进行匹配校验
- 工具: 私用工具:包含生成秘钥对和通过秘钥生成加密内容 客户工具:包含获取客户机唯一标识内容、校验license内容、守护线程获取及存储结果
- 时序图二、文件防破解
- 防止篡改:文件内容加密,使用AES加密,但是AES加密解密都是使用同一个key;使用非对称公私钥(本文使用的RSA)对内容加密解密,但是对内容长度有限制;综合方案,将AES的key(内部定义)用RSA加密,公钥放在加密工具中,内部持有,私钥放在解密工具中,引入软件产品解密使用。
- 防止修改系统时间绕过许可证使用时间:许可证带上发布时间戳,并定时修改运行时间记录到文件,如果系统时间小于这个时间戳,就算大于许可证限制的起始时间也无法使用
- 提高破解难度:懂技术的可以将代码反编译过来修改代码文件直接绕过校验,所以需要进行代码混淆,有测试过xjar的混淆效果比较好。
实现流程,直接上图!!!
代码示例讲解
私用工具端:
RSA秘钥对生成
packagecom.license.tools.licensecreate.utils;importjava.security.KeyPair;importjava.security.KeyPairGenerator;importjava.security.NoSuchAlgorithmException;importjava.security.interfaces.RSAPrivateKey;importjava.security.interfaces.RSAPublicKey;importjava.util.Base64;/**
* @Description 生成公钥私钥对
* @createDate 2022/05/05
* @createTime 14:25
*/publicclassKeyGenerator{/**
* 私钥
*/privatestaticbyte[] privateKey;/**
* 公钥
*/privatestaticbyte[] publicKey;/**
* 加密算法
*/privatestaticfinalString KEY_ALGORITHM ="RSA";publicvoidgenerater(){try{KeyPairGenerator keyPairGenerator =KeyPairGenerator.getInstance(KEY_ALGORITHM);
keyPairGenerator.initialize(1024);KeyPair keyPair = keyPairGenerator.genKeyPair();RSAPublicKey pubKey =(RSAPublicKey) keyPair.getPublic();RSAPrivateKey priKey =(RSAPrivateKey) keyPair.getPrivate();
privateKey =Base64.getEncoder().encode(priKey.getEncoded());
publicKey =Base64.getEncoder().encode(pubKey.getEncoded());System.out.println("公钥:"+newString(publicKey));System.out.println("私钥:"+newString(privateKey));}catch(NoSuchAlgorithmException e){
e.printStackTrace();System.out.println("生成密钥对失败!");}}publicstaticvoidmain(String[] args){KeyGenerator keyGenerator =newKeyGenerator();
keyGenerator.generater();}}
运行main方法,生成的秘钥对填入RSAUtils的puk和prk,公钥用于加密,私钥用于解密
packagecom.license.tools.licensecreate.utils;importjavax.crypto.BadPaddingException;importjavax.crypto.Cipher;importjavax.crypto.IllegalBlockSizeException;importjavax.crypto.NoSuchPaddingException;importjava.security.*;importjava.security.spec.InvalidKeySpecException;importjava.security.spec.PKCS8EncodedKeySpec;importjava.security.spec.X509EncodedKeySpec;importjava.util.Base64;/**
* @Description 对AES密码加密
* @createDate 2022/05/05
* @createTime 14:28
*/publicclassRSAUtils{/**
* 公钥base64
*/privatestaticString puk ="MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCdfujgTmG4aOa4oK2VysmKvAI+hurN/wuKQjzgJTo3ct6TH5NHFHncb9KXijC1xk2Po+pJ8UjU4XGjU4gq5yhTdeSYPYR6hj5jqLy8fkWpFzeC6RvM4bLDe1lDNKphpcUoo5ZO7T77w9fX2lgJSyy/8LxdBThc4Megga3KW1/W4wIDAQAB";/**
* 加密
*
* @return
* @throws Exception
*/protectedstaticStringencrypt(String content)throwsException{byte[] publicKeyBytes = puk.getBytes();X509EncodedKeySpec x =newX509EncodedKeySpec(Base64.getDecoder().decode(publicKeyBytes));KeyFactory keyFactory =KeyFactory.getInstance("RSA");PublicKey pubKey = keyFactory.generatePublic(x);Cipher cipher =Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);byte[] result = cipher.doFinal(content.getBytes("UTF-8"));returnBase64.getEncoder().encodeToString(result);}/**
* 私钥base64
*/privatestaticString prk ="MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAJ1+6OBOYbho5rigrZXKyYq8Aj6G6s3/C4pCPOAlOjdy3pMfk0cUedxv0peKMLXGTY+j6knxSNThcaNTiCrnKFN15Jg9hHqGPmOovLx+RakXN4LpG8zhssN7WUM0qmGlxSijlk7tPvvD19faWAlLLL/wvF0FOFzgx6CBrcpbX9bjAgMBAAECgYA8uRWohg//PdLXFHxY6JrUNrDW0sXtLoyQfgFimnfbsRpHt0DdgvOJHkQf0VP+gbqdyyEl6TWfflyGEErL39wX1rrosy+LpiN0HeISERJuwJtuiGeR+0qw+Xz2M7VE+e5oD94dRtlzERft2mcDbQAQYUCFNgUBtd1dCJgMJPZJYQJBANHxKKHqMbsH91JsGP8eCu+yeMah0X8cT79nwD71SJRc03W5P1MPKhRyGWJj0M+Wax32pAPCMTfbj19scLplJpUCQQDADD5OuSLYRVqx68/CYbFVK3ye/YD4Cgc+0kT9SoI9bLB10JumHT0seDGeXQqwUPAF3bBZGI8pW2bdtzDj8YGXAkABQXgEv+ncPIf2Lj9YB035cQ/X4E/oerrfYjd8KOtuN7/sDFecn5KY3LXaKM6u7y9k1nzUqOyycNXCtFtYQhKhAkBvgyxyvaFz/uFoyko6zksP705Pa1eFrx0B50pT4P26+O+FmXmnfPbWaXw2PkREmNqmLVGGinImS4JxXzuuP79FAkAFQejjE+5Twi8oSCcNwse7FFP86U6jgcc+S+XCUUkLXQ5SPlkyb037hwoV1lEEJpcyI2tSFRxBKT89KZN0Nfat";protectedstaticStringdecrypt(String signEncrypt)throwsNoSuchAlgorithmException,InvalidKeySpecException,NoSuchPaddingException,InvalidKeyException,IllegalBlockSizeException,BadPaddingException{byte[] privateKeyBytes = prk.getBytes();PKCS8EncodedKeySpec pkcs8EncodedKeySpec =newPKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKeyBytes));KeyFactory keyFactory =KeyFactory.getInstance("RSA");PrivateKey priKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);Cipher cipher =Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, priKey);byte[] result = cipher.doFinal(Base64.getDecoder().decode(signEncrypt));returnnewString(result);}publicstaticvoidmain(String[] args)throwsException{String password ="123456";String a =encrypt(password);System.out.println("AES加密秘钥:"+ a);}}
main方法输入AES需要的明文(示例:123456),用RSA加密成密文,做为AESUtils的aesKey
packagecom.license.tools.licensecreate.utils;importorg.springframework.util.Base64Utils;importjavax.crypto.KeyGenerator;importjavax.crypto.*;importjavax.crypto.spec.SecretKeySpec;importjava.security.InvalidKeyException;importjava.security.NoSuchAlgorithmException;importjava.security.NoSuchProviderException;importjava.security.SecureRandom;importjava.security.spec.InvalidKeySpecException;/**
* @version V1.0
* @desc AES 加密工具类
*/publicclassAESUtils{/**
* RSA加密后的AES秘钥
*/privatestaticString aesEncyptPwd="ZIkun+KvXFWLZLYUwXqFWazQeRe119AkcGcl+p8Erzi4EEaHBFYcQuGuKthIE+1IWSQxoUpUJkT0T1+xtoRi3txDnBikdrFhccGZdRpqwRv58q5nqxJX4wVrq0Ms02KBKgQRTqqlzfYLzQcYPyhv8KPE8JDVkttic+W+j5pFles=";privatestaticfinalString KEY_ALGORITHM ="AES";privatestaticfinalString DEFAULT_CIPHER_ALGORITHM ="AES/ECB/PKCS5Padding";/**
* AES 加密操作
*
* @param content 待加密内容
* @return 返回Base64转码后的加密数据
*/publicstaticStringencrypt(String content){try{Cipher cipher =Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);byte[] byteContent = content.getBytes("utf-8");
cipher.init(Cipher.ENCRYPT_MODE,getSecretKey(RSAUtils.decrypt(aesEncyptPwd)));byte[] result = cipher.doFinal(byteContent);returnBase64Utils.encodeToString(result);}catch(Exception ex){
ex.printStackTrace();System.out.println("AES加密失败");}returnnull;}/**
* AES 解密操作
*
* @param content 已加密内容
* @return
*/publicstaticStringdecrypt(String content){try{//实例化Cipher cipher =Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);//使用密钥初始化,设置为解密模式
cipher.init(Cipher.DECRYPT_MODE,getSecretKey(RSAUtils.decrypt(aesEncyptPwd)));//执行操作byte[] result = cipher.doFinal(Base64Utils.decodeFromString(content));returnnewString(result,"utf-8");}catch(Exception ex){
ex.printStackTrace();System.out.println("AES解密失败");}returnnull;}/**
* 生成加密秘钥
*
* @return
*/privatestaticSecretKeySpecgetSecretKey(String aesKey){//返回生成指定算法密钥生成器的 KeyGenerator 对象KeyGenerator kg =null;try{
kg =KeyGenerator.getInstance(KEY_ALGORITHM);//AES 要求密钥长度为 128SecureRandom random=SecureRandom.getInstance("SHA1PRNG","SUN");
random.setSeed(aesKey.getBytes());
kg.init(128, random);//生成一个密钥SecretKey secretKey = kg.generateKey();returnnewSecretKeySpec(secretKey.getEncoded(), KEY_ALGORITHM);}catch(NoSuchAlgorithmException ex){
ex.printStackTrace();System.out.println("生成加密秘钥失败");}catch(NoSuchProviderException e){
e.printStackTrace();System.out.println("生成加密秘钥失败");}returnnull;}}
生成license文件的方法
packagecom.license.tools.licensecreate.test;importcom.license.tools.licensecreate.utils.AESUtils;importcom.license.tools.licensecreate.utils.DateUtils;importorg.dom4j.Document;importorg.dom4j.DocumentHelper;importorg.dom4j.Element;importorg.dom4j.io.OutputFormat;importorg.dom4j.io.XMLWriter;importjava.io.File;importjava.io.FileWriter;importjava.util.Scanner;/**
* @Description 生成签名
* @createDate 2022/05/05
* @createTime 17:41
*/publicclassCreateSign{publicstaticvoidmain(String[] args){Scanner sc =newScanner(System.in);//系统标识---由mac地址+cpu序列号,在客户工具端获取客户机mac地址和cpu序列号后用AES加密得到System.out.println("请输入系统标识串(部署的服务获取):");String systemSign = sc.nextLine();System.out.println("请输入生效起始时间(格式如:2022-05-05 00:00:00):");String generatedTimeStr = sc.nextLine();System.out.println("请输入生效截止时间(格式如:2022-05-05 00:00:00):");String expiredTimeStr = sc.nextLine();System.out.println("请输入上一次校验时间初始值(格式如:2022-05-05 00:00:00):");String lastValidateTimeStr = sc.nextLine();System.out.println("请输入项目部署唯一版本号(不能带“-”):");String version = sc.nextLine();System.out.println("请输入license文件生成路径:");String path = sc.nextLine();createLicense(systemSign, generatedTimeStr, expiredTimeStr, lastValidateTimeStr, version, path);System.out.println("license文件生成成功,文件路径:"+ path);}privatestaticvoidcreateLicense(String systemSign,String generatedTimeStr,String expiredTimeStr,String lastValidateTimeStr,String version,String path){try{//解密系统标识得到mac地址+cpu序列号String macAndCpu =AESUtils.decrypt(systemSign);System.out.println("客户服务器mac地址和cpu序列号:"+ macAndCpu);//MAC地址-CPU序列号-生效起始时间-生效结束结束时间-软件产品序列号(项目版本唯一标识)// String content = "A8:A1:59:41:89:36-BFEBFBFF000906EA-20220506-20220507-dmoiji3xkoa4p33";StringBuilder signBuilder =newStringBuilder(macAndCpu);//生效起始时间long generatedTime =DateUtils.getTimeInMillis(generatedTimeStr);//生效截止时间long expiredTime =DateUtils.getTimeInMillis(expiredTimeStr);//项目唯一标识
signBuilder.append("-").append(generatedTime).append("-").append(expiredTime).append("-").append(version);String sign =AESUtils.encrypt(signBuilder.toString());System.out.println("AES加密生成签名:");System.out.println("-----------------------------------------------------------------------------------------------");System.out.println(sign);System.out.println("-----------------------------------------------------------------------------------------------");//生成licence文件Document document =DocumentHelper.createDocument();//根节点Element rootEle = document.addElement("license");//功能数据节点,扩展参数时可在此节点下扩展Element dataEle = rootEle.addElement("features");Element featureEle = dataEle.addElement("feature");
featureEle.addAttribute("name","lastValidateTi");
featureEle.addAttribute("ti",AESUtils.encrypt(String.valueOf(DateUtils.getTimeInMillis(lastValidateTimeStr))));//签名节点Element signEle = rootEle.addElement("signature");
signEle.setText(sign);System.out.println(document.asXML());OutputFormat format =OutputFormat.createPrettyPrint();// 设置编码格式
format.setEncoding("UTF-8");FileWriter fileWriter =newFileWriter(newFile(path));XMLWriter xmlWriter =newXMLWriter(fileWriter, format);// 设置是否转义,默认使用转义字符
xmlWriter.setEscapeText(false);
xmlWriter.write(document);
xmlWriter.close();}catch(Exception e){
e.printStackTrace();}}}
其中第一步的系统标识由下面的客户工具获取客户机的mac地址和cpu序列号而来,然后将此方法生成的license文件和软件包同路径部署(也可修改客户工具中指定路径)
客户工具端:
只有解密方法的RSAUtils
packagecom.dtranx.tools.license.utils;importjavax.crypto.BadPaddingException;importjavax.crypto.Cipher;importjavax.crypto.IllegalBlockSizeException;importjavax.crypto.NoSuchPaddingException;importjava.security.InvalidKeyException;importjava.security.KeyFactory;importjava.security.NoSuchAlgorithmException;importjava.security.PrivateKey;importjava.security.spec.InvalidKeySpecException;importjava.security.spec.PKCS8EncodedKeySpec;importjava.util.Base64;/**
* @author penghao
* @Description 对AES密码加密
* @createDate 2022/05/05
* @createTime 14:28
*/publicclassRSAUtils{/**
* 私钥base64
*/privatestaticString prk ="MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAJ1+6OBOYbho5rigrZXKyYq8Aj6G6s3/C4pCPOAlOjdy3pMfk0cUedxv0peKMLXGTY+j6knxSNThcaNTiCrnKFN15Jg9hHqGPmOovLx+RakXN4LpG8zhssN7WUM0qmGlxSijlk7tPvvD19faWAlLLL/wvF0FOFzgx6CBrcpbX9bjAgMBAAECgYA8uRWohg//PdLXFHxY6JrUNrDW0sXtLoyQfgFimnfbsRpHt0DdgvOJHkQf0VP+gbqdyyEl6TWfflyGEErL39wX1rrosy+LpiN0HeISERJuwJtuiGeR+0qw+Xz2M7VE+e5oD94dRtlzERft2mcDbQAQYUCFNgUBtd1dCJgMJPZJYQJBANHxKKHqMbsH91JsGP8eCu+yeMah0X8cT79nwD71SJRc03W5P1MPKhRyGWJj0M+Wax32pAPCMTfbj19scLplJpUCQQDADD5OuSLYRVqx68/CYbFVK3ye/YD4Cgc+0kT9SoI9bLB10JumHT0seDGeXQqwUPAF3bBZGI8pW2bdtzDj8YGXAkABQXgEv+ncPIf2Lj9YB035cQ/X4E/oerrfYjd8KOtuN7/sDFecn5KY3LXaKM6u7y9k1nzUqOyycNXCtFtYQhKhAkBvgyxyvaFz/uFoyko6zksP705Pa1eFrx0B50pT4P26+O+FmXmnfPbWaXw2PkREmNqmLVGGinImS4JxXzuuP79FAkAFQejjE+5Twi8oSCcNwse7FFP86U6jgcc+S+XCUUkLXQ5SPlkyb037hwoV1lEEJpcyI2tSFRxBKT89KZN0Nfat";protectedstaticStringdecrypt(String signEncrypt)throwsNoSuchAlgorithmException,InvalidKeySpecException,NoSuchPaddingException,InvalidKeyException,IllegalBlockSizeException,BadPaddingException{byte[] privateKeyBytes = prk.getBytes();PKCS8EncodedKeySpec pkcs8EncodedKeySpec =newPKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKeyBytes));KeyFactory keyFactory =KeyFactory.getInstance("RSA");PrivateKey priKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);Cipher cipher =Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, priKey);byte[] result = cipher.doFinal(Base64.getDecoder().decode(signEncrypt));returnnewString(result);}}
AESUtils
packagecom.dtranx.tools.license.utils;importorg.springframework.util.Base64Utils;importjavax.crypto.Cipher;importjavax.crypto.KeyGenerator;importjavax.crypto.SecretKey;importjavax.crypto.spec.SecretKeySpec;importjava.security.NoSuchAlgorithmException;importjava.security.NoSuchProviderException;importjava.security.SecureRandom;/**
* @version V1.0
* @desc AES 加密工具类
*/publicclassAESUtils{/**
* RSA加密后的AES秘钥
*/privatestaticString aesKey="ZIkun+KvXFWLZLYUwXqFWazQeRe119AkcGcl+p8Erzi4EEaHBFYcQuGuKthIE+1IWSQxoUpUJkT0T1+xtoRi3t"+"xDnBikdrFhccGZdRpqwRv58q5nqxJX4wVrq0Ms02KBKgQRTqqlzfYLzQcYPyhv8KPE8JDVkttic+W+j5pFles=";privatestaticfinalString KEY_ALGORITHM ="AES";privatestaticfinalString DEFAULT_CIPHER_ALGORITHM ="AES/ECB/PKCS5Padding";/**
* AES 加密操作
*
* @param content 待加密内容
* @return 返回Base64转码后的加密数据
*/protectedstaticStringencrypt(String content){try{Cipher cipher =Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);byte[] byteContent = content.getBytes("utf-8");
cipher.init(Cipher.ENCRYPT_MODE,getSecretKey(RSAUtils.decrypt(aesKey)));byte[] result = cipher.doFinal(byteContent);returnBase64Utils.encodeToString(result);}catch(Exception ex){
ex.printStackTrace();System.out.println("AES加密失败");}returnnull;}/**
* AES 解密操作
*
* @param content 已加密内容
* @return
*/protectedstaticStringdecrypt(String content){try{//实例化Cipher cipher =Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);//使用密钥初始化,设置为解密模式
cipher.init(Cipher.DECRYPT_MODE,getSecretKey(RSAUtils.decrypt(aesKey)));//执行操作byte[] result = cipher.doFinal(Base64Utils.decodeFromString(content));returnnewString(result,"utf-8");}catch(Exception ex){
ex.printStackTrace();System.out.println("AES解密失败");}returnnull;}/**
* 生成加密秘钥
*
* @return
*/privatestaticSecretKeySpecgetSecretKey(String aesKey){//返回生成指定算法密钥生成器的 KeyGenerator 对象KeyGenerator kg =null;try{
kg =KeyGenerator.getInstance(KEY_ALGORITHM);//AES 要求密钥长度为 128SecureRandom random=SecureRandom.getInstance("SHA1PRNG","SUN");
random.setSeed(aesKey.getBytes());
kg.init(128, random);//生成一个密钥SecretKey secretKey = kg.generateKey();returnnewSecretKeySpec(secretKey.getEncoded(), KEY_ALGORITHM);}catch(NoSuchAlgorithmException ex){
ex.printStackTrace();System.out.println("生成加密秘钥失败");}catch(NoSuchProviderException e){
e.printStackTrace();System.out.println("生成加密秘钥失败");}returnnull;}}
获取系统mac地址和cpu序列号的SystemUtils
packagecom.dtranx.tools.license.utils;importlombok.extern.slf4j.Slf4j;importjava.io.BufferedReader;importjava.io.IOException;importjava.io.InputStreamReader;importjava.net.InetAddress;importjava.net.InterfaceAddress;importjava.net.NetworkInterface;importjava.util.ArrayList;importjava.util.List;importjava.util.Scanner;importjava.util.stream.Collectors;/**
* @author penghao
*/@Slf4jpublicclassSystemutils{protectedstaticStringgetMacAddress(){try{java.util.Enumeration<NetworkInterface> en =NetworkInterface.getNetworkInterfaces();StringBuilder sb =newStringBuilder();while(en.hasMoreElements()){NetworkInterface iface = en.nextElement();List<InterfaceAddress> addrs = iface.getInterfaceAddresses();for(InterfaceAddress addr : addrs){InetAddress ip = addr.getAddress();NetworkInterface network =NetworkInterface.getByInetAddress(ip);if(network ==null){continue;}if(network.getName().toLowerCase().startsWith("ens")){byte[] mac = network.getHardwareAddress();if(mac ==null){continue;}for(int i =0; i < mac.length; i++){
sb.append(String.format("%02X%s", mac[i],(i < mac.length -1)?"-":""));}String xxy = sb.toString().replaceAll("-","").toUpperCase();
log.info("xxy地址:{}", xxy);return xxy;}}}}catch(Exception e){
e.printStackTrace();
log.error("读取本机系统信息失败!");}returnnull;}protectedstaticStringgetCpuNum(){BufferedReader reader =null;InputStreamReader ir =null;try{String[] linux ={"/bin/bash","-c","dmidecode -t processor | grep 'ID' | awk -F ':' '{print $2}' | head -n 1"};String[] windows ={"wmic","cpu","get","ProcessorId"};// 获取系统信息String property =System.getProperty("os.name");Process process =Runtime.getRuntime().exec(property.contains("Window")? windows : linux);
process.getOutputStream().close();
ir =newInputStreamReader(process.getInputStream());
reader =newBufferedReader(ir);String xxw = reader.readLine();if(xxw !=null){
xxw = xxw.replaceAll(" ","");}
log.info("xxw识别码:{}", xxw);return xxw;}catch(Exception e){
e.printStackTrace();
log.error("获取系统信息失败!");}finally{if(reader !=null){try{
reader.close();}catch(IOException e){
e.printStackTrace();}}if(ir !=null){try{
ir.close();}catch(IOException e){
e.printStackTrace();}}}returnnull;}publicstaticvoidmain(String[] args)throwsException{// List<String> macs = getMacAddress();// System.out.println("本机的mac网卡的地址列表" + macs);System.out.println(getCpuNum());}}
校验方法
packagecom.dtranx.tools.license.utils;importcom.dtranx.tools.license.bean.CheckParams;importcom.dtranx.tools.license.bean.ValidateCodeEnum;importcom.dtranx.tools.license.bean.ValidateResult;importlombok.extern.slf4j.Slf4j;importorg.dom4j.Document;importorg.dom4j.Element;importorg.dom4j.io.OutputFormat;importorg.dom4j.io.SAXReader;importorg.dom4j.io.XMLWriter;importorg.springframework.stereotype.Component;importjava.io.File;importjava.io.FileWriter;importjava.util.HashMap;importjava.util.List;importjava.util.Map;/**
* @author PH
*/@Slf4j@ComponentpublicclassLicenseManager{publicstaticMap<String,ValidateResult>validate(){Map<String,ValidateResult> result =newHashMap<String,ValidateResult>();CheckParams checkParams =null;try{
checkParams =getCheckParams(result);if(checkParams ==null){return result;}}catch(Exception e){
e.printStackTrace();
result.put("Authorize",ValidateResult.error(ValidateCodeEnum.EXCEPTION));return result;}//校验mac地址if(!checkParams.getMacAddress().equals(Systemutils.getMacAddress())){
result.put("Authorize",ValidateResult.error(ValidateCodeEnum.UNAUTHORIZED));return result;}//校验cpu序列号if(!checkParams.getCpuSerial().equals(Systemutils.getCpuNum())){
result.put("Authorize",ValidateResult.error(ValidateCodeEnum.UNAUTHORIZED));return result;}long currentTi =System.currentTimeMillis();//校验时间if(notAfterLastValidateTime(checkParams.getLastValidateTime(), currentTi)||notAfter(checkParams.getGeneratedTime(), currentTi)||notBefore(checkParams.getExpiredTime(), currentTi)){
result.put("Authorize",ValidateResult.error(ValidateCodeEnum.EXPIRED));return result;}
result.put("Authorize",ValidateResult.ok());return result;}publicstaticStringgetSystemSign(){StringMacAddress=Systemutils.getMacAddress();String cpuNum =Systemutils.getCpuNum();returnAESUtils.encrypt(MacAddress+"-"+ cpuNum);}publicstaticvoidupdateSign(String sign){try{Document document =readLicense();Element rootElement = document.getRootElement();Element signatureEle = rootElement.element("signature");
signatureEle.setText(sign);OutputFormat format =OutputFormat.createPrettyPrint();// 设置编码格式
format.setEncoding("UTF-8");String path =System.getProperty("user.dir");FileWriter fileWriter =newFileWriter(newFile(path +File.separator +"license.xml"));XMLWriter xmlWriter =newXMLWriter(fileWriter, format);// 设置是否转义,默认使用转义字符
xmlWriter.setEscapeText(false);
xmlWriter.write(document);
xmlWriter.close();
log.info("更新授权码成功");}catch(Exception e){
e.printStackTrace();thrownewRuntimeException("更新授权码失败!");}}privatestaticbooleannotAfterLastValidateTime(long lastValidateTime,long currentTi){return lastValidateTime >= currentTi;}privatestaticbooleannotBefore(Long expiredTime,long currentTi){return expiredTime <= currentTi;}privatestaticbooleannotAfter(long generatedTime,long currentTi){return generatedTime >= currentTi;}privatestaticCheckParamsgetCheckParams(Map<String,ValidateResult> result){//读取license文件Document document =readLicense();if(document ==null){
log.error("license 读取失败!");
result.put("Authorize",ValidateResult.error(ValidateCodeEnum.FILE_NOT_EXIST));returnnull;}Element rootElement = document.getRootElement();Element dataEle = rootElement.element("features");List<Element> featuresEles = dataEle.elements();Element lastValidateTimeEle = featuresEles.get(0);//提取上一次验证时间String lastValidateTimeStr = lastValidateTimeEle.attributeValue("ti");long lastValidateTime =Long.parseLong(AESUtils.decrypt(lastValidateTimeStr));
log.debug("上一次校验时间:{}", lastValidateTime);//提取签名内容Element signEle = rootElement.element("signature");String signStr = signEle.getText();String sign =AESUtils.decrypt(signStr);if(sign ==null){
log.error("授权码不正确");
result.put("Authorize",ValidateResult.error(ValidateCodeEnum.ILLEGAL));returnnull;}
log.debug("签名内容:{}", sign);String[] signArr = sign.split("-");if(signArr.length !=5){
log.error("授权码不正确");
result.put("Authorize",ValidateResult.error(ValidateCodeEnum.ILLEGAL));returnnull;}CheckParams params =CheckParams.builder().lastValidateTime(lastValidateTime).macAddress(signArr[0]).cpuSerial(signArr[1]).generatedTime(Long.parseLong(signArr[2])).expiredTime(Long.parseLong(signArr[3])).version(signArr[4]).build();return params;}privatestaticDocumentreadLicense(){Document document =null;try{SAXReader saxReader =newSAXReader();String path =System.getProperty("user.dir");
document = saxReader.read(newFile(path +File.separator +"license.xml"));return document;}catch(Exception e){
e.printStackTrace();}returnnull;}publicstaticvoidmain(String[] args){// String sign = AESUtils.decrypt("VorZodH/B6eeNLPA09TNJ8fpjlvrsckBk3VW3Pvr2qzhQVdeL38xS8unNFFxzQrjZ70f4wIoi1Tg1wlZq9DFKuVyp2zD20A//lDswyaD8NsmwMR72R2Ua+Gb0dp+PpM3b9gx2iIFIAtKOyaJlMMV8H4az/EKc/d733lyHfY3wbhsmo4vUvsqPYiriaj+psPu7DgO0DsQqw0xjAblpcrfL1xc42E3STEi9NTNbbBTsLU=");String s="HPdW5CR3bRzVGEDMkZtsfQMHbcJ6SabTLJqdNsvJ7aU=";System.out.println(AESUtils.decrypt(s));}}
守护线程,定时获取校验结果,校验时间间隔从配置文件读取
packagecom.dtranx.tools.license.utils;importcom.dtranx.tools.license.bean.ValidateResult;importlombok.extern.slf4j.Slf4j;importorg.springframework.beans.factory.annotation.Value;importorg.springframework.context.annotation.Bean;importorg.springframework.stereotype.Component;importjava.util.Map;importjava.util.concurrent.TimeUnit;/**
* @author penghao
* @createDate 2022/05/11
* @createTime 16:42
*/@Component@Slf4jpublicclassLicenseThreadimplementsRunnable{publicstaticMap<String,ValidateResult> validateResult =null;@Value("${xxy.checkTime}")privateLong checkTime;@BeanpublicvoidstartThread(){Thread thread =newThread(this);
thread.setDaemon(true);
thread.start();}publicvoidrun(){while(true){
validateResult =LicenseManager.validate();if(validateResult !=null){ValidateResult result = validateResult.get("Authorize");
log.debug("license校验结果:"+ result.getMessage());}try{//正式改为12个小时校验一次,保持与登录同步即可// TimeUnit.HOURS.sleep(12);//测试1分钟校验一次TimeUnit.SECONDS.sleep(checkTime);}catch(InterruptedException e){
e.printStackTrace();}}}publicstaticbooleanvalidateAfterUpdateSign(){
validateResult =LicenseManager.validate();ValidateResult result = validateResult.get("Authorize");return result !=null&& result.getIsValidate();}}
注意事项
1、获取cpu序列号时,实际是通过执行命令“dmidecode -t processor | grep ‘ID’ | awk -F ‘:’ ‘{print $2}’ | head -n 1”获取,在docker中运行服务,如果找不到dmidecode 命令,需要绑定硬件信息配置到容器内
docker 挂载目录增加
- /dev/mem:/dev/mem
- /sbin/dmidecode:/sbin/dmidecode
- /usr/sbin/dmidecode:/usr/sbin/dmidecode
2、docker网络使用非宿主机网络时,docker内的MAC地址会随着docker的重启改变,导致之前生成的授权码校验不通过。处理措施有以下几种:
(1)docker容器内使用宿主机的网络“–net=host --privileged=true ”,则mac地址一直跟随宿主机
(2)docker启动命令添加指定mac地址“ --mac-address=xx:xx:xx:xx:xx:xx”
源码
附完整源码地址(包含使用说明):github
版权归原作者 black_dawn 所有, 如有侵权,请联系我们删除。