Assignment 3: Authenticated Key Exchange
Introduction

In this assignment, you will add functionality to the code you wrote for Assignments 1 and 2, to reach the goal of implementing a secure facility for client-server communication across the Internet.

As before, we will give you some of the code you need, and we will ask you to provide certain functions missing from the code we provide. As before, you must not use any cryptographic libraries; the only primitives you may use are the ones we gave you, and ones you implemented from scratch yourself. Your solution will build on the functionality that you implemented for Assignment 2, but we will test your solution with our own implementation of the Assignment 2 functionality. Your solution must work correctly when we do this — this shouldn’t be a problem as long as you respect the API boundaries between the different classes we have given you. To help you mimic the testing setup that we will use, we are providing a pre-compiled JAR file (library) containing our reference solution to Assignment 2. We recommend that you use this instead of your own solution to Assignment 2.

In this assignment you will implement a secure channel abstraction that can be used by two programs, a client and a server, to communicate across a network, with the confidentiality and integrity of messages guaranteed. We have given you a class InsecureChannel that implements a channel that works but is not secure: everything is sent in unprotected cleartext. We have also given you stubbed-out code for the SecureChannel class that extends InsecureChannel and (once you have modified it) will protect security and confidentiality.

To facilitate the testing process, we have provided the following files:

AuthEncryptor.java

Your AuthEncryptor class should implement the following API:

public class AuthEncryptor {
   public AuthEncryptor(byte[] key)
   public byte[] authEncrypt(byte[] in, byte[] nonce, boolean includeNonce)
}

This class is used to perform authenticated encryption on values. Authenticated encryption protects the confidentiality of a value, so that the only way to recover the initial value is to decrypt the value using the same key and nonce that was used to encrypt it. At the same time, authenticated encryption protects the integrity of a value, so that a party possessing the same key and nonce (that were used to authenticate it) can verify that nobody has tampered with the value.

Code that uses AuthEncryptor will be required to pass in a different nonce for every call to the authEncrypt method. The AuthEncryptor class is not required to detect violations of this rule; it is the responsibility of the code that uses AuthEncryptor to avoid re-using a nonce with the same AuthEncryptor instance.

If includeNonce is true, then the nonce should be included (in plaintext form) in the output of authEncrypt. If includeNonce is false, then the nonce should still be used in calculating the output, but the nonce itself should not be copied into the output. (Presumably the party who will decrypt the message already knows what the nonce will be.)

AuthDecryptor.java

Your AuthDecryptor class should implement the following API:

public class AuthDecryptor {
   public AuthDecryptor(byte[] key)
   public byte[] authDecrypt(byte[] in)
   public byte[] authDecrypt(byte[] in, byte[] nonce)
}

The value passed as in will normally have been created by calling authEncrypt() with the same nonce in an AuthEncryptor that was initialized with the same key as this AuthDecryptor.

If the integrity of the input value cannot be verified (that is, if the input value could not have been created by calling authEncrypt() with the same nonce in an AuthEncryptor that was initialized with the same key as this AuthDecryptor), then this method returns null. Otherwise it returns a newly allocated byte-array containing the plaintext value that was originally passed to authEncrypt().

If the nonce is included in the message, then the message should be processed with the authDecrypt(byte[] in) method. Otherwise, the nonce should be provided along with the ciphertext to authDecrypt(byte[] in, byte[] nonce).

SecureChannel

Your SecureChannel class should implement the following API:

public class SecureChannel extends InsecureChannel {
   public SecureChannel(InputStream inStr, OutputStream outStr,
                        PRGen rand, boolean iAmServer,
                        RSAKey serverKey) throws IOException
   public void sendMessage(byte[] message) throws IOException
   public byte[] receiveMessage() throws IOException
}

The constructor will contain the vast majority of your code. Its role is to set up the secure channel such that the sendMessage and receiveMessage methods can do their jobs. These methods should provide authenticated encryption for the messages that pass over the channel, ensuring that messages arrive at the receiving end in the same order that they were send on the sending end. Furthermore, when the client is setting up its channel, it should also authenticate the server’s identity, and should take whatever steps are necessary to detect any man-in-the-middle. If one of the two parties (server or client) detects a potential security problem during channel construction, that party should close the channel by calling close(). To ensure foward secrecy, the close() method must delete any secret values associated with the channel. You can assume the serverKey (public key) passed to the constructor of SecureChannel on the client side of the communication is verified externally in some way (for example via a trusted certificate).

The underlying InsecureChannel will normally deliver messages in the same order they were sent. But note that an adversary might try to reorder messages. The receiveMessage method should return null if an invalid or out-of-order message shows up.

Getting Started
Submission Checklist

Submit your files to the Gradescope: