通过java keytool 生成私钥库->导出证书->导入到公钥库(注意:linux下由于随机数的问题,私钥库访问密码和公钥库访问密码要相同,私钥密码可以和库密码不一样,密钥格式(必须):字母+数字 长度>=6)
oo@oo ~/IdeaProjects/test/LicenseDemo/license/src/main/resources master ●✚ keytool -genkey -alias privatekey -keysize 1024 -keystore privateKeys.store -validity 3650 输入密钥库口令: cb0000 再次输入新口令: cb0000 您的名字与姓氏是什么? [Unknown]: caobin 您的组织单位名称是什么? [Unknown]: caobin 您的组织名称是什么? [Unknown]: caobin 您所在的城市或区域名称是什么? [Unknown]: cd 您所在的省/市/自治区名称是什么? [Unknown]: sc 该单位的双字母国家/地区代码是什么? [Unknown]: cn CN=caobin, OU=caobin, O=caobin, L=cd, ST=sc, C=cn是否正确? [否]: y 输入 <privatekey> 的密钥口令 注意:所有私钥可以设置为一样,但是不要和私钥库密码一样! (如果和密钥库口令相同, 按回车): cb9999 再次输入新口令: cb9999 Warning: JKS 密钥库使用专用格式。建议使用 "keytool -importkeystore -srckeystore privateKeys.store -destkeystore privateKeys.store -deststoretype pkcs12" 迁移到行业标准格式 PKCS12。 oo@oo ~/IdeaProjects/test/LicenseDemo/license/src/main/resources master ●✚ keytool -export -alias privatekey -file certfile.cer -keystore privateKeys.store 输入密钥库口令: cb0000 存储在文件 <certfile.cer> 中的证书 Warning: JKS 密钥库使用专用格式。建议使用 "keytool -importkeystore -srckeystore privateKeys.store -destkeystore privateKeys.store -deststoretype pkcs12" 迁移到行业标准格式 PKCS12。 oo@oo ~/IdeaProjects/test/LicenseDemo/license/src/main/resources master ●✚ keytool -import -alias publiccert -file certfile.cer -keystore publicCerts.store 输入密钥库口令: cb0000 再次输入新口令: cb0000 所有者: CN=caobin, OU=caobin, O=caobin, L=cd, ST=sc, C=cn 发布者: CN=caobin, OU=caobin, O=caobin, L=cd, ST=sc, C=cn 序列号: b0f76e6 有效期为 Wed Sep 30 14:25:19 CST 2020 至 Sat Sep 28 14:25:19 CST 2030 证书指纹: MD5: 9C:EC:2A:B6:4E:14:25:18:B7:71:69:BE:62:1E:26:8C SHA1: 7F:69:FA:54:C6:AC:40:9B:96:BF:BE:22:F4:BC:B2:A6:3F:70:E2:BB SHA256: 9A:EB:7A:59:6F:78:02:E8:2B:B7:8F:79:CF:77:09:4B:9A:F7:73:6A:37:B0:D0:8E:15:78:1C:A1:04:CF:CA:AF 签名算法名称: SHA256withDSA 主体公共密钥算法: 1024 位 DSA 密钥 版本: 3 扩展: #1: ObjectId: 2.5.29.14 Criticality=false SubjectKeyIdentifier [ KeyIdentifier [ 0000: 59 01 47 4F 9E EF 56 A6 93 5E 68 59 32 E9 AA 19 Y.GO..V..^hY2... 0010: 9A 59 E6 06 .Y.. ] ] 是否信任此证书? [否]: y 证书已添加到密钥库中
创建 校验 license代码
<!-- License --> <dependency> <groupId>de.schlichtherle.truelicense</groupId> <artifactId>truelicense-core</artifactId> <version>1.33</version> </dependency>
import de.schlichtherle.license.LicenseManager; import de.schlichtherle.license.LicenseParam; public class LicenseManagerHolder { private static volatile LicenseManager licenseManager = null; private LicenseManagerHolder() { } public static LicenseManager getLicenseManager(LicenseParam param) { if (licenseManager == null) { synchronized (LicenseManagerHolder.class) { if (licenseManager == null) { licenseManager = new LicenseManager(param); } } } return licenseManager; } }
import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.stream.Collectors; /** * 用于获取客户Linux服务器的基本信息 */ public class LinuxServerInfos{ protected List<String> getIpAddress() throws Exception { List<String> result = null; //获取所有网络接口 List<InetAddress> inetAddresses = getLocalAllInetAddress(); if(inetAddresses != null && inetAddresses.size() > 0){ result = inetAddresses.stream().map(InetAddress::getHostAddress).distinct().map(String::toLowerCase).collect(Collectors.toList()); } return result; } /** * 获取某个网络接口的Mac地址 */ protected String getMacByInetAddress(InetAddress inetAddr){ try { byte[] mac = NetworkInterface.getByInetAddress(inetAddr).getHardwareAddress(); StringBuffer stringBuffer = new StringBuffer(); for(int i=0;i<mac.length;i++){ if(i != 0) { stringBuffer.append("-"); } //将十六进制byte转化为字符串 String temp = Integer.toHexString(mac[i] & 0xff); if(temp.length() == 1){ stringBuffer.append("0" + temp); }else{ stringBuffer.append(temp); } } return stringBuffer.toString().toUpperCase(); } catch (SocketException e) { e.printStackTrace(); } return null; } public List<String> getMacAddress() throws Exception { List<String> result = null; //1. 获取所有网络接口 List<InetAddress> inetAddresses = getLocalAllInetAddress(); if(inetAddresses != null && inetAddresses.size() > 0){ //2. 获取所有网络接口的Mac地址 result = inetAddresses.stream().map(this::getMacByInetAddress).distinct().collect(Collectors.toList()); } return result; } private List<InetAddress> getLocalAllInetAddress() throws Exception { List<InetAddress> result = new ArrayList<>(4); // 遍历所有的网络接口 for (Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); networkInterfaces.hasMoreElements(); ) { NetworkInterface iface = (NetworkInterface) networkInterfaces.nextElement(); // 在所有的接口下再遍历IP for (Enumeration inetAddresses = iface.getInetAddresses(); inetAddresses.hasMoreElements(); ) { InetAddress inetAddr = (InetAddress) inetAddresses.nextElement(); //排除LoopbackAddress、SiteLocalAddress、LinkLocalAddress、MulticastAddress类型的IP地址 if(!inetAddr.isLoopbackAddress() /*&& !inetAddr.isSiteLocalAddress()*/ && !inetAddr.isLinkLocalAddress() && !inetAddr.isMulticastAddress()){ result.add(inetAddr); } } } return result; } }
import de.schlichtherle.license.*; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import javax.security.auth.x500.X500Principal; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.prefs.Preferences; public class CreateLicense { private static Logger log = LogManager.getLogger(CreateLicense.class); /** * X500Princal 是一个证书文件的固有格式,详见API */ private final static X500Principal DEFAULT_HOLDERAND_ISSUER = new X500Principal("CN=Duke, OU=JavaSoft, O=Sun Microsystems, C=US"); private String priAlias; private String privateKeyPwd; private String keyStorePwd; private String subject; private String priPath; private String issued; private String notBefore; private String notAfter; private String ipAddress; private String macAddress; private String userNumber; private String consumerType; private int consumerAmount; private String info; private String licPath; /** * 构造器,参数初始化 * * @param confPath 参数配置文件路径 */ public CreateLicense(String confPath) { // 获取参数 Properties prop = new Properties(); try (InputStream in = getClass().getResourceAsStream(confPath)) { prop.load(in); } catch (IOException e) { log.error("CreateLicense Properties load inputStream error.", e); } //common param priAlias = prop.getProperty("private.key.alias"); privateKeyPwd = prop.getProperty("private.key.pwd"); keyStorePwd = prop.getProperty("key.store.pwd"); subject = prop.getProperty("subject"); priPath = prop.getProperty("priPath"); // license content issued = prop.getProperty("issuedTime"); notBefore = prop.getProperty("notBefore"); notAfter = prop.getProperty("notAfter"); ipAddress = prop.getProperty("ipAddress"); macAddress = prop.getProperty("macAddress"); userNumber = prop.getProperty("userNumber"); consumerType = prop.getProperty("consumerType"); consumerAmount = Integer.valueOf(prop.getProperty("consumerAmount")); info = prop.getProperty("info"); licPath = prop.getProperty("licPath"); } /** * 生成证书,在证书发布者端执行 * * @throws Exception */ public void create() throws Exception { LicenseManager licenseManager = LicenseManagerHolder.getLicenseManager(initLicenseParams()); licenseManager.store(buildLicenseContent(), new File(licPath)); log.info("------ 证书发布成功 ------"); } /** * 初始化证书的相关参数 * * @return */ private LicenseParam initLicenseParams() { Class<CreateLicense> clazz = CreateLicense.class; Preferences preferences = Preferences.userNodeForPackage(clazz); // 设置对证书内容加密的对称密码 CipherParam cipherParam = new DefaultCipherParam(keyStorePwd); // 参数 1,2 从哪个Class.getResource()获得密钥库; // 参数 3 密钥库的别名; // 参数 4 密钥库存储密码; // 参数 5 密钥库密码 KeyStoreParam privateStoreParam = new DefaultKeyStoreParam(clazz, priPath, priAlias, keyStorePwd, privateKeyPwd); // 返回生成证书时需要的参数 return new DefaultLicenseParam(subject, preferences, privateStoreParam, cipherParam); } /** * 通过外部配置文件构建证书的的相关信息 * * @return * @throws ParseException */ public LicenseContent buildLicenseContent() throws ParseException { LicenseContent content = new LicenseContent(); SimpleDateFormat formate = new SimpleDateFormat("yyyy-MM-dd"); content.setConsumerAmount(consumerAmount); content.setConsumerType(consumerType); content.setHolder(DEFAULT_HOLDERAND_ISSUER); content.setIssuer(DEFAULT_HOLDERAND_ISSUER); content.setIssued(formate.parse(issued)); content.setNotBefore(formate.parse(notBefore)); content.setNotAfter(formate.parse(notAfter)); content.setInfo(info); // 扩展字段 Map<String, String> map = new HashMap<>(4); map.put("ip", ipAddress); map.put("mac", macAddress); map.put("userNumber", userNumber); content.setExtra(map); return content; } public static void main(String[] args) throws Exception { CreateLicense clicense = new CreateLicense("/licenseCreateParam.properties"); clicense.create(); } }
import de.schlichtherle.license.*; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.prefs.Preferences; public class VerifyLicense { private static Logger log = LogManager.getLogger(VerifyLicense.class); private String pubAlias; private String keyStorePwd; private String subject; private String licDir; private String pubPath; public VerifyLicense() { // 取默认配置 setConf("/licenseVerifyParam.properties"); } public VerifyLicense(String confPath) { setConf(confPath); } /** * 通过外部配置文件获取配置信息 * * @param confPath 配置文件路径 */ private void setConf(String confPath) { // 获取参数 Properties prop = new Properties(); InputStream in = getClass().getResourceAsStream(confPath); try { prop.load(in); } catch (IOException e) { log.error("VerifyLicense Properties load inputStream error.", e); } this.subject = prop.getProperty("subject"); this.pubAlias = prop.getProperty("public.alias"); this.keyStorePwd = prop.getProperty("key.store.pwd"); this.licDir = prop.getProperty("license.dir"); this.pubPath = prop.getProperty("public.store.path"); } /** * 安装证书证书 */ public void install() { try { LicenseManager licenseManager = getLicenseManager(); licenseManager.install(new File(licDir)); log.info("安装证书成功!"); } catch (Exception e) { log.error("安装证书失败!", e); Runtime.getRuntime().halt(1); } } private LicenseManager getLicenseManager() { return LicenseManagerHolder.getLicenseManager(initLicenseParams()); } /** * 初始化证书的相关参数 */ private LicenseParam initLicenseParams() { Class<VerifyLicense> clazz = VerifyLicense.class; Preferences pre = Preferences.userNodeForPackage(clazz); CipherParam cipherParam = new DefaultCipherParam(keyStorePwd); KeyStoreParam pubStoreParam = new DefaultKeyStoreParam(clazz, pubPath, pubAlias, keyStorePwd, null); return new DefaultLicenseParam(subject, pre, pubStoreParam, cipherParam); } /** * 验证证书的合法性 */ public boolean vertify() { try { LicenseManager licenseManager = getLicenseManager(); LicenseContent verify = licenseManager.verify(); log.info("验证证书成功!"); Map<String, String> extra = (Map) verify.getExtra(); System.out.println("license map:"+extra); String licenseIpString = extra.get("ip");//ip1,ip2,ip3 String licenseMacString = extra.get("mac");//mac1,mac2,mac3 DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); Date notAfter = verify.getNotAfter(); Date notBefore = verify.getNotBefore(); Instant notBeforeInstant = notBefore.toInstant(); Instant notAfterInstant = notAfter.toInstant(); ZoneId zoneId = ZoneId.systemDefault(); LocalDate startTime = notBeforeInstant.atZone(zoneId).toLocalDate(); LocalDate endTime = notAfterInstant.atZone(zoneId).toLocalDate(); Integer licenseUserNumber = Integer.valueOf(extra.get("userNumber")); LinuxServerInfos serverinfos =new LinuxServerInfos(); //校验时间 LocalDate now = LocalDate.now(); if (!(now.isAfter(startTime) && now.isBefore(endTime))) { log.error("有效期 验证不通过"); } if (!(serverinfos.getIpAddress().stream().anyMatch(ip-> Arrays.asList(licenseIpString.split(",")).stream().anyMatch(licenseIp-> licenseIp.equalsIgnoreCase(ip))))){ log.error("IP 验证不通过"); return false; } if (!(serverinfos.getMacAddress().stream().anyMatch(mac-> Arrays.asList(licenseMacString.split(",")).stream().anyMatch(licenseMac-> licenseMac.equalsIgnoreCase(mac))))){ log.error("MAC 验证不通过"); return false; } //todo 获取服务器当前user表用户数量 Integer serverUserNumber=11; if (serverUserNumber>licenseUserNumber){ log.error("User Number 验证不通过"); return false; } log.info("IP、MAC、用户数量 验证通过"); return true; } catch (LicenseContentException ex) { log.error("证书已经过期!", ex); return false; } catch (Exception e) { log.error("验证证书失败!", e); return false; } } /** * 得到本机 mac 地址 * * @param inetAddress * @throws SocketException */ private String getLocalMac(InetAddress inetAddress) throws SocketException { //获取网卡,获取地址 byte[] mac = NetworkInterface.getByInetAddress(inetAddress).getHardwareAddress(); StringBuffer sb = new StringBuffer(); for (int i = 0; i < mac.length; i++) { if (i != 0) { sb.append("-"); } //字节转换为整数 int temp = mac[i] & 0xff; String str = Integer.toHexString(temp); if (str.length() == 1) { sb.append("0" + str); } else { sb.append(str); } } return sb.toString().toUpperCase(); } public static void main(String[] args) { VerifyLicense vlicense = new VerifyLicense(); vlicense.install(); if (!vlicense.vertify()) { Runtime.getRuntime().halt(1); } } }
package com.ruoyi.web.controller.license; import de.schlichtherle.license.*; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.time.Instant; import java.time.LocalDate; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.Arrays; import java.util.Date; import java.util.Map; import java.util.Properties; import java.util.prefs.Preferences; @Component public class VerifyLicense { private static Logger log = LogManager.getLogger(VerifyLicense.class); private String pubAlias; private String keyStorePwd; private String subject; private String licDir; private String pubPath; public VerifyLicense() { // 取默认配置 setConf("/licenseVerifyParam.properties"); } public VerifyLicense(String confPath) { setConf(confPath); } /** * 通过外部配置文件获取配置信息 * * @param confPath 配置文件路径 */ private void setConf(String confPath) { // 获取参数 Properties prop = new Properties(); InputStream in = getClass().getResourceAsStream(confPath); try { prop.load(in); } catch (IOException e) { log.error("VerifyLicense Properties load inputStream error.", e); } this.subject = prop.getProperty("subject"); this.pubAlias = prop.getProperty("public.alias"); this.keyStorePwd = prop.getProperty("key.store.pwd"); this.licDir = prop.getProperty("license.dir"); this.pubPath = prop.getProperty("public.store.path"); } /** * 安装证书证书 */ public void install() { try { LicenseManager licenseManager = getLicenseManager(); licenseManager.install(new File(licDir)); log.info("安装证书成功!"); } catch (Exception e) { log.error("安装证书失败!", e); Runtime.getRuntime().halt(1); } } private LicenseManager getLicenseManager() { return LicenseManagerHolder.getLicenseManager(initLicenseParams()); } /** * 初始化证书的相关参数 */ private LicenseParam initLicenseParams() { Class<VerifyLicense> clazz = VerifyLicense.class; Preferences pre = Preferences.userNodeForPackage(clazz); CipherParam cipherParam = new DefaultCipherParam(keyStorePwd); KeyStoreParam pubStoreParam = new DefaultKeyStoreParam(clazz, pubPath, pubAlias, keyStorePwd, null); return new DefaultLicenseParam(subject, pre, pubStoreParam, cipherParam); } /** * 验证证书的合法性 */ public boolean vertify() { try { LicenseManager licenseManager = getLicenseManager(); LicenseContent verify = licenseManager.verify(); log.info("验证证书成功!"); Map<String, String> extra = (Map) verify.getExtra(); //System.out.println("license map:"+extra); String licenseIpString = extra.get("ip");//ip1,ip2,ip3 String licenseMacString = extra.get("mac");//mac1,mac2,mac3 DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); Date notAfter = verify.getNotAfter(); Date notBefore = verify.getNotBefore(); Instant notBeforeInstant = notBefore.toInstant(); Instant notAfterInstant = notAfter.toInstant(); ZoneId zoneId = ZoneId.systemDefault(); LocalDate startTime = notBeforeInstant.atZone(zoneId).toLocalDate(); LocalDate endTime = notAfterInstant.atZone(zoneId).toLocalDate(); Integer licenseUserNumber = Integer.valueOf(extra.get("userNumber")); LinuxServerInfos serverinfos =new LinuxServerInfos(); //校验时间 LocalDate now = LocalDate.now(); if (!(now.isAfter(startTime) && now.isBefore(endTime))) { log.error("有效期 验证不通过"); } if (!(serverinfos.getIpAddress().stream().anyMatch(ip-> Arrays.asList(licenseIpString.split(",")).stream().anyMatch(licenseIp-> licenseIp.equalsIgnoreCase(ip))))){ log.error("IP 验证不通过"); return false; } if (!(serverinfos.getMacAddress().stream().anyMatch(mac-> Arrays.asList(licenseMacString.split(",")).stream().anyMatch(licenseMac-> licenseMac.equalsIgnoreCase(mac))))){ log.error("MAC 验证不通过"); return false; } //todo 获取服务器当前user表用户数量 Integer serverUserNumber=10; if (serverUserNumber>licenseUserNumber){ log.error("User Number 验证不通过"); return false; } log.info("IP、MAC、用户数量 验证通过"); return true; } catch (LicenseContentException ex) { log.error("证书已经过期!", ex); return false; } catch (Exception e) { log.error("验证证书失败!", e); return false; } } /** * 得到本机 mac 地址 * * @param inetAddress * @throws SocketException */ private String getLocalMac(InetAddress inetAddress) throws SocketException { //获取网卡,获取地址 byte[] mac = NetworkInterface.getByInetAddress(inetAddress).getHardwareAddress(); StringBuffer sb = new StringBuffer(); for (int i = 0; i < mac.length; i++) { if (i != 0) { sb.append("-"); } //字节转换为整数 int temp = mac[i] & 0xff; String str = Integer.toHexString(temp); if (str.length() == 1) { sb.append("0" + str); } else { sb.append(str); } } return sb.toString().toUpperCase(); } @PostConstruct public void installLicense() { install(); } //{秒数} {分钟} {小时} {日期} {月份} {星期} {年份(可为空)} //每天1点1分1秒执行1次 //需要在application启动类上开启@EnableScheduling @Scheduled(cron = "1 1 1 * * ?") public void vertifyLicense() { boolean vertifyResult = vertify(); if (!vertifyResult) { Runtime.getRuntime().halt(1); } } }
配置信息
########## 私钥的配置信息 ########### # 私钥的别名 private.key.alias=privatekey # privateKeyPwd(该密码是生成密钥对的密码 — 需要妥善保管,不能让使用者知道) private.key.pwd=cb9999 # keyStorePwd(该密码是访问密钥库的密码 — 使用 keytool 生成密钥对时设置,使用者知道该密码) key.store.pwd=cb0000 # 项目的唯一识别码 subject=caobin # 密钥库的地址(放在 resource 目录下) priPath=/privateKeys.store ########## license content ########### # 发布日期 issuedTime=2019-09-12 # 有效开始日期 notBefore=2019-09-12 # 有效截止日期 notAfter=2029-12-30 # ip 地址 ipAddress=192.168.22.55 # mac 地址 macAddress=9C-B6-D0-BC-05-C9 #用户数量 userNumber=10 # 使用者类型,用户(user)、电脑(computer)、其他(else) consumerType=user # 证书允许使用的消费者数量 consumerAmount=1 # 证书说明 info=power by caobin #生成证书的地址 licPath=/home/oo/IdeaProjects/test/LicenseDemo/license/src/main/resources/license.lic
########## 公钥的配置信息 ########### # 公钥别名 public.alias=publiccert # 该密码是访问密钥库的密码 — 使用 keytool 生成密钥对时设置,使用者知道该密码 key.store.pwd=cb0000 # 项目的唯一识别码 — 和私钥的 subject 保持一致 subject = caobin # 证书路径(我这边配置在了 linux 根路径下,即 /license.lic ) license.dir=/home/oo/IdeaProjects/test/LicenseDemo/license/src/main/resources/license.lic # 公共库路径(放在 resource 目录下) public.store.path=/publicCerts.store
项目文件 license