In the next three assignments, you will build code to implement a secure facility for client-server communication across the Internet. You should work on this assignment by yourself and we strongly recommend that you get started early.
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.
We are giving you one fully implemented code file, on which you should build all of the crypto functionality you need to do this assignment. The file PRF.java gives you access to a pseudo-random function, as described in lecture. This is the only crypto primitive you are allowed to use---any other crypto you use must be built (by you) on top of this file. Specifically, you may not use any other crypto libraries, not even the ones that are part of the standard Java libraries.
In this assignment you will implement two facilities, by modifying two Java code files. You will modify PRGen.java to implement a pseudo-random generator class. You will modify StreamCipher.java to implement a stream cipher. In each case, we have provided you with a code file 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.
PRGen. Your PRGen class should implement the following API:
public class PRGen extends Random { public PRGen(byte[] key) //creates a new PRGen protected int next(int bits) //generates the next pseudorandom number }
You do not need to implement any other methods of the Random class. The long key that is used by the parent Random class will not be used by your PRGen class.
As stated in the Random spec "The general contract of next is that it returns an int value and if the argument bits is between 1 and 32 (inclusive), then that many low-order bits of the returned value will be (approximately) independently chosen bit values, each of which is (approximately) equally likely to be 0 or 1." For example, if you call next(4), it will return an int between 0 and 15. If you call next(32), it will return an int between -2,147,483,648 and 2,147,483,647 (the highest order bit determines the sign).
Your PRGen must obey the following three properties:
StreamCipher. Your StreamCipher class should implement the following API:
public class StreamCipher { public StreamCipher(byte[] key, byte[] nonceArr, int nonceOffset) public StreamCipher(byte[] key, byte[] nonce) public byte cryptByte(byte in) //encrypts the next byte public void cryptBytes(byte[] inBuf, int inOffset, //encrypts next numBytes byte[] outBuf, int outOffset, //in the inBuf, writing int numBytes) //results to the outBuf }This class encrypts or decrypts a stream of bytes, using a stream cipher. (Recall that for a stream cipher, encryption and decryption are the same operation.)
Tips for Getting Started. This list may grow in response to Piazza questions.
Advice on testing crypto code. As always, it's important to test your code. But you should be aware that crypto code presents different testing issues than other code does. Testing can sanity-check your code, but it can't verify that your code has the desired security properties. For example, if your code is encrypting data for confidentiality, you can test whether the ciphertext is the right size, and you can test whether the ciphertext looks kind of randomish, and you can test whether different plaintexts yield different ciphertexts. But you can't test whether there is a way for an adversary to recover the plaintext. So by all means test your code --- if you don't, it's almost certain not to work --- but remember that passing the tests is not enough.
Submitting your solution. You should only submit 2 files: PRGen.java, StreamCipher.java. You may submit any number of times. Only your most recent submission will be graded. Submit your code using this link.
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 assignment 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 assignment 1, your solution to this homework should not rely on any specific properties of your assignment 1 code. We will test your solution with our own implementation of the assignment 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. When decodeOaep() fails integrity checks, it should reveal this by returning null or throwing an exception. 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:
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.
Submitting your solution. You should only submit 3 files: RSAKeyPair.java, RSAKey.java, and KeyExchange.java. 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.
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 solutions to Assignments 1 and 2. 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. If you'd like to use our reference solutions for assignment 1 and 2, you may download them here after the submission deadline of assignment 2. Note that this library does not contain source code.
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 the network, with the confidentiality and integrity of messages guaranteed. We have given you a class InsecureChannel which implements a channel that works but is not secure: everything is sent in unprotected cleartext. We have also given you stubbed-out code for a class SecureChannel that extends InsecureChannel and (once you have modified it) will protect security and confidentiality.
To facilitate the testing process, we have provided a few test files. The first is Util432s.java, which provides a few methods used by the other testing files (feel free to use these methods for debugging). The second is ChannelTest.java, which provides a demonstration that the InsecureChannel class works correctly. The third is SecureChannelTest, which you can use to test your SecureChannel class (once implemented). Note that this class does NOT test security properties, instead testing only basic functionality. Note the commented out lines which give an example of how you can Util432s for debugging. The fourth is InsecureChannelDebug, which is a special version of InsecureChannel which provides the ability to echo channel transmissions to the screen. To use this class, simply rename the file "InsecureChannel.java" and place it in the same directory as SecureChannel.java.
Although your solution will call on code that you wrote for Assignments 1 and 2, we will test your solution with our own implementation of the Assignment 1 and 2 functionality. Your solution must work correctly when we do this --- this shouldn't be a problem for you as long as you respect the API requirements of the previous assignments.
IMPORTANT: For this assignment, we will also REQUIRE a README file. In the file, you should describe your setup and your threat model: What are you doing? How would a user of the classes you've written use them? And what security properties (against what sort of adversary) should they expect to get if they use them correctly? This should be in addition to any documentation you would normally put in comments or a README.
We're not looking for War and Peace in this (i.e. it doesn't need to be very long). Rather, you should provide a clean and clear description of your security goals and how they are achieved in a few paragraphs at most.
AuthEncryptor.Your AuthEncryptor class should implement the following API:
public class AuthEncryptor { public AuthEncryptor(byte[] key) public byte[] encrypt(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 decrypting the value using the same key and nonce (that were used to encrypt it) can verify that nobody has tampered with the value since it was encrypted.
Code that uses AuthEncryptor will be required to pass in a different nonce for every call to encrypt. 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 encrypt. 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.Your AuthDecryptor class should implement the following API:
public class AuthDecryptor { public AuthDecryptor(byte[] key) public byte[] decrypt(byte[] in) public byte[] decrypt(byte[] in, byte[] nonce) }
The value passed as in will normally have been created by calling encrypt() 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 encrypt() 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 encrypt().
If the nonce is included in the message, then the message should be decrypted with decrypt(byte[] in). Otherwise, the nonce should be provided along with the ciphertext to decrypt(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(). 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. receiveMessage should return null if an invalid or out-of-order message shows up.
Assignment Tips and Tricks. This list will definitely grow in response to Piazza questions.
Copyright 1998-2017, Edward W. Felten.