Computer Science 461

Distributed Computing and Networking

Fall 1997


Assignment 3

In this assignment you will implement secure communication in AcmeNet, using the facilities you built in previous assignments. Your solution will allow clients and services to have private, cryptographically protected conversations, and it will allow a process to verify which user is running the process at the other end of a connection.

[Note: though the protocols used in this assignment offer some security, you shouldn't consider them to be great examples of how to provide high security in a real product. At several points in the design we chose to ignore minor vulnerabilities in order to keep the assignment from getting more complicated. You can read about the weaknesses of these protocols if you're interested.]

In this assignment, you will build two layers of software. First, you will implement private communication, on top of your solution to Assignment 2. Then you will use the private communication facilities to implement secure (private and authenticated) communication.

Ciphers, Keys, and Random Numbers

This assignment uses cryptography in various places. We have provided you with the AcmeNet.Util.AcmeCipher class which implements the encryption/decryption algorithm you should use. To encrypt a stream of data, create an AcmeCipher object and call its crypt method with each byte of the stream. To decrypt the encrypted stream, do exactly the same thing. Of course, decryption won't give you back the original plaintext unless you decrypt with the same key that was used to encrypt. Be sure to create a separate AcmeCipher object for every stream that you want to encrypt or decrypt.

The AcmeCipher uses 64-bit keys, which are represented as Java longs. At various places in the protocol, the documentation says to use a "fresh key". This means that you should generate a key randomly, using the AcmeNet.Util.SecureRandom.generator random number generator. Other keys are chosen according to specific algorithms that will be specified.

When you need to generate a "random" number that is really unpredictable, use the AcmeNet.Util.SecureRandom class. (The generator field of that class is a SecureRandom generator that is preinitialized for you. It implements all of the methods of java.util.Random.) Normal pseudorandom number generators are predictable, in the sense that somebody who observes the output of the generator for a while can predict all of the generator's future output. For cryptographic applications, that's a problem. You need a generator that's strongly unpredictable, so that an adversary who has seen the entire output of the generator can't predict what value it will generate next. The SecureRandom generator is strongly unpredictable in this sense.

Note that the SecureRandom class may take a long time to initialize --- ten seconds or more on some machines. Don't be fooled into thinking your program is defective just because it is frozen waiting for SecureRandom to initialize. Sorry, but it really takes that long to measure enough of the ever-changing state of the machine to generate a truly unpredictable initial seed for the generator.

Private Communication

The private communication layer is implemented by the classes AcmeNet.Assn3.PrivateConnection and AcmeNet.Assn3.PrivateService, which you must write. The PrivateConnection constructor takes an AcmeNet.Assn2.Connection that has already been set up, and establishes private communication on that Connection.

Establishing private communication goes in two stages. First, the two parties engage in the Diffie-Hellman (DH) key exchange protocol to choose a shared key. The mathematical operations required by the DH protocol are contained in the class AcmeNet.Assn3.DiffieHellman, which we have provided for you. The DH protocol requires each side to send the other a single large integer. You should do this by translating the large integer into a String (using the toString method of java.math.BigInteger), sending the string, and then translating back into a java.math.BigInteger on the receiving end.

The result of the DH algorithm is a shared secret in the form of a byte-array. Running AcmeNet.Util.Login.keyFromByteArray will give you a 64-bit (Java long) secret that is shared between the two parties. At this point, it's tempting to set up encrypted communication, using the same 64-bit key in both directions on the Connection. But this turns out to be cryptographically weak; we have to use different keys in the two directions. So we have each party generate a random 64-bit (long) value, and send it to the other party. Then each party sets up encrypted communication. On its outgoing stream it encrypts using (sharedKey+randValueSent) as the key; on its incoming stream it decrypts using (sharedKey+randValueReceived) as the key.

Secure Communication

The secure communication layer is implemented by the classes AcmeNet.Assn3.SecureConnection and AcmeNet.Assn3.SecureService, which you must write. To build a secure connection, you first build a private connection, and then the two parties prove their identities to each other. More precisely, the two parties engage in a protocol in which each of them ends up with a cryptographic key. The protocol has the property that if both parties are telling the truth about their identity, they will end up with equal keys. If either side is lying about its identity, they will end up with different keys. Finally, the two parties engage in a simple challenge-response protocol to see whether they have the same keys. If this step succeeds, both parties know that the other does indeed have the claimed identity.

Identities and Private Keys

When we talk about the identity of a process, what we really mean is the identity of the person who is running the process. AcmeNet represents identities as Strings in Java. Each of you has an identity that is equal to your username on the CIT Unix machines.

Each identity has a 64-bit private key. Your private key should be known only to you and the AcmeNet Authorization Service. At some point in the semester we will ask you to give us a piece of paper with your private key written on it.

How should you choose your private key? Please do not use a secret number like your PAC number (since we can't provide strong guarantees of the secrecy of your private key), or an easily guessable number like your Social Security Number. One way to choose your private key is to make up a memorable "passphrase". Then you can use the AcmeNet.Util.Login.keyFromPassphrase method to distill your passphrase down into a 64-bit private key. Again, don't choose anything terribly private, or anything easily guessable. [The instructor's passphrase is a cute not-quite-English phrase that was often uttered by a one-year-old he knew.]

When you "prove your identity is X", what you're really proving is that you know X's private key. So don't tell other people your private key, and don't store your private key or your passphrase in a world-readable file. [For that matter, don't store any of your homework in world-readable files. It only invites cheating by others.]

The Authentication Service

Authentication in AcmeNet uses a trusted third party protocol. In other words, when two parties want to authenticate each other, they must involve a third party who is trusted to know the private keys or all parties and to behave appropriately. This third party is the AcmeNet Authentication Service. It lives at the ServiceAddress given by AcmeNet.Assn3.AuthenticationService.Location. The AuthenticationService is a PrivateService, so you use a PrivateConnection to talk to it. You know the AuthenticationService is genuine because it runs on a machine that doesn't accept network logins and is behind a locked door. Only the instructor can start the AuthenticationService.

The Authentication Protocol

The AcmeNet protocol is a modified version of the Needham-Schroeder private-key authentication protocol. The protocol works like this:

  1. The two parties state their identities: each party sends the other a String.
  2. The two parties agree on who will play the role of "initiator" and who will play the role of "recipient". They do this by a randomized protocol. Each party generates a random long, and sends it to the other party. The party who chose the larger number becomes the "initiator" and the other party becomes the "recipient". If the two numbers are equal, they try again, repeating until the numbers are unequal.
  3. The initiator makes a PrivateConnection to the AuthenticationService.
  4. The initiator sends to the AuthenticationService the claimed identity of the initiator, the claimed identity of the recipient, and a randomly-generated 64-bit "nonce" value.
  5. The AuthenticationService generates a fresh key K.
  6. The AuthenticationService generates the "ticket", which consists of K followed by the identity of the initiator (a String). It then encrypts the ticket under the private key of the recipient.
  7. The AuthenticationService sends to the initiator, encrypted under the initiator's private key, the following things: the nonce from step 4, the identity of the recipient, the key K, and the encrypted ticket.
  8. The initiator and the AuthenticationServer close their connection to each other.
  9. The initiator receives the data sent in the step 7 and decrypts it with the initiator's private key. The initiator makes sure the nonce it received is equal to the one it sent in step 4. (If it's not, something fishy is happening and the initator stops participating in the protocol.)
  10. The initiator sends the encrypted ticket to the recipient.
  11. The recipient decrypts the ticket using the recipient's private key.

At the end of this protocol, each party looks at the information it received in step 9 (for the initiator) or step 11 (for the recipient). We can prove that we're in one of two situations:

Both parties now change the keys they are using to encrypt data on the PrivateConnection between them. The stream from initiator to recipient is given the key K, and the stream from recipient to initiator is given the key K+1. But we still don't know whether one of the parties was lying. To tell for sure, the two parties now engage in a simple challenge-response protocol.

  1. Each party generates a random value and sends it to the other party.
  2. Each party now takes the number it received, increments it, and sends it back to the other party.
  3. Each party takes the reply it received, decrements it, and verifies that the result is equal to the value it generated in step 1. If it's not equal, then the other party must be a liar, so the party discovering the liar breaks the connection.

If the parties make it through this part of the protocol successfully, then the initiator knows that it received the correct identity of the other party in step 9, and the recipient knows that it received the correct identity of the other party in step 11. The authentication protocol has succeeded (finally).

Testing Your Solution

We have implemented the AuthenticationService, and it should be running at the location AcmeNet.Assn3.AuthenticationService.Location. We will also have a SecureEchoService running at a location to be announced.

One thing to watch out for: Many of the possible bugs in encrypted communication lead to a situation where data gets encrypted with one key and then decrypted with another key. When this happens, the resulting "decrypted" data is total gibberish. Any software that expects the decrypted data to be formatted in any special way will almost certainly generate an exception. If you're consuming the "decrypted" data with a DataInputStream, this will usually manifest itself as an IOException.


Copyright (c) 1997 by Edward W. Felten

Last modified: Wednesday, September 10, 1997 03:07 PM