javaee加密,tomcat使用自己的classloader解密

【起因】

公司需要对一个web项目进行加密之后出售,

大家都知道的,class很好反编译,

所以需要对class文件先进行加密,

然后使用自己的classloader进行解密并加载。


【步骤】

大概分两步:

1.对class文件进行加密

2.写解密class文件并加载的classloader

3.将这个classloader加入到tomcat中,也就是使tomcat可以调用到这个classloader


【加密】

1.思路

字节流读取class文件,进行简单的移位

2.实现

做了一个小程序,实现了对某文件夹下所有class文件字节流读取,并+2位的加密方式

3.说明

swing是使用myeclipse的插件做的,可能比较乱

4.代码&下载

源代码和程序打包成jar文件上传到了这里,双击可以使用。


【classloader】

package com.uikoo9;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;

import org.apache.catalina.loader.WebappClassLoader;

/**
 * 自己的ClassLoader
 * 用于解密加密过的class文件并加载
 * @author uikoo9
 */
public class MyClassLoader extends WebappClassLoader{

	/**
	 * 默认构造器
	 */
	public MyClassLoader() {
		super();
	}

	/**
	 * 默认构造器
	 * @param parent
	 */
	public MyClassLoader(ClassLoader parent) {
		super(parent);
	}

	/* (non-Javadoc)
	 * @see org.apache.catalina.loader.WebappClassLoader#findClass(java.lang.String)
	 */
	public Class<?> findClass(String name) throws ClassNotFoundException {
		byte[] classBytes = null;
		
		try {
			classBytes = loadClassBytes(name);
		} catch (Exception e) {
			throw new ClassNotFoundException(name);
		}
		
		Class<?> cl= defineClass(name, classBytes, 0, classBytes.length);
		if(cl == null) throw new ClassNotFoundException(name);
		
		return cl;
	}
	
	/**
	 * 简单的解密
	 * @param name
	 * @return
	 * @throws IOException
	 */
	private byte[] loadClassBytes(String name) throws IOException{
		String cname = name.replace('.', '/') + ".class";
		
		FileInputStream in = new FileInputStream(cname);
		try {
			ByteArrayOutputStream buffer = new ByteArrayOutputStream();
			
			int ch;
			while((ch = in.read()) != -1){
				if(cname.contains("uikoo9")){// 如果包含uikoo9说明是自己写的class,进行解密
					System.out.println("++");
					buffer.write((byte)(ch-2));
				}else{
					buffer.write((byte)ch);
				}
			}
			in.close();
			
			return buffer.toByteArray();
		}finally{
			in.close();
		}
	}
}

【加入到tomcat中】

1.网上

网上很多文章都问到tomcat怎么使用自己的classloader,但是说明白的几乎没有,

最后自己读了tomcat官网的文档,找到了答案,

地址:http://tomcat.apache.org/tomcat-6.0-doc/config/loader.html

2.方法

说简单点,就是在tomcat\conf\context.xml中添加以下这段代码:

	<Loader loaderClass="com.uikoo9.MyClassLoader"></Loader >
3.classloader

但是注意,这里的com.uikoo9.MyClassLoader并不是项目中的,

而是需要放到tomcat\lib下。


【新的问题】

1.这个自己写的classloader确实起作用的,但是问题也随之而来,

原来tomcat在调用classloader之前会调用一个自己的classparser类来对class文件进行解析

2.classparser

位于org\apache\tomcat\util\bcel\classfile下的ClassParser.java,

源代码:

/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */
package org.apache.tomcat.util.bcel.classfile;

import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import org.apache.tomcat.util.bcel.Constants;

/**
 * Wrapper class that parses a given Java .class file. The method <A
 * href ="#parse">parse</A> returns a <A href ="JavaClass.html">
 * JavaClass</A> object on success. When an I/O error or an
 * inconsistency occurs an appropiate exception is propagated back to
 * the caller.
 *
 * The structure and the names comply, except for a few conveniences,
 * exactly with the <A href="ftp://java.sun.com/docs/specs/vmspec.ps">
 * JVM specification 1.0</a>. See this paper for
 * further details about the structure of a bytecode file.
 *
 * @version $Id: ClassParser.java 992409 2010-09-03 18:35:59Z markt $
 * @author <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A> 
 */
public final class ClassParser {

    private DataInputStream file;
    private boolean fileOwned;
    private String file_name;
    private String zip_file;
    private int class_name_index, superclass_name_index;
    private int major, minor; // Compiler version
    private int access_flags; // Access rights of parsed class
    private int[] interfaces; // Names of implemented interfaces
    private ConstantPool constant_pool; // collection of constants
    private Field[] fields; // class fields, i.e., its variables
    private Method[] methods; // methods defined in the class
    private Attribute[] attributes; // attributes defined in the class
    private boolean is_zip; // Loaded from zip file
    private static final int BUFSIZE = 8192;


    /**
     * Parse class from the given stream.
     *
     * @param file Input stream
     * @param file_name File name
     */
    public ClassParser(InputStream file, String file_name) {
        this.file_name = file_name;
        fileOwned = false;
        String clazz = file.getClass().getName(); // Not a very clean solution ...
        is_zip = clazz.startsWith("java.util.zip.") || clazz.startsWith("java.util.jar.");
        if (file instanceof DataInputStream) {
            this.file = (DataInputStream) file;
        } else {
            this.file = new DataInputStream(new BufferedInputStream(file, BUFSIZE));
        }
    }


    /**
     * Parse the given Java class file and return an object that represents
     * the contained data, i.e., constants, methods, fields and commands.
     * A <em>ClassFormatException</em> is raised, if the file is not a valid
     * .class file. (This does not include verification of the byte code as it
     * is performed by the java interpreter).
     *
     * @return Class object representing the parsed class file
     * @throws  IOException
     * @throws  ClassFormatException
     */
    public JavaClass parse() throws IOException, ClassFormatException {
        ZipFile zip = null;
        try {
            if (fileOwned) {
                if (is_zip) {
                    zip = new ZipFile(zip_file);
                    ZipEntry entry = zip.getEntry(file_name);
                    
                    if (entry == null) {
                        throw new IOException("File " + file_name + " not found");
                    }
                    
                    file = new DataInputStream(new BufferedInputStream(zip.getInputStream(entry),
                            BUFSIZE));
                } else {
                    file = new DataInputStream(new BufferedInputStream(new FileInputStream(
                            file_name), BUFSIZE));
                }
            }
            /****************** Read headers ********************************/
            // Check magic tag of class file
            readID();
            // Get compiler version
            readVersion();
            /****************** Read constant pool and related **************/
            // Read constant pool entries
            readConstantPool();
            // Get class information
            readClassInfo();
            // Get interface information, i.e., implemented interfaces
            readInterfaces();
            /****************** Read class fields and methods ***************/
            // Read class fields, i.e., the variables of the class
            readFields();
            // Read class methods, i.e., the functions in the class
            readMethods();
            // Read class attributes
            readAttributes();
            // Check for unknown variables
            //Unknown[] u = Unknown.getUnknownAttributes();
            //for(int i=0; i < u.length; i++)
            //  System.err.println("WARNING: " + u[i]);
            // Everything should have been read now
            //      if(file.available() > 0) {
            //        int bytes = file.available();
            //        byte[] buf = new byte[bytes];
            //        file.read(buf);
            //        if(!(is_zip && (buf.length == 1))) {
            //          System.err.println("WARNING: Trailing garbage at end of " + file_name);
            //          System.err.println(bytes + " extra bytes: " + Utility.toHexString(buf));
            //        }
            //      }
        } finally {
            // Read everything of interest, so close the file
            if (fileOwned) {
                try {
                    if (file != null) {
                        file.close();
                    }
                    if (zip != null) {
                        zip.close();
                    }
                } catch (IOException ioe) {
                    //ignore close exceptions
                }
            }
        }
        // Return the information we have gathered in a new object
        return new JavaClass(class_name_index, superclass_name_index, file_name, major, minor,
                access_flags, constant_pool, interfaces, fields, methods, attributes);
    }


    /**
     * Read information about the attributes of the class.
     * @throws  IOException
     * @throws  ClassFormatException
     */
    private final void readAttributes() throws IOException, ClassFormatException {
        int attributes_count;
        attributes_count = file.readUnsignedShort();
        attributes = new Attribute[attributes_count];
        for (int i = 0; i < attributes_count; i++) {
            attributes[i] = Attribute.readAttribute(file, constant_pool);
        }
    }


    /**
     * Read information about the class and its super class.
     * @throws  IOException
     * @throws  ClassFormatException
     */
    private final void readClassInfo() throws IOException, ClassFormatException {
        access_flags = file.readUnsignedShort();
        /* Interfaces are implicitely abstract, the flag should be set
         * according to the JVM specification.
         */
        if ((access_flags & Constants.ACC_INTERFACE) != 0) {
            access_flags |= Constants.ACC_ABSTRACT;
        }
        if (((access_flags & Constants.ACC_ABSTRACT) != 0)
                && ((access_flags & Constants.ACC_FINAL) != 0)) {
            throw new ClassFormatException("Class " + file_name + " can't be both final and abstract");
        }
        class_name_index = file.readUnsignedShort();
        superclass_name_index = file.readUnsignedShort();
    }


    /**
     * Read constant pool entries.
     * @throws  IOException
     * @throws  ClassFormatException
     */
    private final void readConstantPool() throws IOException, ClassFormatException {
        constant_pool = new ConstantPool(file);
    }


    /**
     * Read information about the fields of the class, i.e., its variables.
     * @throws  IOException
     * @throws  ClassFormatException
     */
    private final void readFields() throws IOException, ClassFormatException {
        int fields_count;
        fields_count = file.readUnsignedShort();
        fields = new Field[fields_count];
        for (int i = 0; i < fields_count; i++) {
            fields[i] = new Field(file, constant_pool);
        }
    }


    /******************** Private utility methods **********************/
    /**
     * Check whether the header of the file is ok.
     * Of course, this has to be the first action on successive file reads.
     * @throws  IOException
     * @throws  ClassFormatException
     */
    private final void readID() throws IOException, ClassFormatException {
        int magic = 0xCAFEBABE;
        if (file.readInt() != magic) {
            throw new ClassFormatException(file_name + " is not a Java .class file");
        }
    }


    /**
     * Read information about the interfaces implemented by this class.
     * @throws  IOException
     * @throws  ClassFormatException
     */
    private final void readInterfaces() throws IOException, ClassFormatException {
        int interfaces_count;
        interfaces_count = file.readUnsignedShort();
        interfaces = new int[interfaces_count];
        for (int i = 0; i < interfaces_count; i++) {
            interfaces[i] = file.readUnsignedShort();
        }
    }


    /**
     * Read information about the methods of the class.
     * @throws  IOException
     * @throws  ClassFormatException
     */
    private final void readMethods() throws IOException, ClassFormatException {
        int methods_count;
        methods_count = file.readUnsignedShort();
        methods = new Method[methods_count];
        for (int i = 0; i < methods_count; i++) {
            methods[i] = new Method(file, constant_pool);
        }
    }


    /**
     * Read major and minor version of compiler which created the file.
     * @throws  IOException
     * @throws  ClassFormatException
     */
    private final void readVersion() throws IOException, ClassFormatException {
        minor = file.readUnsignedShort();
        major = file.readUnsignedShort();
    }
}

3.问题

发现这个解析类的文件会先去判断class的头信息来确定是不是class文件,

但是由于我们对class进行了加密,所以头信息变了,所以这个解析class文件的类会报错,

也就不会调用到classloader了。


【继续】

文章有点长,不知道有人有耐心看完不。

1.上面的问题折腾了一天,才发现是自己解密的部分有问题,

2.不过也是有收获的,发现自定写的loader只能加载非class的文件,而不能加载class

3.意思就是说,你需要将原来的class文件加密并改变文件后缀,然后配合自己的loader使用

4.加密和解密两个程序:加密解密


【delegate】

由于自己英语水平有限,所以之前的tomcat文章一知半解,

通过今天的研究发现context.xml中的delegate属性的用法。

1.loader的代码:

package com.uikoo9.loader;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;

import org.apache.catalina.loader.WebappClassLoader;

/**
 * 自定义的classloader
 * 可以解密文件并加载
 * @author uikoo9
 */
public class UClassLoader extends WebappClassLoader{
	
	/**
	 * 默认构造器
	 */
	public UClassLoader() {
		super();
	}

	/**
	 * 默认构造器
	 * @param parent
	 */
	public UClassLoader(ClassLoader parent) {
		super(parent);
	}

	/* (non-Javadoc)
	 * @see org.apache.catalina.loader.WebappClassLoader#findClass(java.lang.String)
	 */
	public Class<?> findClass(String name) throws ClassNotFoundException {
		byte[] classBytes = null;
		try {
			if(name.contains("uikoo9")){
				System.out.println("++++++" + name);
				classBytes = loadClassBytesEncrypt(name);
			}else{
				System.out.println("-------" + name);
				classBytes = loadClassBytesDefault(name);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

		Class<?> cl = defineClass(name, classBytes, 0, classBytes.length);
		if (cl == null)
			throw new ClassNotFoundException(name);
		return cl;
	}
	
	@Override
	public Class<?> loadClass(String name) throws ClassNotFoundException {
		if(name.contains("uikoo9")){
			return findClass(name);
		}else{
			return super.loadClass(name);
		}
	}

	/**
	 * 加载加密后的class字节流
	 * @param name
	 * @return
	 * @throws IOException
	 */
	private byte[] loadClassBytesEncrypt(String name) throws IOException {
		String cname = name.replace('.', '/') + ".uikoo9";
		FileInputStream in = null;
		in = new FileInputStream(cname);
		try {
			ByteArrayOutputStream buffer = new ByteArrayOutputStream();
			int ch;
			while ((ch = in.read()) != -1) {
				buffer.write((byte)(ch - 2));
			}
			in.close();
			return buffer.toByteArray();
		} finally {
			in.close();
		}
	}
	
	/**
	 * 加载普通的class字节流
	 * @param name
	 * @return
	 * @throws IOException
	 */
	private byte[] loadClassBytesDefault(String name) throws IOException {
		String cname = name.replace('.', '/') + ".class";
		FileInputStream in = null;
		in = new FileInputStream(cname);
		try {
			ByteArrayOutputStream buffer = new ByteArrayOutputStream();
			int ch;
			while ((ch = in.read()) != -1) {
				buffer.write((byte)ch);
			}
			in.close();
			return buffer.toByteArray();
		} finally {
			in.close();
		}
	}
}

2.delegate="false"时,启动tomcat:

		<Loader loaderClass="com.uikoo9.loader.UClassLoader" delegate="false"></Loader >


3.delegate="true"时,启动tomcat:

		<Loader loaderClass="com.uikoo9.loader.UClassLoader" delegate="true"></Loader >

4.总结

delegate为true的时候自定义的loader只用来加载自己的代码


【新问题】

以上的代码整理一下,启动tomcat,没有报错,

但是当点击页面的时候,也就是向后台请求的时候依然报错,


【end】

经过中午的挣扎,这个问题终于解决了,

详情:http://blog.csdn.net/uikoo9/article/details/17281403

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

uikoo9

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值