Now that we've seen how to use the Signature class, we'll look at how to implement our own class. The techniques we'll see here should be very familiar from our other examples of implementing an engine in the security provider architecture. In particular, since in 1.2 the Signature class extends its own SPI, we can implement a single class that extends the Signature class.
To construct our subclass, we must use the following constructor:
This is the only constructor of the Signature class, so all subclasses of this class must use this constructor. The string passed to the constructor is the name that will be registered with the security provider.
Once we've constructed our engine object, we must implement the following methods in it:
Initialize the object to prepare it to verify a digital signature. If the public key does not support the correct algorithm or is otherwise corrupted, an InvalidKeyException is thrown.
Initialize the object to prepare it to create a digital signature. If the private key does not support the correct algorithm or is otherwise corrupted, an InvalidKeyException is thrown.
Add the given bytes to the data that is being accumulated for the signature. These methods are called by the update() methods; they typically call the update() method of a message digest held in the engine. If the engine has not been correctly initialized, a SignatureException is thrown.
Create the signature based on the accumulated data. If there is an error in generating the signature, a SignatureException is thrown.
Return an indication of whether or not the given signature matches the expected signature of the accumulated data. If there is an error in validating the signature, a SignatureException is thrown.
Set the given parameters, which may be algorithm-specific. If this parameter does not apply to this algorithm, this method should throw an InvalidParameterException.
Return the desired parameter, which is algorithm-specific. If the given parameter does not apply to this algorithm, this method should throw an InvalidParameterException.
In addition to those methods, there are a few protected instance variables that keep track of the state of the signature object--whether it has been initialized, whether it can be used to sign or to verify, and so on:
These variables control the internal state of signature object. The state is initially UNITIALIZED; it is set to SIGN by the initSign() method and to VERIFY by the initVerify() method.
These variables are not normally used by the subclasses of Signature, since the logic to maintain them is already implemented in the Signature class itself.
Here is an implementation of a signature class. Note that the XYZSign class depends on other aspects of the security architecture--in this example, the message digest engine to create an SHA message digest, and the DSA key interfaces to handle the public and private keys. This is very typical of signature algorithms--even to the point where the default name of the algorithm reflects the underlying components. The actual encryption of the message digest will use a simple XOR-based algorithm (so that we can, as usual, avoid the mathematics involved with a secure example).
public class XYZSign extends Signature implements Cloneable { private DSAPublicKey pub; private DSAPrivateKey priv; private MessageDigest md; public XYZSign() throws NoSuchAlgorithmException { super("XYZSign"); md = MessageDigest.getInstance("SHA"); } public void engineInitVerify(PublicKey publicKey) throws InvalidKeyException { try { pub = (DSAPublicKey) publicKey; } catch (ClassCastException cce) { throw new InvalidKeyException("Wrong public key type"); } } public void engineInitSign(PrivateKey privateKey) throws InvalidKeyException { try { priv = (DSAPrivateKey) privateKey; } catch (ClassCastException cce) { throw new InvalidKeyException("Wrong private key type"); } } public void engineUpdate(byte b) throws SignatureException { try { md.update(b); } catch (NullPointerException npe) { throw new SignatureException("No SHA digest found"); } } public void engineUpdate(byte b[], int offset, int length) throws SignatureException { try { md.update(b, offset, length); } catch (NullPointerException npe) { throw new SignatureException("No SHA digest found"); } } public byte[] engineSign() throws SignatureException { byte b[] = null; try { b = md.digest(); } catch (NullPointerException npe) { throw new SignatureException("No SHA digest found"); } return crypt(b, priv); } public boolean engineVerify(byte[] sigBytes) throws SignatureException { byte b[] = null; try { b = md.digest(); } catch (NullPointerException npe) { throw new SignatureException("No SHA digest found"); } byte sig[] = crypt(sigBytes, pub); return MessageDigest.isEqual(sig, b); } public void engineSetParameter(String param, Object value) { throw new InvalidParameterException("No parameters"); } public void engineSetParameter(AlgorithmParameterSpec aps) { throw new InvalidParameterException("No parameters"); } public Object engineGetParameter(String param) { throw new InvalidParameterException("No parameters"); } public void engineReset() { } private byte[] crypt(byte s[], DSAKey key) { DSAParams p = key.getParams(); int rotValue = p.getP().intValue(); byte d[] = rot(s, (byte) rotValue); return d; } private byte[] rot(byte in[], byte rotValue) { byte out[] = new byte[in.length]; for (int i = 0; i < in.length; i++) { out[i] = (byte) (in[i] ^ rotValue); } return out; } }
Like all implementations of engines in the security architecture, this class must have a constructor that takes no arguments, but it must call its superclass with its name. The constructor also is responsible for creating the instance of the underlying message digest using whatever algorithm this class feels is important. It is interesting to note that this requires the constructor to specify that it can throw a NoSuchAlgorithmException (in case the SHA algorithm can't be found).
The keys for this test algorithm are required to be DSA public and private keys. In general, the correspondence between an algorithm and the type of key it requires is very strong, so this is a typical usage. Hence, the two engine initialization methods cast the key to make sure that the key has the correct format. The engine initialization methods are not required to keep track of the state of the signature object--that is, whether the object has been initialized for signing or for verifying. That logic, since it is common to all signature objects, is present in the generic initialization methods of the Signature class itself.
The methods that update the engine can simply pass their data to the message digest, since the message digest is responsible for providing the fingerprint of the data that this object is going to sign or verify. Hence, the only interesting logic in this class is that employed by the signing and verification methods. Each method uses the message digest to create the digital fingerprint of the data. Then, to sign the data, the digest must be encrypted or otherwise operated upon with the previously defined private key--this produces a unique digest that could only have been produced by the given data and the given private key. Conversely, to verify the data, the digest must be decrypted or otherwise operated upon with the previously defined public key; the resulting digest can then be compared to the expected digest to test for verification.
Clearly, the security of this algorithm depends on a strong implementation of the signing operations. Our example here does not meet that definition--we're simply XORing every byte of the digest with a byte obtained from the parameters used to generate the keys. This XOR-encryption provides a good example, since it's both simple and symmetric; a real digital signature implementation is much more complex.
These engine signing and verification methods are also responsible for setting the internal state of the engine back to an initialization state, so that the same object can be used to sign or verify multiple signatures. In this case, no other work needs to be done for that; the message digest object itself is already reset once it creates its digest, and there is no other internal state inside the algorithm that needs to be reset. But if there were another state, it would need to be reset in those methods.
Copyright © 2001 O'Reilly & Associates. All rights reserved.
This HTML Help has been published using the chm2web software. |