start page | rating of books | rating of authors | reviews | copyrights

Book Home Java Security Search this book

13.7. Symmetric Key Agreement

When we discussed public and private key pairs, we talked about the bootstrapping issue involved with key distribution: the problem of obtaining the public key of a trusted certificate authority. In the case of key pairs, keeping the private key secret is of paramount importance. Anyone with access to the private key will be able to sign documents as the owner of the private key; he or she will also be able to decrypt data that is intended for the owner of the private key. Keeping the private key secret is made easier because both parties involved in the cryptographic transfer do not need to use it.

With the symmetric key we introduced in this chapter, however, the bootstrapping issue is even harder to solve because both parties need access to the same key. The question then becomes how this key can be transmitted securely between the two parties in such a way that only those parties have access to the key.

One technique to do this is to use traditional (i.e., nonelectronic) means to distribute the key. The key could be put onto a floppy disk, for example, and then mailed or otherwise distributed to the parties involved in the encryption. Or the key could be distributed in paper format, requiring the recipient of the key to type in the long string of hex digits (the password-based encryption algorithm makes this easier, of course). This is the type of technique we used in the section on cipher data streams. In those examples, the key was saved in a file that was created when the ciphertext was generated (although the key could have been pregenerated, and the Send class could have also read it from a file).

Another technique is to use public key/private key encryption to encrypt the symmetric key, and then to send the encrypted key over the network. This allows the key to be sent electronically and then to be used to set up the desired cipher engine. This is a particularly attractive option, because symmetric encryption is usually much faster than public key encryption. You can use the slower encryption to send the secret key, and then use the faster encryption for the rest of your data. This option requires that your security provider implement a form of public key encryption (which the SunJCE security provider does not).

The final option is to use a key agreement algorithm. Key agreement algorithms exchange some public information between two parties so they each can calculate a shared secret key. However, they do not exchange enough information that eavesdroppers on the conversation can calculate the same shared key.

In the JCE, these algorithms are represented by the KeyAgreement class (javax.crypto.KeyAgreement):

public class KeyAgreement

Provide an engine for the implementation of a key agreement algorithm. This class allows for two cooperating parties to generate the same secret key while preventing parties unrelated to the agreement from generating the same key.

As an engine class, this class has no constructors, but it has the usual method to retrieve instances of the class:

public final KeyAgreement getInstance(String algorithm)
public final KeyAgreement getInstance(String algorithm, String provider)

Return an instance of the KeyAgreement class that implements the given algorithm, loaded either from the standard set of providers or from the named provider. If no suitable class that implements the algorithm can be found, a NoSuchAlgorithmException is generated; if the given provider cannot be found, a NoSuchProviderException is generated.

The interface to this class is very simple (much simpler than its use would indicate, as our example will show):

public final void init(Key k)
public final void init(Key k, SecureRandom sr)
public final void init(Key k, AlgorithmParameterSpec aps)
public final void init(Key k, AlgorithmParameterSpec aps, SecureRandom sr)

Initialize the key agreement engine. The parameter specifications (if present) will vary depending upon the underlying algorithm; if the parameters are invalid, of the incorrect class, or not supported, an InvalidAlgorithmParameterException is generated. This method will also perform the first phase of the key agreement protocol.

public final Key doPhase(Key key, boolean final)

Execute the next phase of the key agreement protocol. Key agreement protocols usually require a set of operations to be performed in a particular order. Each operation is represented in this class by a particular phase, which usually requires a key to succeed. If the provided key is not supported by the key agreement protocol, is incorrect for the current phase, or is otherwise invalid, an InvalidKeyException will be thrown.

The number of phases, along with the types of keys they require, vary drastically from key exchange algorithm to algorithm. Your security provider must document the types of keys required for each phase. In addition, you must specify which is the final phase of the protocol.

public final byte[] generateSecret()
public final int generateSecret(byte[] secret, int offset)

Generate the bytes that represent the secret key; these bytes can then be used to create a SecretKey object. The type of that object will vary depending upon the algorithm implemented by this key agreement. The bytes are either returned from this argument or placed into the given array (starting at the given offset). In the latter case, if the array is not large enough to hold all the bytes a ShortBufferException is thrown. If all phases of the key agreement protocol have not been executed, an IllegalStateException is generated.

After this method has been called, the engine is reset and may be used to generate more secret keys (starting with a new call to the init() method).

public final String getAlgorithm()

Return the name of the algorithm implemented by this key agreement object.

public final Provider getProvider()

Return the provider that implemented this key agreement.

Despite its simple interface, using the key agreement engine can be very complex. The SunJCEsecurity provider implements one key agreement algorithm: Diffie-Hellman key agreement. This key agreement is based on the following protocol:

  1. Alice (the first party in the exchange) generates a Diffie-Hellman public key/private key pair.

  2. Alice transmits the public key and the algorithm specification of the key pair to Bob (the second party in the exchange).

  3. Bob uses the algorithm specification to generate his own public and private keys; he sends the public key to Alice.

  4. Alice uses her private key and Bob's public key to create a secret key. In the KeyAgreement class, this requires two phases: one that uses her private key and one that uses her public key.

  5. Bob performs the same operations with his private key and Alice's public key. Due to the properties of a Diffie-Hellman key pair, this generates the same secret key Alice generated.

  6. Bob and Alice convert their secret keys into a DES key.

  7. Alice uses that key to encrypt data that she sends to Bob.

  8. Bob uses that key to decrypt data that he reads.

These last two steps, of course, are symmetric: both Bob and Alice can encrypt as well as decrypt data with the secret key. They can both send and receive data as well.

Nothing in this key agreement protocol prevents someone from impersonating Bob--Alice could exchange keys with me, I could say that I am Bob, and then Alice and I could exchange encrypted data. So even though the transmissions of the public keys do not need to be encrypted, they should be signed for maximum safety.

This algorithm works because of the properties of the Diffie-Hellman public key/private key pair. These keys are not suitable for use in an encryption algorithm; they are used only in a key agreement such as this.

Here's how a key agreement might be implemented:

Class Definition

public class DHAgreement implements Runnable {
	byte bob[], alice[];
	boolean doneAlice = false;
	byte[] ciphertext;

	BigInteger aliceP, aliceG;
	int aliceL;

	public synchronized void run() {
		if (!doneAlice) {
			doneAlice = true;
			doAlice();
		}
		else doBob();
	}

	public synchronized void doAlice() {
		try {
			// Step 1:  Alice generates a key pair
			KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH");
			kpg.initialize(1024);
			KeyPair kp = kpg.generateKeyPair();
			
			// Step 2:  Alice sends the public key and the
			// 		Diffie-Hellman key parameters to Bob
			Class dhClass = Class.forName(
								"javax.crypto.spec.DHParameterSpec");
			DHParameterSpec dhSpec = (
							(DHPublicKey) kp.getPublic()).getParams();
			aliceG = dhSpec.getG();
			aliceP = dhSpec.getP();
			aliceL = dhSpec.getL();
			alice = kp.getPublic().getEncoded();
			notify();

			// Step 4 part 1:  Alice performs the first phase of the
			//		protocol with her private key
			KeyAgreement ka = KeyAgreement.getInstance("DH");
			ka.init(kp.getPrivate());

			// Step 4 part 2:  Alice performs the second phase of the
			//		protocol with Bob's public key
			while (bob == null) {
				wait();
			}
			KeyFactory kf = KeyFactory.getInstance("DH");
			X509EncodedKeySpec x509Spec = new X509EncodedKeySpec(bob);
			PublicKey pk = kf.generatePublic(x509Spec);
			ka.doPhase(pk, true);

			// Step 4 part 3:  Alice can generate the secret key
			byte secret[] = ka.generateSecret();

			// Step 6:  Alice generates a DES key
			SecretKeyFactory skf = SecretKeyFactory.getInstance("DES");
			DESKeySpec desSpec = new DESKeySpec(secret);
			SecretKey key = skf.generateSecret(desSpec);
			
			// Step 7:  Alice encrypts data with the key and sends
			//		the encrypted data to Bob
			Cipher c = Cipher.getInstance("DES/ECB/PKCS5Padding");
			c.init(Cipher.ENCRYPT_MODE, key);
			ciphertext = c.doFinal(
						"Stand and unfold yourself".getBytes());
			notify();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public synchronized void doBob() {
		try {
			// Step 3:  Bob uses the parameters supplied by Alice
			//		to generate a key pair and sends the public key
			while (alice == null) {
				wait();
			}
			KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH");
			DHParameterSpec dhSpec = new DHParameterSpec(
								aliceP, aliceG, aliceL);
			kpg.initialize(dhSpec);
			KeyPair kp = kpg.generateKeyPair();
			bob = kp.getPublic().getEncoded();
			notify();

			// Step 5 part 1:  Bob uses his private key to perform the
			//		first phase of the protocol
			KeyAgreement ka = KeyAgreement.getInstance("DH");
			ka.init(kp.getPrivate());

			// Step 5 part 2:  Bob uses Alice's public key to perform
			/		the second phase of the protocol.
			KeyFactory kf = KeyFactory.getInstance("DH");
			X509EncodedKeySpec x509Spec =
							new X509EncodedKeySpec(alice);
			PublicKey pk = kf.generatePublic(x509Spec);
			ka.doPhase(pk, true);
			ka.doPhase(1, k

			// Step 5 part 3:  Bob generates the secret key
			byte secret[] = ka.generateSecret();

			// Step 6:  Bob generates a DES key
			SecretKeyFactory skf = SecretKeyFactory.getInstance("DES");
			DESKeySpec desSpec = new DESKeySpec(secret);
			SecretKey key = skf.generateSecret(desSpec);
			
			// Step 8:  Bob receives the encrypted text and decrypts it
			Cipher c = Cipher.getInstance("DES/ECB/PKCS5Padding");
			c.init(Cipher.DECRYPT_MODE, key);
			while (ciphertext == null) {
				wait();
			}
			byte plaintext[] = c.doFinal(ciphertext);
			System.out.println("Bob got the string " +
						new String(plaintext));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static void main(String args[]) {
		DHAgreement test = new DHAgreement();
		new Thread(test).start();		// Starts Alice
		new Thread(test).start();		// Starts Bob
	}
}

In typical usage, of course, Bob and Alice would be executing code in different classes, probably on different machines. We've shown the code here using two threads in a shared object so that you can run the example more easily (although beware: generating a Diffie-Hellman key is an expensive operation, especially for a size of 1024; a size of 512 will be better for testing). Our second reason for showing the example like this is to make explicit the points at which the protocol must be synchronized: Alice must wait for certain information from Bob, Bob must wait for certain information from Alice, and both must perform the operations in the order specified. Once the secret key has been created, however, they may send and receive encrypted data at will.

Otherwise, despite its complexity, this example merely reuses a lot of the techniques we've been using throughout this book. Keys are generated, they are transmitted in neutral (encoded) format, they are re-formed by their recipient, and b oth sides can continue.



Library Navigation Links

Copyright © 2001 O'Reilly & Associates. All rights reserved.