从零开始实现 Java 程序 License 验证系统(附完整代码与实操指南)

发布于:2025-07-30 ⋅ 阅读:(21) ⋅ 点赞:(0)

前言

最近自己在做一款用户对于安全性要求很高的工具软件,结合实际工程经验做一下分享。毕竟在软件开发中,为了保护知识产权、控制软件使用权限,License 验证系统是常见的技术手段。本文将以单机 Java 程序为例,详细讲解如何实现一套完整的 License 验证系统,涵盖RSA 非对称加密、AES 对称加密、硬件绑定、有效期校验等核心功能,且原理同样适用于 BS 架构程序(文末会说明 BS 场景的适配方法)。

通过本文,你将学会:

  1. 生成 RSA 公私钥对
  2. 获取机器唯一硬件标识(防篡改)
  3. 生成带有效期和硬件绑定的 License
  4. 加密存储 License 配置文件
  5. 程序启动时验证 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


网站公告

今日签到

点亮在社区的每一天
去签到