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:
-
Alice (the first party in the exchange) generates a Diffie-Hellman
public key/private key pair.
-
Alice transmits the public key and the algorithm specification of the
key pair to Bob (the second party in the exchange).
-
Bob uses the algorithm specification to generate his own public and
private keys; he sends the public key to Alice.
-
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.
-
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.
-
Bob and Alice convert their secret keys into a DES key.
-
Alice uses that key to encrypt data that she sends to Bob.
-
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.
 |  |  |
| 13.6. Cipher Streams |  | 13.8. Sealed Objects |

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