java jar包加密保护解决方案


为什么需要保护?

我用java写了一个程序如下:

     
     
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
     
     
package com.monkey.demo;
// App.java
public class App
{
static public void main( String args[] ) throws Exception {
System.out.println( "This is your application." );
System.out.print( "Args: " );
for (int a=0; a<args.length; ++a)
System.out.print( args[a]+" " );
System.out.println( "" );
new App().new AppChild().print();
new Foo();
new Bar();
}
public class AppChild{
public void print(){
System.out.println("haha ....");
}
}
}

然后编译生成.class文件后,我发布出去了,别人拿到我的.class文件拖到JD-GUI里面看到的是这样:

image

玩毛线,看源代码一样,当然你也可以使用ProGuard混淆,不过别人有点耐心还是能分析出来的。另外你还可以修改class文件里面的某些字段,这些字段对运行没有影响,但是能导致别人无法反编译。这里我们暂且不讨论这种方式,分别讨论下使用ClassLoaderjvmti对class文件加密解密的方案。

ClassLoader

Java运行时装入字节码的机制隐含地意味着可以对字节码进行修改。JVM每次装入类文件时都需要一个称为ClassLoader的对象,这个对象负责把新的类装入正在运行的JVM。JVM给ClassLoader一个包含了待装入类(比如java.lang.Object)名字的字符串,然后由ClassLoader负责找到类文件,装入原始数据,并把它转换成一个Class对象。

所以我们可以通过自定义一个ClassLoader,然后先对class进行解密之后,再加载到JVM。大致流程如下:

     
     
1
2
3
4
5
6
7
     
     
// 首先创建一个ClassLoader对象
ClassLoader myClassLoader = new myClassLoader();
// 利用定制ClassLoader对象装入类文件
// 并把它转换成Class对象
Class myClass = myClassLoader.loadClass( "mypackage.MyClass" );
// 最后,创建该类的一个实例
Object newInstance = myClass.newInstance();
loadClass

在创建自定义的ClassLoader时,只需覆盖其中的一个,即loadClass,获取加密后的文件数据解密加载。

     
     
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
     
     
public Class loadClass( String name, boolean resolve )
throws ClassNotFoundException {
try {
// 我们要创建的Class对象
Class clasz = null;
// 如果类已经在系统缓冲之中,不必再次装入它
clasz = findLoadedClass( name );
if (clasz != null)
return clasz;
// 下面是定制部分
byte classData[] = /* 解密加密后的字节数据 */;
if (classData != null) {
// 成功读取字节码数据,现在把它转换成一个Class对象
clasz = defineClass( name, classData, 0, classData.length );
}
// 如果上面没有成功,尝试用默认的ClassLoader装入它
if (clasz == null)
clasz = findSystemClass( name );
//如有必要,则装入相关的类
if (resolve && clasz != null)
resolveClass( clasz );
// 把类返回给调用者
return clasz;
} catch( IOException ie ) {
throw new ClassNotFoundException( ie.toString() );
} catch( GeneralSecurityException gse ) {
throw new ClassNotFoundException( gse.toString() );
}
}

上面是一个简单的loadClass实现,其中涉及到如下几个方法:

  • findLoadedClass: 检查当前要加载的类是否已经加载。
  • defineClass: 获得原始类文件字节码数据后,调用defineClass转换成一个Class对象。
  • findSystemClass: 提供默认ClassLoader支持。
  • resolveClass: 当JVM想要装入的不仅包括指定的类,而且还包括该类引用的所有其他类时,它会把loadClass的resolve参数设置成true。这时,必须在返回刚刚装入的Class对象给调用者之前调用resolveClass。

加密、解密

直接使用java自带加密算法,比如DES。

生成密钥
     
     
1
2
3
4
5
6
7
8
9
     
     
//DES算法要求有一个可信任的随机数源
SecureRandom sr = new SecureRandom();
//为选择的DES算法生成一个KeyGenerator对象
KeyGenerator kg = KeyGenerator.getInstance( "DES" );
kg.init( sr );
// 生成密匙
SecretKey key = kg.generateKey();
// 获取密匙数据
byte rawKeyData[] = key.getEncoded();
加密数据
     
     
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
     
     
// DES算法要求有一个可信任的随机数源
SecureRandom sr = new SecureRandom();
byte rawKeyData[] = /* 用某种方法获得密匙数据 */;
// 从原始密匙数据创建DESKeySpec对象
DESKeySpec dks = new DESKeySpec( rawKeyData );
// 创建一个密匙工厂,然后用它把DESKeySpec转换成一个SecretKey对象
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance( "DES" );
SecretKey key = keyFactory.generateSecret( dks );
// Cipher对象实际完成加密操作
Cipher cipher = Cipher.getInstance( "DES" );
// 用密匙初始化Cipher对象
cipher.init( Cipher.ENCRYPT_MODE, key, sr );
// 现在,获取数据并加密
byte data[] = /* 用某种方法获取数据 */
// 正式执行加密操作
byte encryptedData[] = cipher.doFinal( data );
// 进一步处理加密后的数据
doSomething( encryptedData );
解密数据
     
     
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
     
     
// DES算法要求有一个可信任的随机数源
SecureRandom sr = new SecureRandom();
byte rawKeyData[] = /* 用某种方法获取原始密匙数据 */;
// 从原始密匙数据创建一个DESKeySpec对象
DESKeySpec dks = new DESKeySpec( rawKeyData );
// 创建一个密匙工厂,然后用它把DESKeySpec对象转换成
// 一个SecretKey对象
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance( "DES" );
SecretKey key = keyFactory.generateSecret( dks );
// Cipher对象实际完成解密操作
Cipher cipher = Cipher.getInstance( "DES" );
// 用密匙初始化Cipher对象
cipher.init( Cipher.DECRYPT_MODE, key, sr );
// 现在,获取数据并解密
byte encryptedData[] = /* 获得经过加密的数据 */
// 正式执行解密操作
byte decryptedData[] = cipher.doFinal( encryptedData );
// 进一步处理解密后的数据
doSomething( decryptedData );

实际案例

这里写了一个简单的例子,代码在github

首先生成密钥:

     
     
1
2
3
     
     
javac FileUtil.java
javac GenerateKey.java
java GenerateKey key.data

然后加密class:

     
     
1
2
     
     
javac EncryptClasses.java
java EncryptClasses key.data App.class Foo.class Bar.class

运行加密后的应用:

     
     
1
2
     
     
javac MyClassLoader.java -Xlint:unchecked
java MyClassLoader key.data App

总的来说ClassLoader在类非常多的情况还是比较麻烦,而且这样一来自定义的ClassLoader本身就成为了突破口。下面介绍另外一种加密保护的方案。

jvmti

jvmti(JVMTM Tool Interface)是JDK提供的一套用于开发JVM监控,问题定位与性能调优工具的通用变成接口。通过JVMTI,我们可以开发各式各样的JVMTI Agent。这个Agent的表现形式是一个以c/c++语言编写的动态共享库。

JVMTI Agent原理: java启动或运行时,动态加载一个外部基于JVM TI编写的dynamic module到Java进程内,然后触发JVM源生线程Attach Listener来执行这个dynamic module的回调函数。在函数体内,你可以获取各种各样的VM级信息,注册感兴趣的VM事件,甚至控制VM的行为。

这里我们只需要监控class的加载信息,而jvmti也提供了这样的接口,通过下面的方式我们就能监控到class的加载:

     
     
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
     
     
JNIEXPORT jint JNICALL
Agent_OnLoad (
JavaVM *vm,
char *options,
void *reserved
)
{
......
//设置事件回调
jvmtiEventCallbacks callbacks;
( void) memset(&callbacks, 0, sizeof(callbacks));
callbacks.ClassFileLoadHook = &MyClassFileLoadHook;
error = jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks));
......
}
void JNICALL
MyClassFileLoadHook (
jvmtiEnv *jvmti_env,
JNIEnv* jni_env,
jclass class_being_redefined,
jobject loader,
const char* name, //class名字
jobject protection_domain,
jint class_data_len, //class文件数据长度
const unsigned char* class_data, //class文件数据
jint* new_class_data_len, //新的class文件数据长度
unsigned char** new_class_data //新的class文件数据
)
{
......
}

通过这样的方式就能监控到class的加载然后再对其进行解密。

加密、解密

加密class文件

这里简单的通过遍历class文件,然后对每个字节进行一个异或处理,具体的加密方法可以自己扩展:

     
     
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
     
     
extern "C" JNIEXPORT jbyteArray JNICALL
Java_Encrypt_encrypt (
JNIEnv * _env,
jobject _obj,
jbyteArray _buf
)
{
jsize len = _env->GetArrayLength( _buf);
unsigned char* dst = ( unsigned char*) _env->GetByteArrayElements( _buf, 0);
for ( int i = 0; i < len; ++i)
{
dst[i] = dst[i] ^ 0x07;
}
_env->SetByteArrayRegion( _buf, 0, len, (jbyte *)dst);
return _buf;
}
解密class文件

在运行jar文件的时候,加载我们的jvmti agent动态库进行动态解密:

     
     
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
     
     
void JNICALL
MyClassFileLoadHook (
jvmtiEnv *jvmti_env,
JNIEnv* jni_env,
jclass class_being_redefined,
jobject loader,
const char* name,
jobject protection_domain,
jint class_data_len,
const unsigned char* class_data,
jint* new_class_data_len,
unsigned char** new_class_data
)
{
*new_class_data_len = class_data_len;
jvmti_env->Allocate(class_data_len, new_class_data);
unsigned char* my_data = *new_class_data;
if(name&& strncmp(name, "com/monkey/", 11)== 0){
for ( int i = 0; i < class_data_len; ++i)
{
my_data[i] = class_data[i] ^ 0x07;
}
} else{
for ( int i = 0; i < class_data_len; ++i)
{
my_data[i] = class_data[i];
}
}
}

实际案例

这里写了一个简单的例子,代码在github

首先加密jar包:

     
     
1
2
     
     
javac Encrypt.java
java -Djava.library.path=. -cp . Encrypt -src jardemo.jar

image

然后会得到一个jardemo_encrypt.jar文件,如果现在直接去运行该文件的话肯定是会出错的,所以要做解密。

先编译生成一个解密的动态库libdecrypt.dylib。然后运行:

     
     
1
     
     
java -jar -agentlib:decrypt jardemo_encrypt.jar

image

总结

总的来说,使用jvmti提供的监控api,方便了我们直接对class的操作,所以第二个方案更好一些,当然其中具体使用怎么样的加密,以及如何去保证加密不被破解就需要各位发挥自己的空间了。

原文地址:http://www.alonemonkey.com/2016/05/25/encrypt-jar-class/

参考:http://blog.csdn.net/yczz/article/details/39034223

https://www.ibm.com/developerworks/cn/java/l-secureclass/


  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
### 回答1: Java WebSocket是一个用于在Java应用程序中实现WebSocket通信的库。WebSocket是一种在Web浏览器和服务器之间进行双向通信的协议,它可以实现实时数据传输和实时更新。 Java WebSocket提供了一组API,使Java应用程序能够创建WebSocket服务器或客户端,并处理WebSocket协议相关的操作。通过使用Java WebSocket库,开发人员可以在Java应用程序中实现WebSocket通信,而不需要手动处理低层的WebSocket协议细节。 使用Java WebSocket,开发人员可以使用简单的编程模型来创建WebSocket服务器和客户端。开发人员可以使用注解或编程方式来定义WebSocket端点,然后处理来自客户端的消息和事件。开发人员还可以使用Java WebSocket提供的API来发送消息给客户端或从客户端接收消息。 Java WebSocket还提供了一些高级功能,例如处理二进制消息、处理 Ping/Pong消息、处理超时和错误、处理并发连接等。开发人员可以使用这些功能来优化和改进WebSocket通信的性能和可靠性。 总之,Java WebSocket是Java应用程序中实现WebSocket通信的解决方案。它提供了一组API,使开发人员能够轻松地创建WebSocket服务器和客户端,并处理WebSocket协议相关的操作。使用Java WebSocket,开发人员可以实现实时数据传输和实时更新,并提供更好的用户体验。 ### 回答2: Java WebSocket jar是用于在Java程序中实现WebSocket功能的库或工具。WebSocket是一种在客户端和服务器之间建立双向通信的协议,可以实现实时交互和数据传输。 在Java中,可以使用WebSocket jar来创建WebSocket服务器或客户端。WebSocket jar提供了各种类和API,用于处理WebSocket连接、消息传输和事件处理。使用WebSocket jar,我们可以轻松地实现WebSocket协议,并与客户端进行双向通信。 通过WebSocket jar,我们可以创建一个WebSocket服务器,监听指定的端口,等待客户端的连接。一旦有客户端连接上来,服务器就可以接收或发送消息,与客户端进行实时的双向通信。 同时,我们也可以使用WebSocket jar创建一个WebSocket客户端,连接到指定的WebSocket服务器。客户端可以发送消息给服务器,服务器也可以向客户端发送消息,实现双向通信。 WebSocket jar还提供了一些事件回调函数,可以在连接建立、消息接收、错误发生等情况下触发,方便我们处理相应的逻辑。 总的来说,Java WebSocket jar是一个方便易用的工具,可以帮助我们在Java程序中实现WebSocket功能,实现实时通信和数据传输。无论是创建WebSocket服务器还是WebSocket客户端,使用WebSocket jar都能简化我们的开发工作,提高效率。 ### 回答3: Java WebSocket是一种在Web应用程序中实现双向通信的技术,它基于WebSocket协议,旨在提供一种实时、高效的通信机制。要使用Java WebSocket,可以通过引入相关的jar文件来实现。 首先,需要引入Java WebSocket的核心库jar文件,这个库文件通常是Apache Tomcat或者Jetty等Web容器自带的。通过将这个jar文件添加到项目的构建路径中,就可以在项目中使用Java WebSocket的相关类和方法。 其次,还可以根据具体的需求,引入一些扩展的jar文件,来拓展Java WebSocket的功能。比如,可以引入JSON库的jar文件,用于在WebSocket通信中处理JSON格式的数据。另外,还可以引入一些与安全相关的jar文件,用于加密或者认证WebSocket连接。 在引入了所需的jar文件后,就可以在Java代码中使用WebSocket相关的类和方法了。首先,需要创建一个WebSocket服务器端,监听指定的端口,并处理接收到的连接请求。然后,可以在WebSocket服务器端的相应方法中,处理连接建立、消息接收、错误处理等逻辑。在需要向客户端发送消息时,可以使用WebSocket提供的方法进行推送。 总之,通过引入Java WebSocket的jar文件,我们可以方便地在Java应用程序中实现WebSocket通信。这里只是简单介绍了引入jar文件的过程及基本使用方法,实际应用中会根据具体需求和框架选择合适的库文件并深入使用相关的API。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值