一、生成序列号思路简述
使用非对称加密技术RSA实现
对RSA不了解的可先看上一篇:Java对称与非对称加密解密(AES与RSA)。
第一步:使用RSA生成一对密钥对;
第二步:将第一步生成的私钥保存至服务端,此私钥和客户端一一对象。公钥提供至客户端;
第三步:服务端使用第一步生成的私钥、客户端MAC地址、有效结束时间 三个信息生成序列号;
第四步:服务端将第三步生成的序列号提供至客户端;
第五步:客户端将序列号配置进系统;
第六步:访问客户端系统时,客户端使用公钥对序列号进行解释,解释出来的MAC地址和有效结束时间,之后使用解释出来的MAC地址和本机MAC地址做校验,将有效结束时间和当前时间做比较。当MAC地址和本机地址匹配并且有效结束时间大于当前时间时允许访问系统和访问资源,否则禁止访问并提示序列号过期。
二、maven依赖
<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all --><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.5</version></dependency><!-- https://mvnrepository.com/artifact/org.apache.directory.studio/org.apache.commons.codec --><dependency><groupId>org.apache.directory.studio</groupId><artifactId>org.apache.commons.codec</artifactId><version>1.8</version></dependency>
三、java代码
RSA加密工具类封装
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import org.apache.commons.codec.binary.Base64;
import java.security.KeyPair;public class RSAUtil {private String publicKey;private String privateKey;/*有参构造方法,加解密时使用。公钥加密需用私钥解密,私钥加密需用公钥解密*/public RSAUtil(String publicKey,String privateKey){this.publicKey = publicKey;this.privateKey = privateKey;}/*无参构造方法,新建密钥对时使用*/public RSAUtil(){KeyPair pair = SecureUtil.generateKeyPair("RSA");publicKey = new String(Base64.encodeBase64(pair.getPublic().getEncoded()));privateKey= new String(Base64.encodeBase64((pair.getPrivate().getEncoded())));}/**** @param str 加密前数据* @return 返回加密后数据*/public String encrypt(String str){return SecureUtil.rsa(privateKey,publicKey).encryptBcd(str, privateKey!=null?KeyType.PrivateKey:KeyType.PublicKey);}/**** @param str 加密后的数据* @return 返回解密后数据*/public String decrypt (String str){return SecureUtil.rsa(privateKey,publicKey).decryptStrFromBcd(str, privateKey!=null?KeyType.PrivateKey:KeyType.PublicKey);}public String getPublicKey() {return publicKey;}public String getPrivateKey() {return privateKey;}
}
序列号属性实体类
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Serial {/*序列号解密时使用*/public Serial(String publicKey,String serialNumber){this.publicKey = publicKey;this.serialNumber = serialNumber;}/*序列号*/private String serialNumber;/*公钥*/private String publicKey;/*私钥*/private String privateKey;/*有结束时间(13位时间戳)*/private String effectiveEndTime;/*MAC地址*/private String mac;
}
序列号生成、解释工具类封装
package com.coolxiaoqi.springbootserialnumber.utils;import com.coolxiaoqi.springbootserialnumber.entity.Serial;public class SerialUtil {/*** 根据私钥、MAC地址、有效期生成序列号* @param serial 需有privateKey、mac、effectiveEndTime(13位时间戳)值* @return serial 含有privateKey、mac、effectiveEndTime、serialNumber值* @throws Exception*/public static Serial generateSerialNumber(Serial serial) {RSAUtil rsa = new RSAUtil(null,serial.getPrivateKey());String serialNumber = rsa.encrypt(serial.getMac()+","+serial.getEffectiveEndTime());serial.setSerialNumber(serialNumber);return serial;}/*** 根据序列号、公钥校验序列号是否有效* @param serial 需有publicKey、serialNumber* @return serial 含有publicKey、mac、effectiveEndTime、serialNumber值* @throws Exception*/public static Serial explainSerialNumber(Serial serial) {RSAUtil rsa = new RSAUtil(serial.getPublicKey(),null);String[] serialList = rsa.decrypt(serial.getSerialNumber()).split(",");serial.setMac(serialList[0]);serial.setEffectiveEndTime(serialList[1]);return serial;}
}
测试
import cn.hutool.core.net.NetUtil;
import com.coolxiaoqi.springbootserialnumber.entity.Serial;
import com.coolxiaoqi.springbootserialnumber.utils.RSAUtil;
import com.coolxiaoqi.springbootserialnumber.utils.SerialUtil;
import org.junit.jupiter.api.Test;import java.net.InetAddress;
import java.sql.Timestamp;public class SerialUtilTest {/*** 测试方法* 生成rsa密钥对,需保存密钥对,后边生成序列号和解释序列号需要用到*/@Testpublic void getKeyPairStart(){/*提前生成好rsa密钥对,私钥保存至服务端,公钥写入至客户端*/RSAUtil rsa = new RSAUtil();System.out.println("我是rsa公钥,请将我保存:"+rsa.getPublicKey());System.out.println("我是rsa私钥,请将我保存:"+rsa.getPrivateKey());}/*** 测试方法* 生成序列号,生成后将序列号提供至客户端进行配置*/@Testpublic void generateStart(){long currentTime = new Timestamp(System.currentTimeMillis()).getTime();/*new一个序列类,构造时传入私钥、MAC地址、有效结束时间*/// Serial serial = new Serial("MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAJ5+LRzIrPPKokLnmQu1Ncy/9efFtBHrV/PKQ59E0H0L1qmeFqm5snl7vRRW1LTiZp7C08U6+ZBmk/SEfk9WYVIromtBgJpAwj5tqNjwqg9g8z/X3Jr6YOVsxG9AUNShdYh++KLr33XZQIBQ5iA4ueOsU2NOUIn9dRCxcFUM2UsfAgMBAAECgYAZ+SuDcA+H1ElLFZErLgMnDr5JwUiFN0Aun5L9YtWX6HzBRzsHjXsGFZUi5CfvjLqsgdkalVOYqnbMt3nLnqhMAMjfzkT5cZK/gV9DZdKNL/KT60ZQlkzJL/b5dxs+3oYhkEB5klTARmjZzpu8a1wLNGU5ECHZRgqQXS5bhttpQQJBANnwSwO4akUfl8pMUg5L8520pv5WAjiY1ONAuSAUdvMBQ1BhcHKlMRQD/sgDS+qZpWb/dIJxvEorKBK+zY6Ff1kCQQC6LCcgZbQR2JcYF6t4fR531SYzkrVw5YnMKA0gOAdB1TvIC/NiSiEJSRYYJu2wOXLLkA93nJnV5xMN5UjSu4c3AkAiLA9XEf93vzBpw/XJ0Bbuz3ivwISwoyYeS/O7F/pet//6Bo0/LK+5V7cHXZz1uAm7UKrQGU1Qw9uQtOI+SqhhAkEAjniFhMRv9gxDLJvIjA9sBpZmgqcuFPSD7GlmChb2bsno0uFaYwiJmZqFvvvjf62nDOL1Azsjes84BLVHcoUyJwJBANmiKNmjVQ6ZS8MoCKutwNGfB+iNPKucb/yvmfT7vKMh1C0BbrZVI+5P5kYn5/DyuqhC8QrBMK0gK3DH+2pQUNo=","00-E0-4C-68-27-DE","1677592201000");Serial serial = new Serial("MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAJ5+LRzIrPPKokLnmQu1Ncy/9efFtBHrV/PKQ59E0H0L1qmeFqm5snl7vRRW1LTiZp7C08U6+ZBmk/SEfk9WYVIromtBgJpAwj5tqNjwqg9g8z/X3Jr6YOVsxG9AUNShdYh++KLr33XZQIBQ5iA4ueOsU2NOUIn9dRCxcFUM2UsfAgMBAAECgYAZ+SuDcA+H1ElLFZErLgMnDr5JwUiFN0Aun5L9YtWX6HzBRzsHjXsGFZUi5CfvjLqsgdkalVOYqnbMt3nLnqhMAMjfzkT5cZK/gV9DZdKNL/KT60ZQlkzJL/b5dxs+3oYhkEB5klTARmjZzpu8a1wLNGU5ECHZRgqQXS5bhttpQQJBANnwSwO4akUfl8pMUg5L8520pv5WAjiY1ONAuSAUdvMBQ1BhcHKlMRQD/sgDS+qZpWb/dIJxvEorKBK+zY6Ff1kCQQC6LCcgZbQR2JcYF6t4fR531SYzkrVw5YnMKA0gOAdB1TvIC/NiSiEJSRYYJu2wOXLLkA93nJnV5xMN5UjSu4c3AkAiLA9XEf93vzBpw/XJ0Bbuz3ivwISwoyYeS/O7F/pet//6Bo0/LK+5V7cHXZz1uAm7UKrQGU1Qw9uQtOI+SqhhAkEAjniFhMRv9gxDLJvIjA9sBpZmgqcuFPSD7GlmChb2bsno0uFaYwiJmZqFvvvjf62nDOL1Azsjes84BLVHcoUyJwJBANmiKNmjVQ6ZS8MoCKutwNGfB+iNPKucb/yvmfT7vKMh1C0BbrZVI+5P5kYn5/DyuqhC8QrBMK0gK3DH+2pQUNo=","00-E0-4C-68-27-DE",(currentTime+1000*30)+"");/*生成序列号,参数是引用类型,调用生成序列号方法后可直接从对象中获取序列号*/SerialUtil.generateSerialNumber(serial);/*获取序列号*/String serialNumber = serial.getSerialNumber();System.out.println("我是生成的序列号,请发送至客户端:"+serialNumber);}/*** 测试方法* 校验序列号是否有效*/@Testpublic void explainStart(){try {/*new一个序列类,构造时传入公钥、序列号*/Serial serial = new Serial("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCefi0cyKzzyqJC55kLtTXMv/XnxbQR61fzykOfRNB9C9apnhapubJ5e70UVtS04maewtPFOvmQZpP0hH5PVmFSK6JrQYCaQMI+bajY8KoPYPM/19ya+mDlbMRvQFDUoXWIfvii69912UCAUOYgOLnjrFNjTlCJ/XUQsXBVDNlLHwIDAQAB","3231551E45B23CEE6D66546EFAFB8F6E487A4ED1377AD568C82346B94639D707122EEE1E7F9978EBFA9E7213A59B22AD0AF2AA367FBBD564617E45AC9FB9A4EB19A7E40B2AD81611ED2FDE0C347F99166B79603B21C64CCBD8C371DD3026FE7E39BF19159B4B425D22061AA462B882DA44DC542CAD56CCD1C851DFEDBA75BB7D");/*解释序列号,参数是引用类型,调用解释序列号方法后可直接从对象中获取MAC地址、有效结束时间*/SerialUtil.explainSerialNumber(serial);/*获取MAC地址*/String mac = serial.getMac();/*获取有效结束时间*/String effectiveEndTime = serial.getEffectiveEndTime();/*获取本机MAC地址*/// String localMac = LocalMac.getLocalMac(InetAddress.getLocalHost());InetAddress localHost = InetAddress.getLocalHost();String macAddress = NetUtil.getMacAddress(localHost);/*获取当前时间戳*/long time = new Timestamp(System.currentTimeMillis()).getTime();/*判断有效结束时间是否大于当前时间*/System.out.println("解析过后的Mac地址:"+mac);System.out.println("客户端的Mac地址:"+macAddress);// System.out.println(mac.equalsIgnoreCase(macAddress));System.out.println("解析出的过期时间戳:"+effectiveEndTime);System.out.println("当前时间戳:"+time);// System.out.println(Long.parseLong(effectiveEndTime)>time);if(mac.equalsIgnoreCase(macAddress)&&Long.parseLong(effectiveEndTime)>time){System.out.println("当前序列号有效,可正常使用系统与访问资源");}else{System.out.println("当前序列号无效,请向厂商购买新的序列号");}} catch (Exception e) {e.printStackTrace();}}
}