前言
最近自己在做一款用户对于安全性要求很高的工具软件,结合实际工程经验做一下分享。毕竟在软件开发中,为了保护知识产权、控制软件使用权限,License 验证系统是常见的技术手段。本文将以单机 Java 程序为例,详细讲解如何实现一套完整的 License 验证系统,涵盖RSA 非对称加密、AES 对称加密、硬件绑定、有效期校验等核心功能,且原理同样适用于 BS 架构程序(文末会说明 BS 场景的适配方法)。
通过本文,你将学会:
- 生成 RSA 公私钥对
- 获取机器唯一硬件标识(防篡改)
- 生成带有效期和硬件绑定的 License
- 加密存储 License 配置文件
- 程序启动时验证 License 有效性
一、系统架构与核心原理
1.1 整体流程概览
License 验证系统的核心是 “先加密生成,后解密验证”,如下:
1.2 核心加密技术
- RSA 非对称加密:开发者持有私钥(生成 License),程序持有公钥(验证 License)。私钥加密的内容只能用公钥解密,确保 License 无法被伪造。
- AES 对称加密:用于加密存储 License 配置文件(公钥 + 加密 License),避免明文暴露。AES 密钥通过环境变量传递(不硬编码),提升安全性。
- 硬件绑定:通过收集机器的 MAC 地址、CPU 序列号等唯一标识,确保 License 仅能在指定机器运行。
二、核心代码实现(附完整类)
2.1 License 核心工具类(LicenseUtils.java)
负责生成 RSA 密钥对、生成 License、验证 License,以及获取硬件信息。
import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import java.io.IOException; import java.net.NetworkInterface; import java.nio.charset.StandardCharsets; import java.security.*; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.List; public class LicenseUtils { // RSA算法常量 private static final String RSA_ALGORITHM = "RSA"; // 日期格式(严格ISO标准:yyyy-MM-dd) private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE; /** * 生成RSA公私钥对(2048位) */ public static KeyPair generateRsaKeyPair() throws NoSuchAlgorithmException { KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(RSA_ALGORITHM); keyPairGen.initialize(2048); // 2048位密钥,足够安全 return keyPairGen.generateKeyPair(); } /** * 生成License(私钥加密) * @param privateKeyBase64 私钥(Base64编码) * @param expireDate 有效期(格式:yyyy-MM-dd) * @param hardwareInfo 硬件信息(机器唯一标识) */ public static String generateLicense(String privateKeyBase64, String expireDate, String hardwareInfo) throws Exception { // 验证日期格式 LocalDate.parse(expireDate, DATE_FORMATTER);
// 拼接License内容:有效期|硬件信息(用|分隔) String licenseContent = String.format("%s|%s", expireDate, hardwareInfo);
// 私钥解密 PrivateKey privateKey = getPrivateKey(privateKeyBase64); Cipher cipher = Cipher.getInstance(RSA_ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, privateKey); byte[] encrypted = cipher.doFinal(licenseContent.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encrypted); } /** * 验证License(公钥解密+有效性检查) * @param publicKeyBase64 公钥(Base64编码) * @param encryptedLicense 加密的License(Base64编码) */ public static boolean verifyLicense(String publicKeyBase64, String encryptedLicense) throws Exception { // 公钥解密 PublicKey publicKey = getPublicKey(publicKeyBase64); byte[] encryptedBytes = Base64.getDecoder().decode(encryptedLicense);
String licenseContent; try { Cipher cipher = Cipher.getInstance(RSA_ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, publicKey); licenseContent = new String(cipher.doFinal(encryptedBytes), StandardCharsets.UTF_8); } catch (IllegalBlockSizeException | BadPaddingException e) { throw new SecurityException("License解密失败(可能被篡改)"); } // 解析License内容(有效期|硬件信息) String[] parts = licenseContent.split("\\|"); if (parts.length != 2) { throw new IllegalArgumentException("License格式错误,预期:有效期|硬件信息,实际:" + licenseContent); } // 检查有效期 LocalDate expireDate = LocalDate.parse(parts[0], DATE_FORMATTER); if (LocalDate.now().isAfter(expireDate)) { throw new SecurityException("License已过期(有效期至:" + parts[0] + ")"); } // 检查硬件信息 String currentHardware = getHardwareInfo(); if (!currentHardware.equals(parts[1])) { throw new SecurityException("硬件信息不匹配(当前:" + currentHardware + ",注册:" + parts[1] + ")"); } return true; } /** * 获取机器硬件信息(MAC地址+CPU/主板序列号) */ public static String getHardwareInfo() throws IOException { List<String> identifiers = new ArrayList<>(); // 1. 获取所有非环回网卡的MAC地址 try { String mac = getMacAddress(); if (!mac.isEmpty()) identifiers.add(mac); } catch (Exception e) { System.err.println("获取MAC地址失败:" + e.getMessage()); } // 2. 获取CPU/主板序列号(跨平台) try { String serial = getHardwareSerial(); if (!serial.isEmpty()) identifiers.add(serial); } catch (Exception e) { System.err.println("获取硬件序列号失败:" + e.getMessage()); &nbs |