Chapter 9. Message DigestsContents:
Using the Message Digest Class
In this chapter, we're 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. 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, so they provide a good starting point in our examination of those engines. In addition, message digests provide the first link in creating and verifying a digital signature--the most important goal 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 we'll see, there are certain limitations on the security of a message digest that is transmitted along with the data it represents. Message digests are implemented through a single class:
In Java 1.1, there is no MessageDigestSpi class, and the MessageDigest class simply extends Object. That difference is important only if you want to implement your own message digest class, which we'll do later in the chapter. Like all engines in the Java security package, the MessageDigest class (java.security.MessageDigest) is an abstract class; it defines an interface that all message digests must have, but the implementation details of a particular message digest class are hidden in the private classes that accompany a security provider. This allows a developer to use the message digest class without knowing the details of a message digest implementation by operating on the public methods of the message digest class, and it allows providers of a security package to implement their own message digests by implementing the abstract methods of the class. We'll examine the message class from the perspectives of both developer and implementor in this chapter. 9.1. Using the Message Digest ClassFor a developer who wants to operate on a message digest, the first step is to obtain an instance of the message digest class. Since the message digest class is abstract, this cannot be done directly; instead, the developer must use one of these methods:
Once a message digest object has been obtained, the developer can operate on that object with these methods:
Let's see an example of how all of this works. As a simple case, let's say that we want to save a simple string to a file, but we're 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. In order to save the pieces of data, we use this code: Class Definitionpublic class Send { public static void main(String args[]) { try { FileOutputStream fos = new FileOutputStream("test"); MessageDigest md = MessageDigest.getInstance("SHA"); ObjectOutputStream oos = new ObjectOutputStream(fos); 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.update(buf); oos.writeObject(data); oos.writeObject(md.digest()); } catch (Exception e) { System.out.println(e); } } } That's 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 we'll examine next). Once we've 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: Class Definitionpublic class Receive { public static void main(String args[]) { try { FileInputStream fis = new FileInputStream("test"); ObjectInputStream ois = new ObjectInputStream(fis); Object o = ois.readObject(); if (!(o instanceof String)) { System.out.println("Unexpected data in file"); System.exit(-1); } String data = (String) o; System.out.println("Got message " + data); o = ois.readObject(); if (!(o instanceof byte[])) { System.out.println("Unexpected data in file"); System.exit(-1); } byte origDigest[] = (byte []) o; MessageDigest md = MessageDigest.getInstance("SHA"); md.update(data.getBytes()); if (MessageDigest.isEqual(md.digest(), origDigest)) System.out.println("Message is valid"); else System.out.println("Message was corrupted"); } catch (Exception e) { System.out.println(e); } } } Once again, if the data was not available all at once, we would need to make multiple calls to the update() method as the data arrived. We do not, however, need to make sure that calls to the update() methods between the Send and Receive classes match in any sense; that is, if we called the update() method four times in the Send class, we do not need to call the update() method four times (with the same data) in the Receive class--we can call it once, five times, or whatever. The calculation of the digest is unaffected by how the data was placed into the message digest object--as long as the order of the bytes presented to the various calls to the update() methods is the same. 9.1.1. Secure Message DigestsAs we stated in Chapter 7, "Introduction to Cryptography", the message digest by itself does not give us a very high level of security. We can tell whether somehow the output file in this example has been corrupted, because the text that we read in won't produce the same message digest that was saved with the file. But there's nothing to prevent someone from changing both the text and the digest stored in the file in such a way that the new digest reflects the altered text. There are various ways in which a message digest can be made into a Message Authentication Code (MAC), but the Java security API does not provide any standard techniques for doing so. One popular way is to encrypt the message digest using the encryption engine (if one is available to you)--which, in fact, is really a variation of a digital signature. If we are not able to encrypt the digest, all is not lost; we can also use a passphrase along with the message digest in order to calculate a secure message digest (or MAC). This requires that both the sender and receiver of the data have a shared passphrase that they have kept secret. Using this passphrase, calculating a MAC requires that we:
We can substitute this code into our original Send example, writing out the data string and the MAC to the file. Note that we can use the same message digest object to calculate both digests, since the object is reset after a call to the digest() method. Also note that the first digest we calculate is not saved to the file: we save only the data and the MAC. Of course, we must make similar changes to the Receive example; if the MACs are equal, the data was not modified in transit. As long as we use exactly the same data for the passphrase in both the transmitting and receiving class, the message digests (that is, the MACs) still compare as equal. That gives a certain level of security to the message digest, but it requires that the sender and the receiver agree on what data to use for the passphrase; the passphrase cannot be transmitted along with the text. In this case, the security of the message digest depends upon the security of the passphrase. Normally, of course, you would prompt for that passphrase rather than hardcoding into the source as we've done above. Copyright © 2001 O'Reilly & Associates. All rights reserved. |
|