开发的软件产品在交付使用时,往往会授权一段时间的试用期,这种时候不太方便对代码添加时间上的约束,这种时候就需要使用license来进行授权使用。这种方式不仅避免了修改代码、改动部署,而且能够方便得对特定机器进行授权,使用起来也比较方便,只需要生成一个新的license发送给使用方替换掉原来的license文件即可。

使用原理

1.生成和获取公钥私钥,可以采用在线工具https://tool.cccyun.cc/rsa,也可以在程序中采用Hutool工具类生成。
2.使用私钥来对包含授权信息的license进行签名。
3.使用公钥进行签名校验,用于验证license是否符合签名使用条件。

签名与校验

1.获取并初始化公钥私钥

1
2
3
4
5
6
7
8
/**  
* 公钥
*/
private static final String PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4WZraimRsyAbWoXDQc66JMXeUgNUmDXx39RyCRTVlujoNY+QZ+3HqLYq4CP4ziiiSefLftek5Y53TSBoeI0gm8Xeq0QTa52u9cAp+aey7a8kDblm6gKlVCAfN0gvdG5xfnivzPyZCRqYUGjenf4bXpr63CKS9H6HePApc7dQ4CjiIDCuR47X1N7jidpOIYE0tCocI5rdqsrCL1fVuShrBs4Aa2lBWw3MVGx6RoFrwdgIBYz/OKcjXUMRm68mAI9dKBZ4+88kOUP5W8BRlChl6eMEY14CeCYblNaWJt5UOXFCdItb3+DUg2bO08o0zDVHTzFaIdtuGPBJGWcxmd9wOQIDAQAB";
/**
* 私钥
*/
private static final String PRIVATE_KEY = "MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDhZmtqKZGzIBtahcNBzrokxd5SA1SYNfHf1HIJFNWW6Og1j5Bn7ceotirgI/jOKKJJ58t+16TljndNIGh4jSCbxd6rRBNrna71wCn5p7LtryQNuWbqAqVUIB83SC90bnF+eK/M/JkJGphQaN6d/htemvrcIpL0fod48Clzt1DgKOIgMK5HjtfU3uOJ2k4hgTS0Khwjmt2qysIvV9W5KGsGzgBraUFbDcxUbHpGgWvB2AgFjP84pyNdQxGbryYAj10oFnj7zyQ5Q/lbwFGUKGXp4wRjXgJ4JhuU1pYm3lQ5cUJ0i1vf4NSDZs7TyjTMNUdPMVoh224Y8EkZZzGZ33A5AgMBAAECggEBAJAQhlc0eoui2djh5gsJtrAKSC7jMg6XbCFECB8F0f7CnqidOg8zS4np5T2dwEkEb7YNEWPhMOQISvhg3mUuzsjBj7CnskH2Zv1r9Wg8Z4DDkKe0+LJZPpKmdU1ANhSVArVzLeo3CRlohjHe1WrmH1g4dl59OrBKrwv9dIEhie+6/rhbiQ216yzS9uxX3529aCu7hwOCbatlaER7BNwjUbAVfgmX4CgRgl0GbDdzNWSg0+JkIOjt8mrDpzTcjtcnhYZcRn6VQcYKts5j0EnsB4XwoMzUc1sKPxBOQpQ6EvfEHhbe7kB4Y5QVu/V6G8bQpZoYoo/jqD5db0AhUP5PwoECgYEA8jQlSsGfmd5Zbs0VFJ5iAgUj4BWRWlvs6R+MbvRcjXkhD5PSZoGJUjJpQhMUaIa8OdQSB7cDWrQTjppWniVufWojXXCrl7FwZ1VXPZ2FAz7HRFIUSesE/ywqOxBeo7gMAogRsoBQ7vJ33VBh3aqZ8itDoFO+of7iDgHg0AFLrrMCgYEA7j09pDM3DzSHMXdAwFKjj+oQSiRGOtztw68pzeNBUWhHKJW+fu65/9A2AtLRdOgNLvr3Ig+IPK2MfilqG/oWVWPIAPo/g14YCfN5H2sGPJ4/sZxnQC59EIMwcfRLbbUUiqlpygl4mWncRCuoYa+1qJUshh9uZc6H/LhgiCAaG2MCgYEA5lNmf0/gKeAD5lWKzX/MriUbN7bq8iXnBs6kqsMg3LyrDvhBJo/a+drMtDQWvUwb5VZiCwokW3ZR/tOQuXXZO9TaarqBw5DO1sWWYBbS7gIeVz9C4gRoTQ0/38kyZrjYWvPw+HIEBSd5i3IsjzL0kRJJjkb1S3JqoG1yz/vbb/cCgYEA2pG44dJS61JHziwYbdnjUX9uXncVL5/NS0CC2+9o16UR07w+PvoKDxYY11JO9DCJF9cLsGYLmY+nW3/nZ69zmfenYK42YvGKoGaNczOfTzHoQay6VXnRJ7sUURfNs+Lz65suATvbNvSWLrzXCHhJ81aYuJxRjnmC7WrCtWCuUv0CgYEA0a7d5YSo7ZSroGdJA5lrJkdUoTKGShXxiVcyHBmckiBSP8ap0yvVOkKBFTnbKRoeM0KMlQwyhcEc4iQ8+PMkUpAamDWz13eLDBDspVYwV+kR9Id4uhtBIgSYdA64ZgXFdY+TUTH8tPOlQHCgc8PnPMVF+QNzOO1eeAJxfemzdl0=";

2.根据机器码和过期时间生成许可证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public static String generateLicense(String machine, String expiryDate) {  
try {
String licenseDate = machine + "|" + expiryDate;
String signData = signData(licenseDate);
String license = licenseDate + "|" + signData;
Files.write(Paths.get("./java-tool/src/main/resources/license.lic"), license.getBytes(), StandardOpenOption.CREATE);
return license;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

/**
* 许可证加密
*/
public static String signData(String data) {
try {
// 采用私钥对签名内容进行加密
byte[] privateKeyBytes = Base64.getDecoder().decode(PRIVATE_KEY);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(data.getBytes());
byte[] signBytes = signature.sign();
return Base64.getEncoder().encodeToString(signBytes);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

3.对许可证进行校验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public static boolean validateLicence(String licenseFilePath, String machineCode) {  
try {
String licenseContent = new String(Files.readAllBytes(Paths.get(licenseFilePath)));
String[] parts = licenseContent.split("\\|");
if (parts.length != 3) {
return false;
}
String licenseMachineCode = parts[0];
String expiryDate = parts[1];
String signature = parts[2];
if (!licenseMachineCode.equals(machineCode)) {
return false;
}
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date = sdf.parse(expiryDate);
boolean before = date.before(new Date());
if (before) {
return false;
}
// 采用公钥对数据进行校验
String licenseData = machineCode + "|" + expiryDate;
byte[] publicKeyBytes = Base64.getDecoder().decode(PUBLIC_KEY);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(keySpec);
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initVerify(publicKey);
sig.update(licenseData.getBytes());
byte[] signatureBytes = Base64.getDecoder().decode(signature);
return sig.verify(signatureBytes);
} catch (Exception e) {
e.printStackTrace();
}
return false;
}

4.获取当前机器码的Mac地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public class MachineCodeUtil {  

/**
* 获取机器码(只能获取到硬件MAC地址,获取不到磁盘序列号)
*/ public static String getMachineCode() {
String macAddress = getMacAddress();
if (macAddress != null) {
return hash(macAddress);
}
return null;
}

/**
* hash处理
*/
private static String hash(String data) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] hashBytes = md.digest(data.getBytes());
StringBuilder hexString = new StringBuilder();
for (byte hashByte : hashBytes) {
hexString.append(String.format("%02x", hashByte));
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}

/**
* 获取电脑 MAC 地址
*/
public static String getMacAddress() {
SystemInfo systemInfo = new SystemInfo();
List<NetworkIF> networkIFs = systemInfo.getHardware().getNetworkIFs();
for (NetworkIF net : networkIFs) {
String alias = net.getIfAlias();
String name = net.getName();
if (alias != null && (alias.contains("以太网") || name.contains("eth0"))) {
String mac = net.getMacaddr();
if (mac != null) {
return mac;
}
}
}
// 传统方式获取机器Mac地址,可以采用拼接所有mac地址的方式生成一个独一的机器码
// Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
// while (networkInterfaces.hasMoreElements()) {
// NetworkInterface networkInterface = networkInterfaces.nextElement();
// String displayName = networkInterface.getDisplayName();
// if (displayName != null && (displayName.contains("以太网") || displayName.contains("eth0"))) {
// byte[] mac = networkInterface.getHardwareAddress();
// if (mac != null && mac.length == 6) {
// StringBuilder macAddress = new StringBuilder();
// for (int i = 0; i < mac.length; i++) {
// macAddress.append(String.format("%02x%s", mac[i], (i < mac.length - 1) ? "-" : ""));
// }
// return macAddress.toString();
// }
// }
// }
return null;
}

}

5.生成许可文件并校验

1
2
3
4
5
public static void main(String[] args) {  
String license = generateLicense(MachineCodeUtil.getMachineCode(), "2025-07-15");
boolean result = validateLicence("./java-tool/src/main/resources/license.lic", MachineCodeUtil.getMachineCode());
System.out.println("校验结果:" + result);
}

实施

通过上述代码的代码说明,已经拥有完成许可证的使用的所有前置条件,在融入到程序中具体使用时,还需要注意以下几点:

1.使用模式为:客户端使用生成license访问服务端以求校验,服务端生成私钥给予用户放入程序中来启动,以此来限制客户端软件被盗用问题。

2.服务端:服务端的处理模式可以为,定义license申请接口,接口中包含必要的信息,可以有申请单位、申请邮箱、申请人、机器码,统一到服务端进行工单处理,待处理通过后进行生成license文件发送至指定邮箱。

3.客户端:客户端来进行license请求流程,在程序启动后校验指定位置是否存在license文件,如果没有许可文件弹出license请求页面或license上传页面,并可以手动刷新license校验,校验成功后定时每天扫描license文件以判断其过期。