13.7. Symmetric Key AgreementWhen 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):
As an engine class, this class has no constructors, but it has the usual method to retrieve instances of the class:
The interface to this class is very simple (much simpler than its use would indicate, as our example will show):
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:
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 Definitionpublic 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. Copyright © 2001 O'Reilly & Associates. All rights reserved. |
|