new Stringplaintext; } catch Exception e {
e.printStackTrace ; }
} public static void mainString args[] {
DHAgreement test = new DHAgreement ; new Threadtest.start ; Starts Alice
new Threadtest.start ; Starts Bob }
}
Note that this example uses the Cipher
class; see Chapter 13 for more details about that class. In typical usage, of course, Bob and Alice would be executing code in different classes, probably on different
machines. Weve 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 length, this example merely uses a lot of the techniques weve been talking about in the past two chapters. Keys are generated, they are transmitted in neutral encoded format, they are reformed by
their recipient, and both sides can continue.
The KeyAgreement
class is an engine class, and you can create your own implementations of it by subclassing the
KeyAgreementSpi class
javax.crypto.KeyAgreementSpi . Remember that this
is a JCE engine class, so you must perform the appropriate steps in the constructor of your engine to verify the JCE installation.
10.6 Comparison with Previous Releases
The fluidity of key management is evident in the progress of Java itself. Key management with the 1.1 API is very different from key management in Java 2. Further complicating this picture is the fact that no
Java−enabled browser except the Java 2 Plug−In uses the technique for key management that comes with the JRE. Each requires keys to be kept in a different key database, and each uses a different technique to store
and retrieve keys from that application−specific database.
As a developer, that means you must adopt different key management features depending on your target platform. If your target platform is Java 2 applications and Java 2 applets run through the Java Plug−in, then
you can use this key management facility. If you must support applets run in Internet Explorer or versions of Netscape Navigator before Netscape 6, then you must use Microsoft− or Netscape−specific key management
techniques. And if youre targeting Java 1.1 applications, you must use Java 1.1 facilities.
There are no keystores in Java 1.1. If you must implement a key management system under Java 1.1, youll need to use the
IdentityScope class. The
IdentityScope class has been deprecated in Java 2.
Java 1.1 comes with a key management system that is based upon the javakey
utility. javakey
has several limitations; in particular, it stores public and private keys in the same, unprotected location often called an
identity database. This allows anyone with access to the javakey
database to determine all the keys that were stored in the file. Since access is required to obtain your own private key to generate your own digital
signature, this essentially gives all users access to each others keys. This problem was a limitation of the javakey
utility itself. Its possible to use the 1.1 classes to write a key database in such a way that your private key is held separately from a group of public keys see Appendix C.
Although the keystore in Java 2 is incompatible with the identity database in 1.1, keytool
is capable of converting between the two. To convert a 1.1 identity database to a Java 2 keystore, use this command:
−identitydb Convert a 1.1 identity database. This command has the following global options:
−v −keystore keystore
−keypass keypass −storepass storepass
−storetype storetype
It also supports this option: −file db_file
The filename of the 1.1 identity database. The default for this is identitydb.obj in the users home directory.
With this command, each trusted entry in the identity database will be created as a key entry in the keystore. All other entries in the identity database will be ignored.
10.7 Summary
In this chapter we examined the key management facilities of Java. Key management revolves around keys and certificates −− ideas weve already discussed −− but it also depends upon the notion of an identity −− an
individual or a corporation −− and the idea that a particular identity can be certified.
Key management in Java can be handled either programmatically with the standard Java API or with the key management tool
keytool .
keytool itself is a good example of how the programming API can be used,
although there are some trade−offs involved here; for example, loading a large keystore is not necessarily the most appropriate choice for a thin−client application. Fortunately, the security package gives us the necessary
tools to implement our own keystore when that is appropriate.
For all the time weve spent on them, keys are not interesting by themselves. They are interesting for what they allow us to do, which among other things includes the ability to operate on a digital signature. In the next
chapters, well look at message digests and digital signatures, their relationship to keys, and the operations that all this enables us to perform.
Chapter 11. Message Digests
In this chapter, were going to look at the API that implements the ability to create and verify message digests. The ability to create a message digest is one of the standard engines provided by the Sun default security
provider, and there are engines that manipulate digests in the Java Cryptography Extension as well. You can therefore reasonably expect every Java implementation to create message digests.
Message digests are the simplest of the standard engines that compose the security provider architecture. They provide the first link in creating and verifying a digital signature −− one of the most important goals of the
provider architecture. However, message digests are useful entities in their own right since a message digest can verify that data has not been tampered with −− up to a point. As well see, there are certain limitations on
the security of a message digest that is transmitted along with the data it represents.
Well examine how developers can use the message digest in this chapter and also explore how a security provider can implement her own message digest algorithm.
11.1 Using the Message Digest Class
Message digests are implemented using the MessageDigest
class java.security.MessageDigest
: public abstract class MessageDigest extends MessageDigestSpi
Implement operations to create and verify a message digest. Like all engine classes, instances of the message digest are obtained through one of these methods:
public static MessageDigest getInstanceString algorithm public static MessageDigest getInstanceString algorithm, String provider
Return an instance of the message digest class that implements the given algorithm, optionally using the given provider. If no provider can be found that implements the given algorithm, a
NoSuchAlgorithmException is thrown. If the named provider cant be found, a
NoSuchProviderException is thrown.
Once a message digest object has been obtained, the developer can operate on that object with these methods: public void updatebyte input
public void updatebyte[] input public void updatebyte[] input, int offset, int length
Add the specified data to the digest. The first of these methods adds a single byte to the data, the second adds the entire array of bytes, and the third adds only the specified subset of the array of data.
These methods may be called in any order and any number of times to add the desired data to the digest. Consecutive calls to these methods append data to the internal accumulation of data over
which the digest will be calculated.
public byte[] digest public byte[] digestbyte[] input
Compute the message digest on the accumulated data optionally adding the specified data before
performing the computation. The resulting digest is returned as a byte array. Once a digest has been calculated, the internal state of the algorithm is reset so that the object may be reused at this point to
create a new message digest.
public int digestbyte[] output, int offset, int len Compute the message digest on the accumulated data and place the answer into the provided array,
starting at the given offset and copying at most len
bytes. Most implementations do not return a partial digest, so if the amount of space in the buffer taking into account its offset is not sufficient to
store the digest, a DigestException
is thrown. This method returns the size of the digest. public static boolean isEqualbyte digestA[], byte digestB[]
Compare two digests for equality. Two digests are considered equal only if each byte in the first digest is exactly equal to each byte in the second digest and the digests are the same length.
public void reset Reset the digest object by discarding all accumulated data and resetting the algorithm that is used to
implement the digest. This is equivalent to creating a new instance of the object. In addition, this method throws away any information that the
toString method would have printed described
later in this list. public final String getAlgorithm
Return the string representing the algorithm name e.g., SHA. public String toString
A string representation of a digest by default contains the name of the class implementing the digest, the words Message Digest, and the bytes that were returned by a previous call to the
digest method. If the
digest method has not been called, or if the
reset method has been
called, then instead of the digest, incomplete is printed. An example string looks like:
sun.security.provider.SHA Message Digest \ 0a808982fee54fd74a86aae72eff7991328ff32b
public Object clone throws CloneNotSupportedException Return a clone of the object. Message digest implementations need to implement the
clone method because some internal operations on the digest object require a call to the
digest method, which resets the digest. These operations are typically done on a clone of the object so that
the state of the original object is not changed.
public final int getDigestLength Return the length of array of bytes that are returned from the
digest method. This value is
usually constant i.e., it does not depend on the amount of data that has been sent through the update
method. Lets see an example of how all of this works. As a simple case, lets say that we want to save a simple string
to a file, but were worried that the file might be corrupted when we read the string back in. Hence, in addition to saving the string, we must save a message digest. We do this by saving the serialized string object followed
by the serialized array of bytes that constitute the message digest. 208
In order to save the pieces of data, we use this code:
package javasec.samples.ch11; import java.io.;
import java.security.; public class Send {
public static void mainString args[] { try {
FileOutputStream fos = new FileOutputStreamtest; MessageDigest md = MessageDigest.getInstanceSHA;
ObjectOutputStream oos = new ObjectOutputStreamfos; String data = This have I thought good to deliver thee, +
that thou mightst not lose the dues of rejoicing + by being ignorant of what greatness is promised thee.;
byte buf[] = data.getBytes ; md.updatebuf;
oos.writeObjectdata; oos.writeObjectmd.digest ;
} catch Exception e { System.out.printlne;
} }
}
Thats all there is to creating a digest of some data. The call to the getInstance
method finds a message digest object that implements the SHA message digest algorithm. After creating our data −− which in
this case is a simple string −− we pass that data to the update
method of the message digest. In practice, this code could be slightly more complicated since all the data might not be available at once. As far as the
message digest object is concerned, though, that situation would just require multiple calls to the update
method instead of a single call it can also be handled with digest streams, which well examine next. Once weve loaded all the data into the object, it is a simple matter to create the digest itself with the
digest method and then save our data objects to the file.
Similarly, to retrieve this data we need only read the object back in and verify the message digest. In order to verify the message digest, we must recompute the digest over the data we received and test to make sure the
digest is equivalent to the original digest:
package javasec.samples.ch11; import java.io.;
import java.security.; public class Receive {
public static void mainString args[] { try {
FileInputStream fis = new FileInputStreamtest; ObjectInputStream ois = new ObjectInputStreamfis;
Object o = ois.readObject ; if o instanceof String {
System.out.printlnUnexpected data in file; System.exit−1;
} String data = String o;
System.out.printlnGot message + data; o = ois.readObject ;
if o instanceof byte[] { System.out.printlnUnexpected data in file;
System.exit−1;