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.
[1]1.2 is now Java 2.
There is a single constructor in the MessageDigest class that is available to implementors:
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:
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.
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.
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.
Reset the internal state of the engine, discarding all accumulated data and resetting the algorithm to an initial condition.
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.
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:
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:
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.
Copyright © 2001 O'Reilly & Associates. All rights reserved.
This HTML Help has been published using the chm2web software. |