The interface to the message digest class requires that you supply the data for the digest as a series of single bytes or byte arrays. As we mentioned earlier, this is not always the most convenient way to process data, which may be coming from a file or other input stream. This brings us to the message digest stream classes. These classes implement the standard input and output filter stream semantics of Java streams so that data can be written to a digest stream that will calculate the digest as the data itself is written (or the reverse operation for reading data).
The first of these classes we'll examine is the DigestOutputStream class (java.security.DigestOutputStream). This class allows us to write data to a particular output stream and calculate the message digest of that data transparently as the data passes through the stream:
Provide a stream that can calculate the message digest of data that is passed through the stream. A digest output stream holds two components internally: the output stream that is the ultimate destination of the data, and a message digest object that computes the data of the stream written to the destination.
The digest output stream is constructed as follows:
Construct a digest output stream that associates the given output stream with the given message digest. Data that is written to the stream is automatically passed to the update() method of the message digest.
In addition to the standard methods available to all output streams, a message digest output stream provides the following interface:
Return the message digest associated with this output stream.
Associate the given message digest with this output stream. The internal reference to the original message digest is lost, but the original message digest is otherwise unaffected (i.e., if you still hold a reference to the original message digest object, you can still calculate the digest of the data that was written to the stream while that digest was in place).
Write the given byte or array of bytes to the underlying output stream, and also update the internal message digest with the given data (if the digest stream is marked as on). These methods may throw an IOException from the underlying stream.
Turn the message digest stream on or off. When data is written to a stream that is off, the data will be passed to the underlying output stream, but the message digest will not be updated.
Note that this last method does not affect the underlying output stream at all; data is still sent to the underlying stream even if the digest output stream is marked as off. The on/off state only affects whether the update() method of the message digest will be called as the data is written.
We can use this class to simplify the example we used earlier:
public class SendStream { public static void main(String args[]) { try { FileOutputStream fos = new FileOutputStream("test"); MessageDigest md = MessageDigest.getInstance("SHA"); DigestOutputStream dos = new DigestOutputStream(fos, md); ObjectOutputStream oos = new ObjectOutputStream(dos); 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."; oos.writeObject(data); dos.on(false); oos.writeObject(md.digest()); } catch (Exception e) { System.out.println(e); } } }
The big change is in constructing the object output stream--we now want to wrap it around the digest output stream so that as each object is written to the file, the message digest will include those bytes. We also want to make sure that we turn off the message digest calculation before we send the digest itself to the file. Turning off the digest isn't strictly necessary in this case, since we don't use the digest object once we've calculated a single digest in this example, but it's good practice to keep the digest on only when strictly required.
Note that there is a subtle difference between the digest produced in this example and the previous example. In the first example, the digest was calculated over just the bytes of the string that we saved to the file. In the second example, the digest was calculated over the serialized string object itself--which includes some information regarding the class definition in addition to the bytes of the string.
The symmetric operation to the digest output stream is the DigestInputStream class (java.security.DigestInputStream):
Create an input stream that is associated with a message digest. When data is read from the input stream, it is also sent to the update() method of the stream's associated message digest.
The digest input stream has essentially the same interface as the digest output stream (with writing replaced by reading). There is a single constructor for the class:
Construct a digest input stream that associates the given input stream with the given message digest. Data that is read from the stream will also automatically be passed to the update() method of the message digest.
The interface provided by the digest input stream is symmetric to the digest output stream:
Return the message digest that is associated with this output stream.
Associate the given message digest with this output stream. The internal reference to the original message digest is lost, but the original message digest is otherwise unaffected (e.g., you can still calculate the digest of the data that had been written to the stream while that digest was in place).
Read one or more bytes from the underlying output stream, and also update the internal message digest with the given data (if the digest stream is marked as on). These methods may throw an IOException from the underlying stream.
Turn the message digest stream on or off. When data is read from a stream that is off, the message digest will not be updated.
Here's how we can use this class to read the file we created with the digest output stream:
public class ReceiveStream { public static void main(String args[]) { try { FileInputStream fis = new FileInputStream("test"); MessageDigest md = MessageDigest.getInstance("SHA"); DigestInputStream dis = new DigestInputStream(fis, md); ObjectInputStream ois = new ObjectInputStream(dis); 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); dis.on(false); o = ois.readObject(); if (!(o instanceof byte[])) { System.out.println("Unexpected data in file"); System.exit(-1); } byte origDigest[] = (byte []) o; 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, constructing the input stream is a matter of providing a message digest. In this example, we've again turned off the digest input stream after reading the string object in the file. Turning off the stream is strictly required in this case. We want to make sure that the digest we calculate is computed only over the string object and not the stored byte array (that is, the stored message digest).
Copyright © 2001 O'Reilly & Associates. All rights reserved.