9.2. Message Digest Streams
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).
9.2.1. The DigestOutputStream Class
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:
-
public class DigestOutputStream extends FilterOutputStream
-
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:
-
public DigestOutputStream(OutputStream os, MessageDigest md)
-
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:
-
public MessageDigest getMessageDigest()
-
Return the message digest associated with this output stream.
-
public void setMessageDigest(MessageDigest md)
-
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).
-
public void write(int b)
-
public void write(byte b[], int off, int len)
-
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.
-
public void on(boolean on)
-
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:
Class Definition
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.
9.2.2. The DigestInputStream Class
The symmetric operation to the digest
output stream is the DigestInputStream class
(java.security.DigestInputStream):
-
public class DigestInputStream extends FilterInputStream
-
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:
-
public DigestInputStream(InputStream is, MessageDigest md)
-
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:
-
public MessageDigest getMessageDigest()
-
Return the message digest that is associated with this output stream.
-
public void setMessageDigest(MessageDigest md)
-
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).
-
public void read(int b)
-
public void read(byte b[], int off, int len)
-
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.
-
public void on(boolean on)
-
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:
Class Definition
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).
 |  |  |
| 9.1. Using the Message Digest Class |  | 9.3. Implementing a MessageDigest Class |

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