EJOE – add encryption features to protect your network traffic

EJOE has a lot of great features. It is easy to use and plays well with a lot of serialization frameworks out of the box. While using it’s RemoteReflection one can publish business logic over network without writing one line of code (well, except the three or four lines of code required to start the EJOE-server component). With EJOE you can easily add networking features to existing applications, create new client-server applications or create a completely new client-server framework or remoting solution. There is even a Crispy integration available.

But one thing is missing: It doesn’t support encryption. Neither SSL nor any other known enryption method.

Is it really so hard to add encryption features to EJOE?
No it isn’t. It’s amazingly easy to add at least symetric encryption features to EJOE. But to be able to understand how one could achieve this, you have to understand how EJOE basically works:

In EJOE network communication is handled between one or more clients (instances of EJClient) and two serverside threadpools which are responsible for reading client requests, calling business logic (through so called ServerHandlers) and sending server responses back to the client.

On both, client- and serverside, data will be serialized from java objects to a so called serialized form and deserialized back to java objects. The serialized form means a transportable representation of the used java objects (beans, pojos, plain java types like String or BigDecimal) and can be XML, JSON, text, binary or what else you are able to imagine.

The networking layer is hidden. One can not get access to the underlying java processes or to the used sockets. So we can forget to add encryption features to the network layer itself. But EJOE makes it very easy to plug-in additional adapters to support new serialization frameworks. Those so called SerializeAdapter are responsible to serialize objects (client requests and server responses) into the serialized form and reverse.

To add a custom SerializeAdapter to EJOE, the interface de.netseeker.ejoe.SerializeAdapter has to be implemented and the new adapter must be added to a file called ejoe-adapter-conf.properties. This file must be available on the classpath.

So let us concentrate on this: If we can get access to the message objects in EJOE, we should be able to add some kind of message based encryption. Asymetric key algorithms are out of topic because we can not get access to EJOEs network layer. So we will focus on kind of “data stream encryption” using a symetric key algorithm.

Ok, the idea is born. We need a custom SerializeAdapter to add encryption. On the other hand we don’t want to deal with serialization itself. We just want add enryption features to already existing SerializeAdapters. So why not implement kind of a WrapperAdapter which is able to wrap any other SerializeAdapter?
This WrapperAdapter would just delegate the de-/serialization tasks to another adapter and

  • plug-in encryption before delegating serialisation to the wrapped SerializeAdapter
  • plug-in decryption before delegating deserialisation to the wrapped
    SerializeAdapter.

A simple “WrapperAdapter” without any encryption features could just look like this:

import java.io.InputStream;
import java.io.OutputStream;

import de.netseeker.ejoe.adapter.BaseAdapter;
import de.netseeker.ejoe.adapter.SerializeAdapter;

/**
 * A simple non-sense wrapper adapter which just is able to wrap
 * any other SerializeAdapter.
 * @author netseeker
 */
public class WrapperAdapter extends BaseAdapter
{
    private static final long serialVersionUID = 1L;

    private SerializeAdapter  parent;

    /**
     *
     */
    public WrapperAdapter(SerializeAdapter parentAdapter)
    {
        parent = parentAdapter;
    }

    /*
     * (non-Javadoc)
     *
     * @see de.netseeker.ejoe.adapter.SerializeAdapter#read(java.io.InputStream)
     */
    public Object read( InputStream in ) throws Exception
    {
        return parent.read( in );
    }

    /*
     * (non-Javadoc)
     *
     * @see de.netseeker.ejoe.adapter.SerializeAdapter#write(java.lang.Object, java.io.OutputStream)
     */
    public void write( Object obj, OutputStream out ) throws Exception
    {
        parent.write( obj, out );
    }
}

As you can see in the previous example, SerializeAdapters use In- and OutputStreams in EJOE. So for encryption we should choose a stream cipher and not a block cipher.
Because we have to use a symetric key algorithm, which means that clients and server will share the same key for encryption as well as for decryption, we need a cipher with no known attacks when using the same encryption key more than once.

The well known RC4 (ARCFOUR), which is used by WEP, WPA, SSL and also within PGP, has a weak key schedule so that we would have to combinate the long term main key with a short term nonce. But how could we create such a nonce on the client and ensure that the server is able to re-create the same nonce?
Within a SerializeAdapter and without access to any kind of session-context or information about the socket this does not seem to be possible.

So instead of RC4 we will use the HC-128 cipher which is more secure when using the same long term key more than once.

Thanks to the Legion of Bouncy Castle and their HC-implementation for java, this will be as easy as using the default ARCFOUR-cipher of the SunJCE Provider:

package de.netseeker.ejoe.examples.crypto;

import java.io.InputStream;
import java.io.OutputStream;
import java.security.spec.AlgorithmParameterSpec;

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;

import org.bouncycastle.util.encoders.Hex;

import de.netseeker.ejoe.adapter.BaseAdapter;
import de.netseeker.ejoe.adapter.SerializeAdapter;
import de.netseeker.ejoe.io.IOUtil;

/**
 * A simple wrapper adapter using Bouncy Castles HC-128 stream cipher implementation
 * for symetric key encryption.
 * @author netseeker
 */
public class HC128Adapter extends BaseAdapter
{
    private static final long      serialVersionUID = 1L;

    private static final byte      HC128[] =
                                    Hex.decode("731500823bfd03a0fb2fd77faa63af0e"
                                         + "de122fc6a7dc29b662a685278b75ec68"
                                         + "9036db1e8189600500ade078491fbf9"
                                         + "a1cdc30136c3d6e2490f664b29cd57102" );

    private SecretKey              secKey           = null;
    private SerializeAdapter       parent           = null;
    private AlgorithmParameterSpec paramSpec        = null;

    /**
     * @param parentAdapter
     * @param key
     */
    public HC128Adapter(SerializeAdapter parentAdapter, SecretKey key)
    {
        secKey = key;
        parent = parentAdapter;
        paramSpec = new IvParameterSpec( HC128 );
    }

    /*
     * (non-Javadoc)
     *
     * @see de.netseeker.ejoe.adapter.SerializeAdapter#read(java.io.InputStream)
     */
    public Object read( InputStream in ) throws Exception
    {
        Object result = null;
        Cipher inCipher = Cipher.getInstance( "HC128", "BC" );
        inCipher.init( Cipher.DECRYPT_MODE, secKey, paramSpec );
        CipherInputStream cipherinputstream = null;
        try
        {
            cipherinputstream = new CipherInputStream( in, inCipher );
            result = parent.read( cipherinputstream );
        }
        finally
        {
            IOUtil.closeQuiet( cipherinputstream );
        }
        return result;
    }

    /*
     * (non-Javadoc)
     *
     * @see de.netseeker.ejoe.adapter.SerializeAdapter#write(java.lang.Object, java.io.OutputStream)
     */
    public void write( Object obj, OutputStream out ) throws Exception
    {
        Cipher outCipher = Cipher.getInstance( "HC128", "BC" );
        outCipher.init( Cipher.ENCRYPT_MODE, secKey, paramSpec );
        CipherOutputStream cipheroutputstream = null;
        try
        {
            cipheroutputstream = new CipherOutputStream( out, outCipher );
            parent.write( obj, cipheroutputstream );
            cipheroutputstream.flush();
        }
        finally
        {
            IOUtil.closeQuiet( cipheroutputstream );
        }
    }
}

To use the new HC128Adapter:

  1. create a SecretKey and store it into a JCEKS-keystore (Take a look here if you do not have any glue how to create a SecretKey and store it into a keystore.)
  2. deploy the keystore in your server as well as distribute the keystore with your client application.
  3. on server side register the new HC128Adapter in EJOEs AdapterFactory before starting EJServer:
    AdapterFactory.registerAdapter(
        new HC128Adapter( new XStreamAdapter(), secKey ) );
    EJServer server = new EJServer( ... );
    ...
    server.start();
  4. on client side just instantiate EJClient with the new HC128Adapter:
    EJClient client = new EJClient( "myServerHostName",
        9999,
        new HC128Adapter( new XStreamAdapter(), secKey ) );

Do not forget to provide a custom ejoe-adapter-conf.properties with the following entry on the classpath:

de.netseeker.ejoe.examples.crypto.HC128Adapter=true

Otherwise you will not be able to use the new adapter, because EJOE does not allow unconfigured Adapters by default for security reasons.

A more complete and fully functional example can be found in EJOEs CVS-tree at sourceforge:
http://ejoe.cvs.sourceforge.net/ejoe/EJOE/examples/de/netseeker/ejoe/examples/crypto/.

Bookmark and Share
No Comments
November 20, 2007 in EJOE

Leave a Reply

Using Gravatars in the comments - get your own and be recognized!

XHTML: These are some of the tags you can use: <a href=""> <b> <blockquote> <code> <em> <i> <strike> <strong>