Homework 2

Due at 11:55pm, Tuesday, October 13th

In this assignment, you'll add functionality to the code you wrote for Homework 1, toward 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'll ask you to provide certain functions missing from the code we provide. You can download the code we are providing. Create a fresh directory and unzip the downloaded code into it. Then copy into that same directory all of the .java files from your solution to Homework 1. As before, you must not use any crypto libraries; the only primitives you may use are the ones we gave you, and ones you implemented from scratch yourself.

In this assignment you will implement three facilities, by modifying three Java code files. You will modify RSAKeyPair.java to generate an RSA key-pair. You will modify RSAKey.java to implement secure RSA encryption and decryption, and to create and verify digital signatures. You will modify KeyExchange.java to implement a secure key exchange. As in the previous assignment, we have provided you with code files in which some parts are "stubbed out". You will replace the stubbed out pieces with code that actually works and provides the required security guarantee. We have put a comment saying "IMPLEMENT THIS" everywhere that you have to supply code.

Although your solution may call on code that you wrote for Homework 1, your solution to this homework should not rely on any specific properties of your Homework 1 code. We will test your solution with our own implementation of the Homework 1 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.

RSAKeyPair. Your RSAKeyPair class should implement the following API:

public class RSAKeyPair {
    public RSAKeyPair(PRGen rand, int numBits)    
    public RSAKey getPublicKey()                   //already implemented
    public RSAKey getPrivateKey()                  //already implemented
    public BigInteger[] getPrimes()                
}

For RSAKeyPair, the bulk of the interesting work is performed by the constructor. This constuctor should create an RSA key pair using the algorithm discussed in class. The constructor will use the PRGen called rand to get pseudorandom bits. numBits is the size in bits of each of the primes that will be used. The key pair should be stored as a pair of RSAKey objects.

getPrimes() is a method we've added in order to help us with the grading process. getPrimes() should return the two primes that were used in key generation. Typically, you would not explicitly have a method to return these primes. The primes may be returned in either order.

RSAKey. Your RSAKey class should implement the following API:

public class RSAKey {
    public RSAKey(BigInteger theExponent, BigInteger theModulus) // already implemented
    public BigInteger getExponent()                              // already implemented   
    public BigInteger getModulus()                               // already implemented
    public byte[] encrypt(byte[] plaintext, PRGen prgen)                        
    public byte[] decrypt(byte[] ciphertext)
    public byte[] sign(byte[] message, PRGen prgen) 
    public boolean verifySignature(byte[] message, byte[] signature) 
    public int maxPlaintextLength()
    public byte[] encodeOaep(byte[] input, PRGen prgen)
    public byte[] decodeOaep(byte[] input)
    public byte[] addPadding(byte[] input)
    public byte[] removePadding(byte[] input)

The RSAKey class implements core RSA functions, namely encrypting/decryption as well as signing/verification. Note that the RSAKey class is used for both public and private keys, even though some key/method combinations are unlikely to be used in practice. For example, it is unlikely that the sign() method of a public RSAKey would ever be used.

The encrypt() method should encrypt the plaintext using optimal asymmetric encryption padding (OAEP) as discussed in class. It is not enough to simply exponentiate and mod the plaintext. encrypt(), sign(), and encodeOaep() take a PRGen parameter, in case the implementation wants to use some pseudorandom bits. The decrypt() method should be able to decrypt the ciphertext.

Your code for OAEP encoding and decoding should be in the provided encodeOaep() and decodeOaep() methods. Your other methods should call these utility methods to encode/decode when necessary. For full credit, don't forget to pad the input to the OAEP algorithm if it is too short -- this is necessary to guarantee security (otherwise the exponentiated message might be smaller than the modulus).

The sign() method should generate a signature (array of bytes) that can be verified by the verifySignature() method of the other RSAKey in the private/public RSAKey pair. You should not include the entire message as part of the signature; assume that the verifier will already have access to this message. This assumption of access is reflected in the API for verifySignature(), which accepts the message as one of its arguments.

The verifySignature() method should be used by a public RSAKey object to verify a signature generated by the corresponding private RSAKey's sign() method.

The maxPlaintextLength() method should return the largest N such that any plaintext of size N bytes can be encrypted with this key and padding scheme. Your code must correctly operate on plaintexts that are any size less than or equal to the size returned by maxPlaintextLength().

The addPadding() and removePadding() methods are used to pad the input to the OAEP algorithm if it is too short. You should not call these methods from within encodeOAEP()/decodeOAEP(). See below for more information on this.

KeyExchange. Your KeyExchange class should implement the following API:

public class KeyExchange {
    public static final int OUTPUT_SIZE_BYTES
    public static final int OUTPUT_SIZE_BITS
    public KeyExchange(PRGen rand, boolean iAmServer)
    public byte[] prepareOutMessage() 
    public byte[] processInMessage(byte[] inMessage) 
}

The constructor should prepare to do a key exchange. rand is a secure pseudorandom generator that can be used by the implementation. iAmServer is true if and only if we are playing the server role in this exchange. Each exchange has two participants; one of them plays the client role and the other plays the server role.

Once the KeyExchange object is created, two things have to happen for the key exchange process to be complete:

  1. Call prepareOutMessage on this object, and send the result to the other participant.
  2. Receive the result of the other participant's prepareOutMessage, and pass it in as the argument to a call on this object's processInMessage.
These two things can happen in either order, or even concurrently (e.g., in different threads). This code must work correctly regardless of the order.

The call to processInMessage should behave as follows:

Your KeyExchange class must provide the following security guarantee: If the two participants end up with the same non-null digest value, then this digest value is not known to anyone else. This must be true even if third parties can observe and modify the messages sent between the participants.

This code is NOT required to check whether the two participants end up with the same digest value; the code calling this must verify that property.

Assignment Tips and Tricks. This list may grow in response to Piazza questions.

Submitting your solution. You should submit any code files that you modified or created. You don't need to submit any files that you did not modify. Please cite any sources you used when developing your code. You may submit any number of times. Only your most recent submission will be graded. Submit your code using this link.


Copyright 1998-2015, Edward W. Felten.