org.openuat.authentication
Class InterlockProtocol

java.lang.Object
  extended by org.openuat.authentication.InterlockProtocol

public class InterlockProtocol
extends java.lang.Object

This class implements the interlock protocol as first defined in Ronald L. Rivest and Adi Shamir: "How to Expose an Eavesdropper", 1984. It uses AES, either with the JSSE/JCE or with the Bouncycastle light API, because the latter can also run on e.g. J2ME devices without JCE support. Attention:The messages that are to be encrypted by interlock are assumed not to be private, i.e. that they can be revealed after the interlock protocol has completed or that information about them can be leaked. These can e.g. be nonces or other pseudo-random streams that have meaning to the receiver (for independent checking that no man-in-the-middle attack is happening) but do not need to be concealed afterwards. Nonetheless, a random nonce is used as initialization vector (IV) when the whole plain text message does not fit into a single block (i.e. 128 Bits). Note: When the plain text message fits into 128 Bits, it is assumed to be a nonce and ECB is used. A "stream-cipher" mode like OFB or CTR might produce less overhead (the IV would not need to be transmitted), but it still needs to be analyzed if this use would compromise the security properties of the interlock protocol. Currently, I do not think that it would, but feel uncomfortable using them without further thought about the implications.

Version:
1.0
Author:
Rene Mayrhofer

Constructor Summary
InterlockProtocol(byte[] sharedKey, int rounds, int numMessageBits, java.lang.String instanceId, boolean useJSSE)
          Initializes the interlock protocol by setting all parameters that must be immutable for a single instance of the protocol.
 
Method Summary
 boolean addMessage(byte[] message, int round)
          Adds a message to the cipher text assemply.
 boolean addMessage(byte[] message, int offset, int numBits, int round)
          Adds a message to the cipher text assemply.
static void addPart(byte[] dest, byte[] src, int bitOffset, int bitLen)
          Small helper function to add a part to a byte array.
 byte[] decrypt(byte[] cipherText)
          Decrypt the cipher text message with the shared key set in the constructor.
 byte[] encrypt(byte[] plainText)
          Encrypt the plain text message with the shared key set in the constructor.
static void extractPart(byte[] dest, byte[] src, int bitOffset, int bitLen)
          Small helper function to extract a part from a byte array.
 int getCipherTextBlocks()
          Returns the number of cipher text blocks necessary to encode the message.
static byte[] interlockExchange(byte[] message, java.io.InputStream fromRemote, java.io.OutputStream toRemote, byte[] sharedKey, int rounds, boolean retransmit, int timeoutMs, boolean useJSSE)
          This method runs a complete interlock exchange with another host.
 byte[] reassemble()
          This method only checks that all rounds have actually been received (i.e.
 byte[] reassemble(byte[][] messages)
          This method is the inverse of split().
 byte[][] split(byte[] cipherText)
          This method splits the cipher text into multiple parts for transmission in an interlocked way.
 
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
 

Constructor Detail

InterlockProtocol

public InterlockProtocol(byte[] sharedKey,
                         int rounds,
                         int numMessageBits,
                         java.lang.String instanceId,
                         boolean useJSSE)
Initializes the interlock protocol by setting all parameters that must be immutable for a single instance of the protocol.

Parameters:
sharedKey - The shared key to use for encryption and decryption.
rounds - The number of rounds to use for the protocol. Must be at least 2 and at most equal to the number of bits in the plain text message.
numMessageBits - The size of the plain text message that should be transmitted, measured in Bits.
useJSSE - If set to true, the JSSE API with the default JCE provider of the JVM will be used for cryptographic operations. If set to false, an internal copy of the Bouncycastle Lightweight API classes will be used.
instanceId - This parameter may be used to distinguish differenc instances of this class running on the same machine. It will be used in logging and error messages. May be set to null.
Method Detail

encrypt

public byte[] encrypt(byte[] plainText)
               throws InternalApplicationException
Encrypt the plain text message with the shared key set in the constructor. If the message length equals the block size of the cipher, it is assumed to be a nonce and is encrypted as a single block in ECB mode. If it is larger, it is encrypted in CBC mode with a random IV prepended.

Parameters:
plainText - The message to encrypt. It must contain exactly as many bits as specified in the numMessageBits parameter in the constructor.
Returns:
The ciper text, which is either one block long or the number of blocks necessary to encrypt numMessageBits plus one block for the IV.
Throws:
InternalApplicationException

decrypt

public byte[] decrypt(byte[] cipherText)
               throws InternalApplicationException
Decrypt the cipher text message with the shared key set in the constructor. If the message length equals the block size of the cipher, the plain text is assumed to have been a nonce and is decrypted as a single block in ECB mode. If it is larger, it is decrypted in CBC mode with a random IV prepended.

Parameters:
cipherText - The cipher text to decrypt. It must be either one block long or the number of blocks necessary to encrypt numMessageBits plus one block for the IV.
Returns:
The ciper text, which contains exactly as many bits as specified in the numMessageBits parameter in the constructor.
Throws:
InternalApplicationException

split

public byte[][] split(byte[] cipherText)
               throws InternalApplicationException
This method splits the cipher text into multiple parts for transmission in an interlocked way. The caller has to make sure that part i+1 is not sent until the other host has acknowledged receipt of part i and sent its part i. If this is not followed, the interlock protocol is not secure! How the splitting is done depends on the mode of the protocol: In the single-block case, the cipher text consists of just a single block encrypted by one call to the block cipher. Thus, this block is simply split into equally sized parts (besides the last one) taken one after the other from the cipher text. In the multi-block case, all blocks are split as in the single-block case and the parts of the independent blocks are then concatenated to form the parts. This makes sure that no single block can be decrypted before all of the message parts have been delivered.

Parameters:
cipherText - The cipher text to split.
Returns:
As many parts as there are rounds in the protocol in the first dimension of the returned array. The last arrays may be set to null if the cipher text does not split cleanly and the last rounds therefore do not contain any bits. The last array that is not null may be smaller than the previous arrays, but all other are equally sized. The last byte in each array might be padded with 0 on the top, i.e. the array will be filled from the LSB part.
Throws:
InternalApplicationException

reassemble

public byte[] reassemble(byte[][] messages)
                  throws InternalApplicationException
This method is the inverse of split(). All comments there apply here.

Parameters:
messages - The parts to reassemble.
Returns:
The assembled cipher text.
Throws:
InternalApplicationException

reassemble

public byte[] reassemble()
This method only checks that all rounds have actually been received (i.e. that they have been added with one of the addMessage methods) and, if everything is ok, returns the assmbled cipher text.

Returns:
The assembled cipher text if it has been received completely, or null in case of an error.
See Also:
addMessage(byte[], int), addMessage(byte[], int, int, int), assembledCipherText

addMessage

public boolean addMessage(byte[] message,
                          int offset,
                          int numBits,
                          int round)
                   throws InternalApplicationException
Adds a message to the cipher text assemply. This method should only be used if not the whole message is to be transferred and/or the application has very specific needs concerning re-assembly. Better use the simple addMessage variant unless sure you need this method.

Parameters:
message - The message to add.
offset - The bit offset where this message part starts in the reassembly.
numBits - The number of bits to take from the message array.
round - The round number of this message. Rounds are counted from 0 to rounds-1.
Returns:
true if added successfully, false if not added. When false is returned, then the part for this round has already been added earlier.
Throws:
InternalApplicationException

interlockExchange

public static byte[] interlockExchange(byte[] message,
                                       java.io.InputStream fromRemote,
                                       java.io.OutputStream toRemote,
                                       byte[] sharedKey,
                                       int rounds,
                                       boolean retransmit,
                                       int timeoutMs,
                                       boolean useJSSE)
                                throws java.io.IOException,
                                       InternalApplicationException
This method runs a complete interlock exchange with another host. To this end, this method must be started on both sides with the same values for the number of rounds and, obviously, for the shared key.

Parameters:
message - The message to send to the remote host.
fromRemote - This stream is used for receiving bytes from the remote host. This method takes care not to consume any more bytes than stricly necessary, so that this stream can be re-used for subsequent communication betweem the hosts.
toRemote - This stream is used for sending bytes to the remote host.
sharedKey - The shared key to use for encryption and decryption. Must be equal on both sides or the messages will not decrypt successfully (and this indicates a man-in-the-middle attack).
rounds - The number of rounds to use.
retransmit - Is set to true, lost messages are allowed and rounds will be retransmitted until the other end acknowledges it or a timeout occurs. THIS IS CURRENTLY NOT IMPLEMENTED. SET TO FALSE.
timeoutMs - If retransmit is set to true, every round will be limited to take this amount of milliseconds. If the other side has not acknowledged the receipt within this time, the protocol aborts. THIS IS CURRENTLY NOT IMPLEMENTED.
Returns:
The message that the remote host sent, or null if the interlock protocol could not be completed successfully.
Throws:
java.io.IOException
InternalApplicationException

addMessage

public boolean addMessage(byte[] message,
                          int round)
                   throws InternalApplicationException
Adds a message to the cipher text assemply. This is a convenience wrapper around the other addMessage method, which computes offset and numBits appropriately, assuming that the whole message (whose length was given to the constructor) is to be transmitted within the interlock rounds. This method should be used in favor of the other one, unless the application has very specific needs.

Parameters:
message - The message to add.
round - The round number of this message. Rounds are counted from 0 to rounds-1.
Returns:
true if added successfully, false if not added. When false is returned, then the part for this round has already been added earlier. It still returns true when the part was not added because the cipher text was already complete without it (this can only happen when the number of bits to be transmitted fits into less rounds than were used).
Throws:
InternalApplicationException

extractPart

public static void extractPart(byte[] dest,
                               byte[] src,
                               int bitOffset,
                               int bitLen)
                        throws InternalApplicationException
Small helper function to extract a part from a byte array. This method is only public for the JUnit tests, there's probably not much use for it elsewhere.

Parameters:
dest - The byte array to put the part into. It is assumed that it has been allocated with sufficient length.
src - The byte array from which the part should be extracted. It will be taken from the LSB part.
bitOffset - The number of bits to shift src before adding to dest.
bitLen - The number of bits to add from src to dest.
Throws:
InternalApplicationException

addPart

public static void addPart(byte[] dest,
                           byte[] src,
                           int bitOffset,
                           int bitLen)
                    throws InternalApplicationException
Small helper function to add a part to a byte array. This method is only public for the JUnit tests, there's probably not much use for it elsewhere.

Parameters:
dest - The byte array to add to. It is assumed that it has been allocated with sufficient length.
src - The part to add to dest. It will be added from the LSB part.
bitOffset - The number of bits to shift src before adding to dest.
bitLen - The number of bits to add from src to dest.
Throws:
InternalApplicationException

getCipherTextBlocks

public int getCipherTextBlocks()
Returns the number of cipher text blocks necessary to encode the message.



2005-2006, Rene Mayrhofer.