9.3. Implementing a MessageDigest Class
If you want to
write your own security provider, you have the option of creating
your own message digest engine. Typically, you'd do this
because you want to ensure that a particular algorithm like SHA is
available regardless of who the default security provider is; if you
have a mathematics background, it's conceivable that you might
want to implement your own algorithm.
In order to implement a message digest algorithm, you must provide a
concrete subclass of the MessageDigest class.
This essentially entails providing an implementation of most of the
public methods we've just looked at. Although the public
methods are not declared abstract, they typically do nothing more
than call an internal (protected) method to accomplish their task.
The MessageDigest class exists in both Java 1.1
and 1.2,[1] which
is why it extends its SPI (see Chapter 8, "Security Providers"). For our
example, we'll directly subclass the
MessageDigest class so that the resulting
example will work under both releases, but remember that in 1.2 you
have the option of extending the
MessageDigestSpi class directly.
There is a single constructor in the
MessageDigest class that is available to
implementors:
-
protected MessageDigest(String name)
-
Construct a message digest object. Classes that extend the
MessageDigest class must call this constructor,
as this is the only constructor in the class. As we'll see,
however, the constructor of the subclass must take no arguments.
In order to write a message digest class, you must implement each of
the following methods:
-
protected abstract void engineUpdate(byte input)
-
protected abstract void engineUpdate(byte[] input, int offset, int len)
-
Add the given bytes to the data over which the digest will be
calculated. Note that there is no method in this list that accepts
simply an array of bytes; the update(byte[] b)
method in the base class simply uses an offset of
and a length equal to the entire array.
-
protected abstract byte[] engineDigest()
-
Calculate the digest over the accumulated data, resetting the
internal state of the object afterwards. Note that there is no
corresponding method that accepts an array of bytes as an argument;
the digest() method in the base class simply
calls the engineUpdate() method if needed before
calling the engineDigest() method.
-
protected int engineDigest(byte buf[], int offset, int len)
-
Calculate the digest, placing the output into the
buf array (starting at the given
offset and proceeding for
len bytes) and returning the length of the
calculated digest. The default implementation of this method simply
calls the engineDigest() method and then copies
the result into buf. The buffer passed to this
method always has sufficient length to hold the digest, since if the
buffer had been too short the digest() method
itself would have thrown an exception.
-
protected abstract void engineReset()
-
Reset the internal state of the engine, discarding all accumulated
data and resetting the algorithm to an initial condition.
-
protected int engineGetDigestLength()
-
Return the digest length that is supported by this implementation.
Unlike most of the protected methods in this class, this method is
not abstract; it does not need to be overridden. However, the default
implementation simply returns 0. If
is returned by this method, the
getDigestLength() method attempts to create a
clone of the digest object, calculate its digest, and return the
length of the calculated digest. If a digest implementation does not
override this method and does not implement the
Cloneable interface, the
getDigestLength() method will not operate
correctly.
Each of these methods corresponds to a public method we just looked
at, with the name of the public method preceded by the word
"engine". The public methods that do not have a
corresponding method in this list are fully implemented in the base
class and do not need to be implemented in the message digest
subclass.
We'll show a simple implementation of a message digest class
here. This implementation is based on a hash algorithm that produces
a 4-byte output. As bytes are accumulated by this algorithm, they are
stored into a 4-byte value (that is, an int); when this value has all
four bytes filled, it is XOR-ed to another integer that accumulates
the hash.
Class Definition
package com.xyz;
public class XYZMessageDigest extends MessageDigest
implements Cloneable {
private int hash;
private int store;
private int nBytes;
public XYZMessageDigest() {
super("XYZ");
engineReset();
}
public void engineUpdate(byte b) {
switch(nBytes) {
case 0:
store = (b << 24) & 0xff000000;
break;
case 1:
store |= (b << 16) & 0x00ff0000;
break;
case 2:
store |= (b << 8) & 0x0000ff00;
break;
case 3:
store |= (b << 0) & 0x000000ff;
break;
}
nBytes++;
if (nBytes == 4) {
hash = hash ^ store;
nBytes = 0;
store = 0;
}
}
public void engineUpdate(byte b[], int offset, int length) {
for (int i = 0; i < length; i++)
engineUpdate(b[i + offset]);
}
public void engineReset() {
hash = 0;
store = 0;
nBytes = 0;
}
public byte[] engineDigest() {
while (nBytes != 0)
engineUpdate((byte) 0);
byte b[] = new byte[4];
b[0] = (byte) (hash >>> 24);
b[1] = (byte) (hash >>> 16);
b[2] = (byte) (hash >>> 8);
b[3] = (byte) (hash >>> 0);
engineReset();
return b;
}
}
The implementation of this class is simple, which isn't
surprising given the fact that the algorithm itself is too simple to
be considered an effective digest algorithm. The major points to
observe are:
-
The name of the class (XYZMessageDigest) and the
name of the algorithm that it implements (XYZ) must match one of the
strings in the provider package for this class to be found. Hence, in
our provider class in Chapter 8, "Security Providers", we included this
property:
Class Definition
put("MessageDigest.XYZ", "com.xyz.XYZMessageDigest");
-
Our constructor calls the only constructor available to us, and the
string "XYZ" that we pass to that constructor takes on
significance--it's the name of the algorithm we've
implemented in this class. This in turn becomes the name that is
registered in the security provider architecture; it must match the
name of the algorithm we registered in our provider.
-
In order for the getDigestLength() method to
function, we chose to implement the Cloneable
interface instead of overriding the
engineGetDigestLength() method. Since there are
no embedded objects in this class, we do not need to override the
clone() method. The default implementation of
that method (a shallow copy) is sufficient for this class.
-
The
engineUpdate() methods accumulate bytes of data until
an integer has been accumulated, at which point that integer can be
XOR-ed into the saved state held in the hash
instance variable.
-
The
engineDigest() method converts the
hash instance variable into a byte array and
returns that to the programmer. Note that the
engineDigest() method is responsible for
resetting the internal state of the algorithm. In addition, the
engineDigest() method is responsible for padding
the data so that it is a multiple of four bytes (the size of a Java
integer). This type of data padding is a common feature of message
digest calculation.
-
The engineReset() method initializes the
algorithm to its initial state.
Once we have an implementation of a message digest, we must install
it into the security provider architecture. If we use the
XYZProvider class from Chapter 8, "Security Providers", we can change our Send
class above to use our new digest algorithm:
Class Definition
public class SendXYZ {
public static void main(String args[]) {
try {
Security.addProvider(new XYZProvider());
FileOutputStream fos = new FileOutputStream("test.xyz");
MessageDigest md = MessageDigest.getInstance("XYZ");
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);
}
}
}
Similar changes to the Receive class will allow
us to accept the message that we've saved to the file
test.xyz.
 |  |  |
| 9.2. Message Digest Streams |  | 9.4. Summary |

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