# license-inject **Repository Path**: JackSonShell/license-inject ## Basic Information - **Project Name**: license-inject - **Description**: 本工具的功能特性: - 支持生成授权证书的功能 - 支持零侵入式注入【证书校验】逻辑功能 - 支持lib目录下依赖的子模块jar包内的class,合并到SpringBoot far jar主包中 - 支持proguard代码混淆功能(可关闭),保护核心代码逻辑和证书校验逻辑 - 支持xjar字节码加密功能(可关闭),实现防反编译、逆向工程等侵权行为 - **Primary Language**: Java - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 11 - **Created**: 2025-02-24 - **Last Updated**: 2025-02-24 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README 请加qq索取源码(* ̄︶ ̄): 1139162887 # 一、背景介绍 使用Java语言开发的软件产品(2B产品,客户环境本地安装),进行市场推广时,需要对客户的安装环境进行License授权: - 绑定服务器MAC地址,防止软件被复制滥用 - 限制用户数量,保护商业版本 - 规定软件使用期限,实现商业可持续 - 重申软件知识归属权 - 禁止反编译、篡改或逆向工程等侵犯版权的行为 - 等等 因此编证书注入工具,下面简称本工具。 本工具的功能特性: - 支持生成授权证书的功能 - 支持零侵入式注入【证书校验】逻辑功能 - 支持lib目录下依赖的子模块jar包内的class,合并到SpringBoot far jar主包中 - 支持proguard代码混淆功能(可关闭),保护核心代码逻辑和证书校验逻辑 - 支持xjar字节码加密功能(可关闭),实现防反编译、逆向工程等侵权行为 说明:零侵入式注入【证书校验】逻辑注入功能,无需改源码、无需改配置、无需使用maven插件等,只需要提供jar包即可,可以是SpringBoot fat jar,也可以是普通工具jar。 ![校验证书信息](./doc/license-log.png "屏幕截图") # 二、本工具使用步骤 ## 1、安装JDK,并配置环境变量 本工具开发时,使用的版本:JDK 17 注:配置环境变量后,可能需要重启IDE ## 2、安装proguard,并配置环境变量 本工具开发时,使用的版本:proguard 7.4.2 注:配置环境变量后,可能需要重启IDE ## 3、安装Golang并配置环境变量 本工具开发时,使用的版本:go1.21.6 注:配置环境变量后,可能需要重启IDE ## 4、编译构建打包license-core 必须构建,后续使用license-tool对目标产品jar包进行字节码注入时,会将本模块的class、依赖lib,自动拷贝到目标产品的jar包中。 ```shell cd license-inject/license-core/ mvn clean package -DskipTests=true ``` ## 5、编译打包springboot2-demo 用于演示license-tool模块中的测试用例,如果不需要可以不构建 ```shell cd license-inject/springboot2-demo/ mvn clean package -DskipTests=true ``` ## 6、对springboot2.jar注入字节码、代码混淆、生成xjar 参考license-tool模块中的[测试用例](./src/test/java/com/sky/wsp/jar/controller/ParseJarUtilTest3.java),结合产品的自身情况进行修改: - 修改要进行字节码注入的类、方法信息 - 修改lib目录下依赖包要合并到主包的信息(proguard只能混淆主包中的class,lib目录下的子模块jar包暂无法修改) - 修改proguard.cfg配置文件的地址 - 修改xjar字节码的加密密码 - 等等 执行测试用例后,生成的内容说明: ```text ./springboot2.jar 原软件产品jar ./inject/springboot2.jar 字节码注入、proguard代码混淆后的jar ./inject/xjar/springboot2.jar 字节码注入、proguard代码混淆、xjar字节码加密后的jar ./inject/xjar/xjar.go Go启动器源码文件 ./inject/xjar/xjar_agentable.go Go启动器源码文件 ``` 编译生成的xjar启动器, linux(x86/arm) 、windows请分别编译: ```shell go build xjar.go ``` ## 7、生成许可证密钥 使用JDK自带的 keytool生成密钥对 ```shell # 进入JDK的bin目录 cd ${JAVA_HOME}\bin #1 、生成密钥对,包含私钥和公钥。 keytool -genkey -keysize 1024 -keyalg DSA -validity 3650 -alias "myPrivateAlias" -keystore "myPrivateKeys.keystore" -storepass "abc123" -keypass "prv123" -dname "CN=localhost, OU=localhost, O=localhost, L=SH, ST=SH, C=CN" # 查看 keytool -list -v -keystore myPrivateKeys.keystore -storepass "abc123" # 导出公钥证书 keytool -exportcert -alias "myPrivateAlias" -keystore "myPrivateKeys.keystore" -storepass "abc123" -file "myCertfile.cer" # 导入公钥(这一个命令在客户端执行) keytool -import -alias "myPublicAlias" -file "myCertfile.cer" -keystore "myPublicCerts.keystore" -storepass "abc123" # 查看公钥 keytool -list -v -keystore myPublicCerts.keystore -storepass "abc123" # 删除私钥 keytool -delete -alias myPrivateAlias -keystore "myPrivateKeys.keystore" -storepass "abc123" # 删除公钥 keytool -delete -alias myPublicAlias -keystore "myPublicCerts.keystore" -storepass "abc123" ``` ## 8、生成授权证书 在license-server模块中: - 1、修改 application.yml 配置文件中的 licensePath 为自己本地路径,就是后续生成的要发给客户端的License证书存储位置。 - 2、启动 Application - 3、访问项目地址:http://localhost:8090/api/doc.html - 4、获取服务器硬件信息 **接口地址**:`/api/license/getServerInfos/{osName}` **请求参数**: | 参数名称 | 参数说明 | in | 是否必须 | 数据类型 | schema | | -------- | -------- | ----- | -------- | -------- | ------ | |osName|windows或linux|path|true|string|| **响应参数**: | 参数名称 | 参数说明 | 类型 | schema | | -------- | -------- | ----- |----- | |cpuSerial|可被允许的CPU序列号|string|| |ipAddress|可被允许的IP地址|array|| |macAddress|可被允许的MAC地址|array|| |mainBoardSerial|可被允许的主板序列号|string|| **响应示例**: ```json { "ipAddress": [ "192.168.40.73" ], "macAddress": [ "7C-8A-E1-9D-4A-8C" ], "cpuSerial": "178BFBFF00A50F00", "mainBoardSerial": "MP22J4ST" } ``` - 5、根据获取到的硬件信息进行加密生成License证书 **接口地址**:`/api/license/generateLicense` **请求参数**: | 参数名称 | 参数说明 | in | 是否必须 | 数据类型 | schema | | -------- | -------- | ----- | -------- | -------- | ------ | |param|param|body|true|生成证书实体类|生成证书实体类| |  consumerAmount|用户数量| - |true|integer(int32)| - | |  consumerType|用户类型| - |true|string| - | |  description|描述信息| - |true|string| - | |  expiryTime|证书失效时间| - |true|string(date-time)| - | |  issuedTime|证书生效时间| - |true|string(date-time)| - | |  keyPass|密钥密码| - |true|string| - | |  licenseCheckModel|额外的服务器硬件校验信息| - |true|设备信息实体类|设备信息实体类| |    cpuSerial|可被允许的CPU序列号| - |true|string| - | |    ipAddress|可被允许的IP地址| - |true|array|string| |    macAddress|可被允许的MAC地址| - |true|array|string| |    mainBoardSerial|可被允许的主板序列号| - |true|string| - | |  licensePath|证书生成路径| - |true|string| - | |  privateAlias|密钥别称| - |true|string| - | |  privateKeysStorePath|密钥库存储路径| - |true|string| - | |  storePass|访问秘钥库的密码| - |true|string| - | |  subject|证书subject| - |true|string| - | **请求示例**: ```json { "subject": "springboot2-1.0", // 必填,产品名-版本号 "privateAlias": "myPrivate", // 必填,密钥别称 "keyPass": "abc123", // 必填,密钥密码 "storePass": "abc123", // 必填,访问秘钥库的密码,和keyPass保持一致 "licensePath": "D:\\java-license\\license.lic", // 必填,证书生成路径 "privateKeysStorePath": "D:\\java-license\\myPrivateKeys.keystore", // 必填,密钥库存储路径 "issuedTime": "2024-03-25 11:10:12",// 必填, "expiryTime": "2025-03-25 15:30:12",// 必填, "consumerType": "user",// 必填, "consumerAmount": 1,// 必填,限制用户数量 "description": "湖北省教育考试院项目",// 必填,项目名 "licenseCheckModel": { "ipAddress": [// 必填, "2.0.0.1", "192.168.215.231" ], "macAddress": [// 必填, "00-FF-13-8B-44-C5", "B4-B5-B6-77-1E-B9" ], "cpuSerial": "178BFBFF00A50F00",// 必填, "mainBoardSerial": "MP22J4ST"// 必填, } } ``` **响应示例**: ```json { "result": "ok", "msg": { "subject": "springboot2-1.0", // 必填,产品名-版本号 "privateAlias": "myPrivate", // 必填,密钥别称 "keyPass": "abc123", // 必填,密钥密码 "storePass": "abc123", // 必填,访问秘钥库的密码,和keyPass保持一致 "licensePath": "D:\\java-license\\license.lic", // 必填,证书生成路径 "privateKeysStorePath": "D:\\java-license\\myPrivateKeys.keystore", // 必填,密钥库存储路径 "issuedTime": "2024-03-25 11:10:12",// 必填, "expiryTime": "2025-03-25 15:30:12",// 必填, "consumerType": "user",// 必填, "consumerAmount": 1,// 必填,限制用户数量 "description": "湖北省教育考试院项目",// 必填,项目名 "licenseCheckModel": { "ipAddress": [// 必填, "2.0.0.1", "192.168.215.231" ], "macAddress": [// 必填, "00-FF-13-8B-44-C5", "B4-B5-B6-77-1E-B9" ], "cpuSerial": "178BFBFF00A50F00",// 必填, "mainBoardSerial": "MP22J4ST"// 必填, } } } ``` ## 8、到客户端安装证书 将公钥文件myPublicCerts.keystore拷贝到客户环境服务器上(软件产品要安装的那台服务器)进行导入 ```shell # 导入公钥(这一个命令在客户端执行) keytool -import -alias "myPublicAlias" -file "myCertfile.cer" -keystore "myPublicCerts.keystore" -storepass "abc123" ``` ## 10、启动加密后的springboot2.jar 部署软件产品时,将第7步中生成的证书文件(D:\\java-license\\license.lic),和证书参数配置文件(license-config.properties,在license-core模块中写死的读这个文件),和软件产品jar,放到同一个目录下. 其中license-config.properties模板: ```properties subject=springboot2-1.0 publicAlias=myPublicAlias storePwd=abc123 licensePath=/home/test/java-license/license.lic publicKeysStorePath=/home/test/java-license/dbproxyPublicCerts.keystore ``` 然后启动应用: - 如果只进行字节码注入或代码混淆,没有使用xjar,则: ```java java -noverify -jar springboot2.jar ``` - 如果使用了xjar,则加密后的jar包启动命令: ```shell xjar java -noverify \ --add-opens java.base/jdk.internal.loader=ALL-UNNAMED \ --add-opens java.base/java.lang=ALL-UNNAMED \ --add-opens java.base/java.net=ALL-UNNAMED \ -jar springboot2.jar ``` # 三、本工具代码仓库结构介绍 ## 1、license-core license-core使用[truelicense](https://truelicense.namespace.global/) ,和JDK自带keytool生成的秘钥,来生成、校验授权证书。( **这里的文档待完善,对这块知识还研究的不深入** ) license-core模块的包路径 **com.talkweb.shardingsphere.sqltranslator** 就是为了混淆视听,其实和sqltranslator模块没有毛关系。 而license-core模块的内容,**不会以依赖包的形式进行打包** ,而是会被proguard和主包console模块混淆到一起,**不会留下模块名**。 ## 2、license-server 一个SpringBoot WEB服务,用于生成数字证书的API接口。 ## 3、license-server 授权证书生成服务,依赖于license-core ## 4、license-tool 对jar包进行字节码注入、混淆、加密的工具类,可以通过本模块下的[测试用例](./src/test/java/com/sky/wsp/jar/controller/ParseJarUtilTest3.java)作为入口,来学习本工具。 ## 5、springboot2-demo 一个SpringBoot2的Demo应用,用于测试license-tool的功能。 # 四、参考技术 ## 1、JAVA编译技术 ```text 静态编译 AOT 本地编译 动态编译 JIT 实时编译 ``` [Spring6和SpringBoot3的新特性AOT](https://zhuanlan.zhihu.com/p/635820419) [GraalVM](https://zhuanlan.zhihu.com/p/676499359) [SpringBoot使用AOT注意事项(不支持的功能说明)](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-with-GraalVM) [SpringCloud使用AOT注意事项(不支持的功能说明)](https://github.com/spring-cloud/spring-cloud-release/wiki/AOT-transformations-and-native-image-support) ## 2、Java类加载器 [Java类加载器 — classloader 的原理及防反编译应用](https://blog.csdn.net/weixin_44259720/article/details/126124735) [SpringBoot怎么通过自定义classloader加密保护class文件](https://www.php.cn/faq/536599.html) [java byte buddy 自定义classloader](https://blog.51cto.com/u_16175499/7643842) ## 3、JavaAgent [Java Agent技术](https://www.cnblogs.com/yanchuanbin/p/17148542.html) [Javaagent入门](https://blog.csdn.net/wufagang/article/details/120878738) ## 4、Java字节码的增强技术(字节码注入) - [Java Agent 和字节码注入技术关系](https://blog.csdn.net/java_faep/article/details/132401088) - [javassist文档](https://github.com/IndustriousSnail/javassist-learn) - Android 进阶之路:ASM 修改字节码,这样学就对了(下面两篇对比着看) ```text 一: https://blog.csdn.net/lmj623565791/article/details/119880194 二: https://blog.csdn.net/2401_84003554/article/details/137215210 ``` - [Java ASM详解:基础知识](https://www.bilibili.com/read/cv6875366) - [Java ASM详解:MethodVisitor与Opcode(五)invokedynamic、方法句柄、lambda](https://www.bilibili.com/read/cv14253327) - [ASM与JAVASSIST有什么区别](https://docs.pingcode.com/ask/29071.html) - [逆向工程库的秘密武器:如何利用 ByteBuddy、Javassist、ASM、CGLIB、Reflections 和 JRebel 提升开发效率和程序灵活性?](https://blog.csdn.net/qq_42531954/article/details/136424097) ## 5、java安全--字节码校验 [java安全--字节码校验](https://blog.csdn.net/qq_18377515/article/details/79590773) ```shell cd target/class java -noverify com.talweb.db.MyClass ``` ## 6、ProGuard原理 [proguard官网](https://www.guardsquare.com/proguard) [proguard github仓库](https://github.com/Guardsquare/proguard) [proguard gitee仓库](https://gitee.com/mirrors/Facebook-ProGuard/) [ProGuard源码分析](https://www.jianshu.com/u/d10ad369a971) [springboot2.x+maven+proguard代码混淆](https://www.jianshu.com/p/7c1f0f62b5f6) [ProGuard桌面工具 - Java代码混淆](https://blog.csdn.net/weixin_44462773/article/details/124172382) ## 7、jar包的解析 [Java JarArchiveEntry类使用实例](http://www.manongjc.com/detail/33-ngskrbqekuztgny.html) [Java JarArchiveInputStream类代码示例](https://vimsky.com/examples/detail/java-class-org.apache.commons.compress.archivers.jar.JarArchiveInputStream.html) ## 8、自定义MAVEN插件 [设计你自己的maven插件](https://blog.csdn.net/weixin_46228112/article/details/134196308)