软件产品license的简单实现java

这篇具有很好参考价值的文章主要介绍了软件产品license的简单实现java。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

软件License简介

我们在使用一些需要购买版权的软件产品时,或者我们做的商业软件需要进行售卖,为了收取费用,一般需要一个软件使用许可证,然后输入这个许可到软件里就能够使用软件。简单的是一串序列码或者一个许可证文件,复杂的是一个定制化插件包。于是有的小伙伴就开始好奇这个许可是怎么实现的,特别是在离线情况下它是怎么给软件授权,同时又能避免被破解的。

License控制内容

本文主要介绍的是许可证形式的授权。

1. 如何控制只在指定服务器上使用

如果不控制指定设备,那么下发了许可证,只要把软件复制多份安装则可到处使用,不利于版权维护,每个设备都有唯一标识:mac地址,ip地址,主板序列号等,在许可证中指定唯一标识则只能指定设备使用。

2. 如何控制软件使用期限

为了版权可持续性收益,对软件使用设置期限,到期续费等,则需要在许可证中配置使用起止日期。

3. 如何控制按收费等级分模块使用功能

售卖的软件产品可能进行分级收费,不同的收费标准可使用的软件功能范围不同。许可证中配置可使用的功能模块节点,前提是软件产品在设计开发时,需要有对应的模块结构代码。

实现方案

一、流程设计

  • 形式:许可证以文件形式下发,放在服务器指定位置
  • 内容:以上控制内容以dom节点形式放在文件中
  • 流程:将控制项加密后写入license文件节点,部署到客户机器,客户机使用时再读取license文件内容与客户机实际参数进行匹配校验
  • 工具:
    私用工具:包含生成秘钥对和通过秘钥生成加密内容
    客户工具:包含获取客户机唯一标识内容、校验license内容、守护线程获取及存储结果
  • 时序图
    软件产品license的简单实现java
    二、文件防破解
  • 防止篡改:文件内容加密,使用AES加密,但是AES加密解密都是使用同一个key;使用非对称公私钥(本文使用的RSA)对内容加密解密,但是对内容长度有限制;综合方案,将AES的key(内部定义)用RSA加密,公钥放在加密工具中,内部持有,私钥放在解密工具中,引入软件产品解密使用。
  • 防止修改系统时间绕过许可证使用时间:许可证带上发布时间戳,并定时修改运行时间记录到文件,如果系统时间小于这个时间戳,就算大于许可证限制的起始时间也无法使用
  • 提高破解难度:懂技术的可以将代码反编译过来修改代码文件直接绕过校验,所以需要进行代码混淆,有测试过xjar的混淆效果比较好。

实现流程,直接上图!!!
软件产品license的简单实现java

代码示例讲解

私用工具端:
RSA秘钥对生成

package com.license.tools.licensecreate.utils;

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Base64;

/**
 * @Description 生成公钥私钥对
 * @createDate 2022/05/05
 * @createTime 14:25
 */
public class KeyGenerator {

    /**
     * 私钥
     */
    private static byte[] privateKey;

    /**
     * 公钥
     */
    private static byte[] publicKey;

    /**
     * 加密算法
     */
    private static final String KEY_ALGORITHM = "RSA";

    public void generater() {
        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("公钥:" + new String(publicKey));
            System.out.println("私钥:" + new String(privateKey));
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            System.out.println("生成密钥对失败!");
        }
    }

    public static void main(String[] args) {
        KeyGenerator keyGenerator = new KeyGenerator();
        keyGenerator.generater();
    }

}

运行main方法,生成的秘钥对填入RSAUtils的puk和prk,公钥用于加密,私钥用于解密

package com.license.tools.licensecreate.utils;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

/**
 * @Description 对AES密码加密
 * @createDate 2022/05/05
 * @createTime 14:28
 */
public class RSAUtils {

    /**
     * 公钥base64
     */
    private static String puk = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCdfujgTmG4aOa4oK2VysmKvAI+hurN/wuKQjzgJTo3ct6TH5NHFHncb9KXijC1xk2Po+pJ8UjU4XGjU4gq5yhTdeSYPYR6hj5jqLy8fkWpFzeC6RvM4bLDe1lDNKphpcUoo5ZO7T77w9fX2lgJSyy/8LxdBThc4Megga3KW1/W4wIDAQAB";

    /**
     * 加密
     *
     * @return
     * @throws Exception
     */
    protected static String encrypt(String content) throws Exception {

        byte[] publicKeyBytes = puk.getBytes();
        X509EncodedKeySpec x = new X509EncodedKeySpec(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"));
        return Base64.getEncoder().encodeToString(result);
    }

    /**
     * 私钥base64
     */
    private static String 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";

    protected static String decrypt(String signEncrypt) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
        byte[] privateKeyBytes = prk.getBytes();
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(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));
        return new String(result);
    }

    public static void main(String[] args) throws Exception {
        String password = "123456";
        String a = encrypt(password);
        System.out.println("AES加密秘钥:" + a);
    }
}

main方法输入AES需要的明文(示例:123456),用RSA加密成密文,做为AESUtils的aesKey

package com.license.tools.licensecreate.utils;

import org.springframework.util.Base64Utils;

import javax.crypto.KeyGenerator;
import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;

/**
 * @version V1.0
 * @desc AES 加密工具类
 */
public class AESUtils {

    /**
     * RSA加密后的AES秘钥
     */
    private static String aesEncyptPwd="ZIkun+KvXFWLZLYUwXqFWazQeRe119AkcGcl+p8Erzi4EEaHBFYcQuGuKthIE+1IWSQxoUpUJkT0T1+xtoRi3txDnBikdrFhccGZdRpqwRv58q5nqxJX4wVrq0Ms02KBKgQRTqqlzfYLzQcYPyhv8KPE8JDVkttic+W+j5pFles=";

    private static final String KEY_ALGORITHM = "AES";

    private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";

    /**
     * AES 加密操作
     *
     * @param content  待加密内容
     * @return 返回Base64转码后的加密数据
     */
    public static String encrypt(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);
            return Base64Utils.encodeToString(result);
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("AES加密失败");
        }

        return null;
    }

    /**
     * AES 解密操作
     *
     * @param content  已加密内容
     * @return
     */
    public static String decrypt(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));
            return new String(result, "utf-8");
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("AES解密失败");
        }
        return null;
    }

    /**
     * 生成加密秘钥
     *
     * @return
     */
    private static SecretKeySpec getSecretKey(String aesKey) {
        //返回生成指定算法密钥生成器的 KeyGenerator 对象
        KeyGenerator kg = null;
        try {
            kg = KeyGenerator.getInstance(KEY_ALGORITHM);
            //AES 要求密钥长度为 128
            SecureRandom random=SecureRandom.getInstance("SHA1PRNG","SUN");
            random.setSeed(aesKey.getBytes());
            kg.init(128, random);
            //生成一个密钥
            SecretKey secretKey = kg.generateKey();
            return new SecretKeySpec(secretKey.getEncoded(), KEY_ALGORITHM);
        } catch (NoSuchAlgorithmException ex) {
            ex.printStackTrace();
            System.out.println("生成加密秘钥失败");
        } catch (NoSuchProviderException e) {
            e.printStackTrace();
            System.out.println("生成加密秘钥失败");
        }
        return null;
    }
}

生成license文件的方法

package com.license.tools.licensecreate.test;

import com.license.tools.licensecreate.utils.AESUtils;
import com.license.tools.licensecreate.utils.DateUtils;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;

import java.io.File;
import java.io.FileWriter;
import java.util.Scanner;

/**
 * @Description 生成签名
 * @createDate 2022/05/05
 * @createTime 17:41
 */
public class CreateSign {

    public static void main(String[] args) {
        Scanner sc = new Scanner(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);
    }

    private static void createLicense(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 = new StringBuilder(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 = new FileWriter(new File(path));
            XMLWriter xmlWriter = new XMLWriter(fileWriter, format);
            // 设置是否转义,默认使用转义字符
            xmlWriter.setEscapeText(false);
            xmlWriter.write(document);
            xmlWriter.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

其中第一步的系统标识由下面的客户工具获取客户机的mac地址和cpu序列号而来,然后将此方法生成的license文件和软件包同路径部署(也可修改客户工具中指定路径)

客户工具端:
只有解密方法的RSAUtils

package com.dtranx.tools.license.utils;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;

/**
 * @author penghao
 * @Description 对AES密码加密
 * @createDate 2022/05/05
 * @createTime 14:28
 */
public class RSAUtils {

    /**
     * 私钥base64
     */
    private static String 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";

    protected static String decrypt(String signEncrypt) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
        byte[] privateKeyBytes = prk.getBytes();
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(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));
        return new String(result);
    }
}

AESUtils

package com.dtranx.tools.license.utils;

import org.springframework.util.Base64Utils;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;

/**
 * @version V1.0
 * @desc AES 加密工具类
 */
public class AESUtils {

    /**
     * RSA加密后的AES秘钥
     */
    private static String aesKey="ZIkun+KvXFWLZLYUwXqFWazQeRe119AkcGcl+p8Erzi4EEaHBFYcQuGuKthIE+1IWSQxoUpUJkT0T1+xtoRi3t" +
            "xDnBikdrFhccGZdRpqwRv58q5nqxJX4wVrq0Ms02KBKgQRTqqlzfYLzQcYPyhv8KPE8JDVkttic+W+j5pFles=";

    private static final String KEY_ALGORITHM = "AES";

    private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";

    /**
     * AES 加密操作
     *
     * @param content  待加密内容
     * @return 返回Base64转码后的加密数据
     */
    protected static String encrypt(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);
            return Base64Utils.encodeToString(result);
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("AES加密失败");
        }

        return null;
    }

    /**
     * AES 解密操作
     *
     * @param content  已加密内容
     * @return
     */
    protected static String decrypt(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));
            return new String(result, "utf-8");
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("AES解密失败");
        }
        return null;
    }

    /**
     * 生成加密秘钥
     *
     * @return
     */
    private static SecretKeySpec getSecretKey(String aesKey) {
        //返回生成指定算法密钥生成器的 KeyGenerator 对象
        KeyGenerator kg = null;
        try {
            kg = KeyGenerator.getInstance(KEY_ALGORITHM);
            //AES 要求密钥长度为 128
            SecureRandom random=SecureRandom.getInstance("SHA1PRNG","SUN");
            random.setSeed(aesKey.getBytes());
            kg.init(128, random);
            //生成一个密钥
            SecretKey secretKey = kg.generateKey();
            return new SecretKeySpec(secretKey.getEncoded(), KEY_ALGORITHM);
        } catch (NoSuchAlgorithmException ex) {
            ex.printStackTrace();
            System.out.println("生成加密秘钥失败");
        } catch (NoSuchProviderException e) {
            e.printStackTrace();
            System.out.println("生成加密秘钥失败");
        }
        return null;
    }
}

获取系统mac地址和cpu序列号的SystemUtils

package com.dtranx.tools.license.utils;


import lombok.extern.slf4j.Slf4j;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.stream.Collectors;

/**
 * @author penghao
 */
@Slf4j
public class Systemutils {

    protected static String getMacAddress() {
        try {
            java.util.Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces();
            StringBuilder sb = new StringBuilder();
            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("读取本机系统信息失败!");
        }
        return null;
    }

    protected static String getCpuNum() {
        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 = new InputStreamReader(process.getInputStream());
            reader = new BufferedReader(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();
                }
            }
        }
        return null;
    }

    public static void main(String[] args) throws Exception {
//        List<String> macs = getMacAddress();
//        System.out.println("本机的mac网卡的地址列表" + macs);
        System.out.println(getCpuNum());
    }
}

校验方法

package com.dtranx.tools.license.utils;


import com.dtranx.tools.license.bean.CheckParams;
import com.dtranx.tools.license.bean.ValidateCodeEnum;
import com.dtranx.tools.license.bean.ValidateResult;
import lombok.extern.slf4j.Slf4j;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.FileWriter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author PH
 */
@Slf4j
@Component
public class LicenseManager {

    public static Map<String, ValidateResult> validate() {
        Map<String, ValidateResult> result = new HashMap<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;
    }

    public static String getSystemSign() {
        String MacAddress = Systemutils.getMacAddress();
        String cpuNum = Systemutils.getCpuNum();
        return AESUtils.encrypt(MacAddress + "-" + cpuNum);
    }


    public static void updateSign(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 = new FileWriter(new File(path + File.separator + "license.xml"));
            XMLWriter xmlWriter = new XMLWriter(fileWriter, format);
            // 设置是否转义,默认使用转义字符
            xmlWriter.setEscapeText(false);
            xmlWriter.write(document);
            xmlWriter.close();
            log.info("更新授权码成功");
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("更新授权码失败!");
        }

    }

    private static boolean notAfterLastValidateTime(long lastValidateTime, long currentTi) {
        return lastValidateTime >= currentTi;
    }

    private static boolean notBefore(Long expiredTime, long currentTi) {
        return expiredTime <= currentTi;
    }

    private static boolean notAfter(long generatedTime, long currentTi) {
        return generatedTime >= currentTi;
    }


    private static CheckParams getCheckParams(Map<String, ValidateResult> result) {
        //读取license文件
        Document document = readLicense();
        if (document == null) {
            log.error("license 读取失败!");
            result.put("Authorize", ValidateResult.error(ValidateCodeEnum.FILE_NOT_EXIST));
            return null;
        }
        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));
            return null;
        }
        log.debug("签名内容:{}", sign);
        String[] signArr = sign.split("-");
        if (signArr.length != 5) {
            log.error("授权码不正确");
            result.put("Authorize", ValidateResult.error(ValidateCodeEnum.ILLEGAL));
            return null;
        }

        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;
    }

    private static Document readLicense() {
        Document document = null;
        try {
            SAXReader saxReader = new SAXReader();
            String path = System.getProperty("user.dir");
            document = saxReader.read(new File(path + File.separator + "license.xml"));
            return document;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) {
//        String sign = AESUtils.decrypt("VorZodH/B6eeNLPA09TNJ8fpjlvrsckBk3VW3Pvr2qzhQVdeL38xS8unNFFxzQrjZ70f4wIoi1Tg1wlZq9DFKuVyp2zD20A//lDswyaD8NsmwMR72R2Ua+Gb0dp+PpM3b9gx2iIFIAtKOyaJlMMV8H4az/EKc/d733lyHfY3wbhsmo4vUvsqPYiriaj+psPu7DgO0DsQqw0xjAblpcrfL1xc42E3STEi9NTNbbBTsLU=");

        String s="HPdW5CR3bRzVGEDMkZtsfQMHbcJ6SabTLJqdNsvJ7aU=";
        System.out.println(AESUtils.decrypt(s));
    }
}

守护线程,定时获取校验结果,校验时间间隔从配置文件读取

package com.dtranx.tools.license.utils;

import com.dtranx.tools.license.bean.ValidateResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author penghao
 * @createDate 2022/05/11
 * @createTime 16:42
 */
@Component
@Slf4j
public class LicenseThread implements Runnable {

    public static Map<String, ValidateResult> validateResult = null;

    @Value("${xxy.checkTime}")
    private Long checkTime;

    @Bean
    public void startThread() {
        Thread thread = new Thread(this);
        thread.setDaemon(true);
        thread.start();
    }

    public void run() {
        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();
            }
        }
    }

    public static boolean validateAfterUpdateSign() {
        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文章来源地址https://www.toymoban.com/news/detail-447511.html

到了这里,关于软件产品license的简单实现java的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 软件产品认定需要准备什么

    一、软件著作权 由于双软认证并不是一个资质,而是\\\"软件产品登记\\\"和\\\"软件企业认定\\\"两种不同资质的统称.称为\\\"双软企业\\\"。要进行软件产品登记就要有软件著作权。 申请软件著作权需要营业执照(个人申请凭身份证就行了)、源代码前后各40页,不足80页的就全部提供、用户

    2024年02月06日
    浏览(48)
  • 创新领航 | 竹云产品入选“年度优秀创新软件产品推广目录”

    8月31日,由中国电子信息行业联合会主办的第二十五届中国国际软件博览会在天津拉开帷幕,本次大会以“塑造软件新生态 赋能发展新变革”为主题,为全国乃至全球软件行业高质量发展聚智聚“力”。 大会发布“2022-2023年度优秀创新软件产品推广目录”,涵盖基础软件、

    2024年02月09日
    浏览(40)
  • 【火鸟视频创作软件】补天云火鸟视频创作软件产品用户手册 (附软件下载地址)

    1 安装) 2 全局选项) 3 概念) 4 构造视频) 5 构造音频) 6 图像构造) 7 电子书构造) 8 帮助和手册) 9 捐赠和广告) 软件下载地址: 【免费下载免费使用】火鸟视频创作软件 免费+批量生成音视频+自动配音+自动配字幕 适用于课程创作+视频创作+音频创作+PDF电子书创作+博客专栏创作

    2024年01月20日
    浏览(65)
  • 利用RSA加密打造强大License验证,确保软件正版合法运行

      概述: C#软件开发中,License扮演着确保软件合法使用的重要角色。采用RSA非对称加密方案,服务端生成带签名的License,客户端验证其有效性,从而实现对软件的授权与安全保障。 License(许可证)在C#软件开发中被广泛应用,以确保软件在合法授权的环境中运行。常见场景

    2024年02月19日
    浏览(37)
  • SOFAStack软件供应链安全产品解析——SCA软件成分分析

    近年来,软件供应链安全相关攻击事件呈快速增长态势,造成的危害也越来越严重,为了保障软件供应链安全,各行业主管单位也出台了诸多政策及技术标准。基于内部多年的实践,蚂蚁数科金融级云原生PaaS平台SOFAStack发布完整的软件供应链安全产品及解决方案,包括静态代

    2024年02月04日
    浏览(46)
  • 云卷云舒:软件产品质量保证思考

    总体产品质量观,从上到下的质量规划,包括质量目标、质量定义和拆解、质量责任制 关系到研发质量的各类预防、检测手段,如日常研发规范,代码审查,检测工具,测试等 一般指针对问题的改进,包括根因分析,问题溯源,倒逼研发改进 几个核心要素: 1,两拨人:规

    2024年01月18日
    浏览(50)
  • 数据蛙恢复软件替代产品有哪些?15款顶尖数据恢复软件清单

    数据蛙恢复软件是一款国内数据恢复软件,可以在很多品牌的电脑上使用。但是你可能会遇到数据蛙恢复软件扫描不到需要恢复文件的情况。那么有没有更专业的数据恢复软件可以找到更多误删数据?本文将为你介绍最值的推荐的15个数据蛙恢复软件替代产品。 丢失的文件是

    2024年01月24日
    浏览(53)
  • 产品设计需要学的8款软件

    1、即时设计: 即时设计 是国内广受 UI/UX 设计师和产品经理欢迎的专业产品设计工具。它内置了 iOS 和 Android 设计系统资源,可帮助用户快速启动设计工作。该工具集成了原型设计、UI 设计、交互设计、交付和资源管理等多种功能,并自带专业设计工具,提供流畅的创作体验

    2024年02月07日
    浏览(44)
  • 系统架构设计高级技能 · 软件产品线

    现在的一切都是为将来的梦想编织翅膀,让梦想在现实中展翅高飞。 Now everything is for the future of dream weaving wings, let the dream fly in reality. 点击进入系列文章目录 软件产品线 主要由两部分组成,分别是 核心资源 和 产品集合 。 核心资源 是领域工程的所有结果的集合,是产品线

    2024年02月09日
    浏览(45)
  • 企业团队协同软件,SaaS模式产品快速增长

    企业团队协同软件 是企业内部提高团队效率、规范工作流程的重要工具。 在企业数字化转型的市场趋势下,SaaS模式的企业团队协同软件更能满足客户对云服务、移动化、终端多样化以及快速部署的产品使用需求。 IDC中国企业软件市场分析师王楠表示:“ 企业团队协同软件

    2024年02月13日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包