12.3. Implementing a Signature Class
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:
-
protected Signature(String algorithm)
-
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:
-
protected abstract void engineInitVerify(PublicKey pk)
-
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.
-
protected abstract void engineInitSign(PrivateKey pk)
-
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.
-
protected abstract void engineUpdate(byte b)
-
protected abstract void engineUpdate(byte b[], int off, int len)
-
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.
-
protected abstract byte[] engineSign()
-
protected int engineSign(byte[] outbuf, int offset, int len)
-
Create the signature based on the accumulated data. If there is an
error in generating the signature, a
SignatureException is thrown.
-
protected abstract boolean engineVerify(byte b[])
-
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.
-
protected abstract void engineSetParameter(String p, Object o)
-
protected abstract void engineSetParameter(AlgorithmParameterSpec p)
-
Set the given parameters, which may be algorithm-specific. If this
parameter does not apply to this algorithm, this method should throw
an InvalidParameterException.
-
protected abstract Object engineGetParameter(String p)
-
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:
-
protected final static int UNINITIALIZED
-
protected final static int SIGN
-
protected final static int VERIFY
-
protected int state
-
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).
Class Definition
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.
 |  |  |
| 12.2. Signed Classes |  | 12.4. Summary |

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