开发的软件产品在交付使用时,往往会授权一段时间的试用期,这种时候不太方便对代码添加时间上的约束,这种时候就需要使用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 {
public static String getMachineCode() { String macAddress = getMacAddress(); if (macAddress != null) { return hash(macAddress); } return null; }
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; }
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; } } }
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文件以判断其过期。