9.2. Message Digest StreamsThe 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). 9.2.1. The DigestOutputStream ClassThe 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:
The digest output stream is constructed as follows:
In addition to the standard methods available to all output streams, a message digest output stream provides the following interface:
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: Class Definitionpublic 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. 9.2.2. The DigestInputStream ClassThe symmetric operation to the digest output stream is the DigestInputStream class (java.security.DigestInputStream):
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:
The interface provided by the digest input stream is symmetric to the digest output stream:
Here's how we can use this class to read the file we created with the digest output stream: Class Definitionpublic 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. |
|