Chapter 7. Message Digests and Digital Signatures

In Chapter 5,"An Introduction to Cryptography," and Chapter 6,"Key Management and Digital Certificates," you were introduced to message digests and digital signatures. These are important elements of Java security because they provide the basic mechanism for trusted code distribution. In this chapter, you'll take a closer look at message digests and digital signatures, study their classes and interfaces, and gain practical experience in creating digests and signatures. You'll also cover message authentication codes (MACs) and learn how they can be used to protect the integrity of messages that are sent over insecure communication channels.

Message Digest Classes and Interfaces

Support for message digests is provided by the following five classes of the java.security package:

  • MessageDigestSpi—The MessageDigestSpi class is an abstract class that offers a service provider interface for the development of MessageDigest classes. It defines abstract methods that must be implemented by a cryptographic service provider to supply the implementation of a particular message digest algorithm.

  • MessageDigest—The MessageDigest class is a subclass of MessageDigestSpi that provides a basic implementation of a message digest. It supports the MD5 and SHA message digest algorithms.

  • DigestInputStream—The DigestInputStream class, a subclass of java.io.FilterInputStream, is used to compute a message digest from the data that is read from a stream.

  • DigestOutputStream—The DigestOutputStream class, a subclass of java.io.FilterOutputStream, is used to compute a message digest from the data that is written to a stream.

  • DigestException—The DigestException class, a subclass of GeneralSecurityException, is thrown as the result of errors in message digest calculation.

These classes are described in the following sections.

MessageDigestSpi

MessageDigestSpi is a service provider interface (SPI) class that defines abstract methods that are to be supplied by MessageDigest implementations. These methods include engineDigest(), engineGetDigestLength(), engineReset(), and engineUpdate(). You don't need to learn these methods unless you're going to build your own custom MessageDigest implementation. However, I'll summarize them here:

  • protected abstract void engineUpdate(byte input)—Updates the digest with the value of a byte.

  • protected abstract void engineUpdate(byte[] input, int offset, int len) —Updates the digest with the array of bytes, beginning at offset and continuing for len bytes.

  • protected abstract byte[] engineDigest()—Completes the message digest computation and returns the message digest value as a byte array.

  • protected int engineDigest(byte[] buf, int offset, int len)—Completes the message digest computation and returns the number of bytes in the digest. The digest is stored in buf as specified by offset and len.

  • protected int engineGetDigestLength()—Returns the message digest length in bytes.

  • protected abstract void engineReset()—Resets the digest to its initial starting point.

  • Object clone()—Provides support for implementing the Cloneable interface. Returns a clone if the MessageDigest implementation implements Cloneable.

If you are interested in creating your own MessageDigest implementation, check the Java 2 API documentation of these functions.

MessageDigest

The MessageDigest class provides a concrete (nonabstract) subclass of MessageDigestSpi that serves as the foundation for message digest computation. It supports the MD5 and SHA message digest algorithms.

The MessageDigest constructor is protected. MessageDigest objects are created via the static getInstance() method. A String identifying the type of message digest algorithm to be used is supplied as an argument:

MessageDigest md = MessageDigest.getInstance("MD5");

The methods of MessageDigest are as follows:

  • static MessageDigest getInstance(String algorithm)—Creates a MessageDigest object that implements the specified digest algorithm.

  • static MessageDigest getInstance(String algorithm, String provider)—Creates a MessageDigest object that implements the specified algorithm from the specified provider (if available).

  • String getAlgorithm()—Returns a String that identifies the algorithm used by the MessageDigest object.

  • void update(byte input)—Updates the digest computation using the specified byte.

  • void update(byte[] input)—Updates the digest computation using the specified array of bytes.

  • void update(byte[] input, int offset, int len)—Updates the digest computation using the specified portion of an array of bytes.

  • byte[] digest( —Finishes the hash computation and returns the message digest as a byte array.

  • byte[] digest(byte[] input)—Updates the digest computation using the specified array of bytes, finishes the digest computation, and returns the digest as a byte array.

  • int digest(byte[] buf, int offset, int len)—Finishes the digest computation and returns the number of bytes in the message digest. The message digest is written to the specified location in buf.

  • int getDigestLength()—Returns the length of the digest in bytes.

  • void reset()—Resets the digest to its initial state.

  • Provider getProvider()—Returns the provider of the MessageDigest object.

  • String toString()—Returns a String representation of the MessageDigest object.

  • Object clone()—Returns a clone of the MessageDigest if the implementation starts the Cloneable interface.

  • static boolean isEqual(byte[] digesta, byte[] digestb)—Compares two MessageDigest objects for equality and returns a boolean value indicating the result of the comparison.

The basic approach to message digest computation uses getInstance() to create a MessageDigest object, update() to update the digest value with a sequence of bytes, and digest() to complete the calculation and retrieve the final digest value. The following section illustrates how this is done.

Computing Message Digests

The MessageDigestTestApp program (Listing 7.1) shows how to use the MessageDigest class to calculate message digests. It performs each of the seven message digest test cases that are documented at the end of RFC 1321. When you run the program, it produces the following output:

Test case:
Expected result:     d41d8cd98f00b204e9800998ecf8427e
Calculated result:   d41d8cd98f00b204e9800998ecf8427e

Test case:           a
Expected result:     0cc175b9c0f1b6a831c399e269772661
Calculated result:   0cc175b9c0f1b6a831c399e269772661

Test case:           abc
Expected result:     900150983cd24fb0d6963f7d28e17f72
Calculated result:   900150983cd24fb0d6963f7d28e17f72

Test case:           message digest
Expected result:     f96b697d7cb7938d525a2f31aaf161d0
Calculated result:   f96b697d7cb7938d525a2f31aaf161d0

Test case:           abcdefghijklmnopqrstuvwxyz
Expected result:     c3fcd3d76192e4007dfb496cca67e13b
Calculated result:   c3fcd3d76192e4007dfb496cca67e13b
Test case:           ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz 0123456789
Expected result:     d174ab98d277d9f5a5611c2c9f419d9f
Calculated result:   d174ab98d277d9f5a5611c2c9f419d9f

Test case:           1234567890123456789012345678901234567890123456789 012345678901
2345678901234567890
Expected result:     57edf4a22be3c955ac49da2e2107b67a
Calculated result:   57edf4a22be3c955ac49da2e2107b67a

As you can see, the program passes all the test cases. The testCase array contains the seven values to be tested, and the expectedResult array contains the expected MD5 digests for these values.

The performMD5Test() method performs the MD5 test for each value. It creates a MessageDigest object using getInstance() and invokes the update() method to process the bytes corresponding to the test case. The digest() method is then invoked to retrieve the digest value. The Conversion class (covered in Chapter 5) is used to convert the digest from a byte array to a String object. The results of the test are then displayed.

Example 7.1. The MessageDigestTestApp Program

package com.jaworski.security.handbook;

import java.security.*;

public class MessageDigestTestApp {
// MD5 test suite from RFC 1321
static String[] testCase = { "", "a", "abc", "message digest",
 "abcdefghijklmnopqrstuvwxyz",
 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
 "123456789012345678901234567890123456789012345678901234567890123456789012345678 90"} ;
static String[] expectedResult = { "d41d8cd98f00b204e9800998ecf8427e",
 "0cc175b9c0f1b6a831c399e269772661",
 "900150983cd24fb0d6963f7d28e17f72",
 "f96b697d7cb7938d525a2f31aaf161d0",
 "c3fcd3d76192e4007dfb496cca67e13b",
 "d174ab98d277d9f5a5611c2c9f419d9f",
 "57edf4a22be3c955ac49da2e2107b67a"} ;
 public static void main(String[] args) {
  for(int i=0; i<testCase.length; ++i)
   performMD5Test(i);
 }
 static void performMD5Test(int i) {
  try {
   MessageDigest md = MessageDigest.getInstance("MD5");
   md.update(testCase[i].getBytes());
   String s = Conversion.byteArrayToHexString(md.digest());
   System.out.println("
Test case:         "+testCase[i]);
   System.out.println("Expected result:   "+expectedResult[i]);
   System.out.println("Calculated result: "+s);
  } catch(NoSuchAlgorithmException e) {
   System.out.println("MD5 is not supported.");
   System.exit(0);
  }
 }
}

DigestInputStream and DigestOutputStream

The DigestInputStream and DigestOutputStream classes simplify the computation of message digests for sequences of bytes that are read from or written to streams. DigestInputStream is used to compute a message digest on a sequence of bytes that is read from a stream. DigestOutputStream is used to calculate a message digest on bytes that are written to a stream. These classes extend java.io.FilterInputStream and java.io.FilterOutputStream, making them digest-calculating input and output filters.

The DigestInputStream constructor takes the InputStream to be filtered and a MessageDigest object as arguments:

DigestInputStream(InputStream stream, MessageDigest digest)

The InputStream is the stream for which the message digest is to be calculated. The MessageDigest object is created using the static getInstance() method of MessageDigest.

The methods of DigestInputStream are as follows:

  • void setMessageDigest(MessageDigest digest)—Sets the MessageDigest object to be used with the stream. Because the MessageDigest object is supplied with the DigestInputStream constructor, this method is needed only when you want to substitute a new MessageDigest object.

  • void on(boolean on)—Turns the digest calculation on or off. By default, the digest calculation is on, and the digest is updated for all bytes read from the stream. You can turn it off for selected input and then turn it on again.

  • int read()—Reads a byte from the input stream and returns it as an int value. The message digest is updated if the digest function is on.

  • int read(byte[] b, int off, int len)—Reads bytes from the input stream into a byte array and returns the number of bytes read. The message digest is updated if the digest function is on.

  • MessageDigest getMessageDigest()—Returns the message digest associated with the stream.

  • String toString()—Returns a String representation of the digest input stream and its associated MessageDigest object.

DigestOutputStream is the output analog of DigestInputStream. The DigestOutputStream constructor takes the OutputStream to be filtered and a MessageDigest object as its arguments:

DigestOutputStream(OutputStream stream, MessageDigest digest)

The OutputStream is the stream for which the message digest is to be calculated. The MessageDigest object is created using the static getInstance() method of MessageDigest.

The methods of DigestOutputStream are as follows:

  • void setMessageDigest(MessageDigest digest)—Sets the MessageDigest object to be used with the stream. Because the MessageDigest object is supplied with the DigestInputStream constructor, this method is needed only when you want to substitute a new MessageDigest object.

  • void on(boolean on)—Turns the digest calculation on or off. By default, the digest calculation is on and the digest is updated for all bytes read from the stream. You can turn it off for selected input and then turn it on again.

  • void write(byte[] b, int off, int len)—Writes the specified portion of the byte array to the output stream. The message digest is updated using the bytes that are written if the digest calculation is on.

  • void write(int b)—Writes the byte to the output stream and updates the message digest (if the digest function is on).

  • MessageDigest getMessageDigest()—Returns the message digest associated with the stream.

  • String toString()—Returns a String representation of the digest input stream and its associated MessageDigest object.

The basic approach to working with DigestInputStream and DigestOutputStream is to create a MessageDigest object and then provide it to the DigestInputStream or DigestOutputStream constructors to create an appropriate digest stream object. Use the stream to perform input or output and then retrieve the updated MessageDigest object. Finally, invoke the digest() method of the MessageDigest object to retrieve the final digest value.

Working with Digest Streams

The DigestStreamTestApp program (see Listing 7.2) illustrates the use of the DigestOutputStream and DigestInputStream classes. It verifies one of the SHA-1 test cases described in FIPS 180-1. It performs the verification using DigestOutputStream and then using DigestInputStream. The program produces the following results:

Output test case: abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq
Expected result:    84983e441c3bd26ebaae4aa1f95129e5e54670f1
Calculated result:  84983e441c3bd26ebaae4aa1f95129e5e54670f1

Input test case: abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq
Expected result:    84983e441c3bd26ebaae4aa1f95129e5e54670f1
Calculated result:  84983e441c3bd26ebaae4aa1f95129e5e54670f1

The performOutputTest() method creates a MessageDigest object that implements the SHA algorithm and a FileOutputStream object that writes to the file sha-results.txt. A DigestOutputStream object is then created using the MessageDigest and FileOutputStream objects.

The write() method of DigestOutputStream is used to write the test string to the output stream. The updated MessageDigest object is then retrieved from the DigestOutputStream, and its digest is retrieved via the digest() method. The results of the test are then displayed.

The performInputTest() method is very similar to the performOutputTest() method except that performInputTest() reads the data that was written to sha-results.txt and performs the digest calculation on the values that are read.

Example 7.2. The DigestStreamTestApp Program

package com.jaworski.security.handbook;

import java.security.*;
import java.io.*;

public class DigestStreamTestApp {
// SHA test suite from FIPS 180-1
static String testCase =
 "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq";
static String expectedResult =
 "84983e441c3bd26ebaae4aa1f95129e5e54670f1";

 public static void main(String[] args) {
  performOutputTest();
  performInputTest();
 }
 static void performOutputTest() {
  try {
   MessageDigest md = MessageDigest.getInstance("SHA");
   FileOutputStream fout = new FileOutputStream("sha-results.txt");
   DigestOutputStream out = new DigestOutputStream(fout,md);
   byte[] b = testCase.getBytes();
   out.write(b,0,b.length);
   md = out.getMessageDigest();
   String s = Conversion.byteArrayToHexString(md.digest());
   System.out.println("
Output test case: "+testCase);
   System.out.println("Expected result:    "+expectedResult);
   System.out.println("Calculated result:  "+s);
  } catch(Exception e) {
   System.out.println(e);
   System.exit(0);
  }
 }
 static void performInputTest() {
  try {
   MessageDigest md = MessageDigest.getInstance("SHA");
   FileInputStream fin = new FileInputStream("sha-results.txt");
   DigestInputStream in = new DigestInputStream(fin,md);
   byte[] b = new byte[testCase.length()];
   in.read(b,0,testCase.length());
   md = in.getMessageDigest();
   String s = Conversion.byteArrayToHexString(md.digest());
   System.out.println("
Input test case: "+(new String(b)));
   System.out.println("Expected result:    "+expectedResult);
   System.out.println("Calculated result:  "+s);
  } catch(Exception e) {
   System.out.println(e);
   System. exit(0);
  }
 }
}

DigestException

DigestException, a subclass of java.security.GeneralSecurityException, is used to indicate that an exception has occurred during the calculation of a message digest. It is thrown by the engineDigest() method of MessageDigestSpi and the digest() method of MessageDigest.

Message Authentication Codes

Message authentication codes (MACs) are message digests that are computed using secret key cryptography. The key is used to detect modifications to a message that is sent over an insecure communication channel. For example, consider Figure 7.1. A wants to send message m to B. A calculates md, the message digest of m, and sends both m and md over an unprotected network to B. However, C intercepts m and md, modifies m (creating m'), and creates md'(the message digest of m'). C then sends m'and md'to B. When B verifies md', he finds that it is the correct message digest for m'.

A security issue involving the use of message digests for message authentication.

Figure 7.1. A security issue involving the use of message digests for message authentication.

Because message authentication codes use a key in the calculation of the message digest, messages that are sent out with a MAC cannot be intercepted, modified, and retransmitted without detection. For example, in Figure 7.2, if C modifies A's message, C must have A's key in order to calculate the MAC of the modified message.

Using MACs for message authentication.

Figure 7.2. Using MACs for message authentication.

In the preceding example, a secure key distribution/agreement protocol is needed to distribute the key that is shared between A and B. Chapter 6 covers key distribution/agreement.

A MAC that uses a message digest or hash function is referred to as an HMAC. For example, HMACs can be created for use with MD5 or SHA-1. An HMAC uses a secret key with the message digest algorithm. RFC 2104 describes the use of HMACs.

Support for message authentication codes is provided with the MacSpi and Mac classes of javax.crypto (part of the Java Cryptography Extension). These classes are covered in the following sections.

MacSpi

The MacSpi class is an abstract class that offers a service provider interface for developing a provider-specific implementation of the Mac class. Its methods are as follows:

  • protected abstract void engineInit(java.security.Key key, java.security.spec.AlgorithmParameterSpec params) —Initializes the MAC with the specified secret key and algorithm parameters.

  • protected abstract void engineUpdate(byte input) —Updates the MAC with the given byte.

  • protected abstract void engineUpdate(byte[] input, int offset, int len) —Updates the MAC with the specified portion of the input array.

  • protected abstract int engineGetMacLength() —Returns the length of the MAC in bytes.

  • protected abstract void engineReset() —Resets the MAC, but retains the secret key that initialized the MAC.

  • protected abstract byte[] engineDoFinal() —Finishes the MAC computation and returns the MAC as a byte array. The MAC is reset but still retains the secret key with which it was initialized.

  • java.lang.Object clone() —Provides the basis for implementing the Cloneable interface.

You don't need the preceding methods unless you want to develop your own MAC implementation. Most of us will use the default Mac class that is provided with javax.crypto.

Mac

The Mac class is the main class that is used in MAC calculation. The Mac class provides support for two MAC algorithms: HMAC-MD5 and HMAC-SHA1, both of which are described in RFC 2104, "HMAC: Keyed-Hashing for Message Authentication." The strings "HmacMD5" and "HmacSHA1" are used to identify these algorithms.

The Mac class, like MessageDigest, is a factory class. Mac objects are created using the static getInstance() method:

Mac mac = Mac.getInstance("HmacSHA1");

Another form of getInstance() enables a specific service provider to be identified.

The methods of Mac are as follows:

  • static Mac getInstance(java.lang.String algorithm) —Creates a Mac object that implements the specified algorithm.

  • static Mac getInstance(java.lang.String algorithm, java.lang.String provider) —Creates a Mac object that implements the specified algorithm and is from the specified provider.

  • void init(java.security.Key key) —Initializes the Mac object with the given key.

  • void init(java.security.Key key, java.security.spec.AlgorithmParameterSpec params) —Initializes the Mac object with the specified key and algorithm parameters.

  • void update(byte input) —Updates the MAC with the specified byte.

  • void update(byte[] input) —Updates the MAC with the specified byte array.

  • void update(byte[] input, int offset, int len) —Updates the MAC with the identified portion of the byte array.

  • byte[] doFinal() —Finishes the MAC operation and returns the MAC as a byte array.

  • byte[] doFinal(byte[] input) —Processes the byte array, finishes the MAC operation, and returns the MAC as a byte array.

  • void doFinal(byte[] output, int outOffset) —Processes the specified portion of the byte array, finishes the MAC operation, and returns the MAC as a byte array.

  • java.lang.String getAlgorithm() —Returns the name of the algorithm used with the Mac object.

  • int getMacLength() —Returns the length of the MAC in bytes.

  • java.security.Provider getProvider() —Returns the provider of the Mac object.

  • void reset() —Resets the Mac object.

  • java.lang.Object clone() —Provides support for implementing the Cloneable interface.

You'll learn how to use these methods to calculate MACs in the next section, "MACs in Action."

MACs in Action

The MACApp program (shown in Listing 7.3) illustrates how MACs are created. It takes a text string (enclosed in quotes) as an argument

java com.jaworski.security.handbook.MACApp "This is a test."

and produces the following output:

Message: This is a test.
MAC:     ac2d81f3e5019f707fdc45ea05e842a9

The performMACTest() method uses the getInstance() method of Mac to create a Mac object that implements the HmacMD5 algorithm. It creates a KeyGenerator object that works with HmacMD5 and then generates a SecretKey object for use in initializing the Mac object. The initialization is performed using the init() method. The update() method is then used to update the Mac object with the text string that was typed at the command line. The doFinal() method completes the MAC calculation and returns the MAC as a byte array. The results of the calculation are then displayed.

Example 7.3. The MACApp Program

package com.jaworski.security.handbook;

import java.security.*;
import javax.crypto.*;

public class MACApp {
 static String errorMessage =
  "Usage: com.jaworski.security.handbook.MACApp testString";
 public static void main(String[] args) {
  if(args.length < 1)
   System.out.println(errorMessage);
  else performMACTest(args[0]);
 }
 static void performMACTest(String s) {
  try {
   String alg = "HmacMD5";
   Mac mac = Mac.getInstance(alg);
   KeyGenerator kg = KeyGenerator.getInstance(alg);
   SecretKey key = kg.generateKey();
   mac.init(key);
   mac.update(s.getBytes());
   byte[] b = mac.doFinal();
   System.out.println("
Message: "+s);
   System.out.println("MAC:     "+
    Conversion.byteArrayToHexString((b)));
  } catch(Exception e) {
   System.out.println(e);
   System.exit (0);
  }
 }
}

Signature Classes and Interfaces

Digital signatures and the NIST digital signature algorithm were introduced in Chapter 5. In this section, you'll cover the signature classes of java.security in detail and learn how to use them to create signed objects.

SignatureSpi

The SignatureSpi class provides a service provider interface for the Signature class. SignatureSpi is implemented by developers who want to provide their own custom implementations of the Signature class. It defines the following methods:

  • protected abstract void engineInitSign(PrivateKey privateKey)—Initializes the Signature object with the specified private key for use in signing operations.

  • protected void engineInitSign(PrivateKey privateKey, SecureRandom random)— Initializes the Signature object with the specified private key and source of randomness for use in signing operations.

  • protected abstract void engineInitVerify(PublicKey publicKey)—Initializes the Signature object with the specified public key for use in signature verification operations.

  • protected void engineSetParameter(AlgorithmParameterSpec params)—Initializes the signature engine with the specified parameter set.

  • protected abstract void engineSetParameter(String param, Object value)—This method has been deprecated in JDK 1.2 and is replaced by the previous engineSetParameter() method.

  • protected abstract Object engineGetParameter(String param)—This method has been deprecated in JDK 1.2.

  • protected abstract void engineUpdate(byte b)—Updates the data to be signed or verified using the specified byte.

  • protected abstract void engineUpdate(byte[] b, int off, int len)—Updates the data to be signed or verified using the specified portion of the byte array.

  • protected abstract byte[] engineSign()—Finishes the signature operation and returns the signature as a byte array.

  • protected int engineSign(byte[] outbuf, int offset, int len)—Finishes the signature operation and returns the signature via outbuf.

  • protected abstract boolean engineVerify(byte[] sigBytes)— Verifies the signature specified by sigBytes.

  • Object clone()—Provides support for implementing the Cloneable interface.

The preceding abstract methods must be implemented by a service provider that implements a signature algorithm.

Signature

The Signature class provides a default implementation of the SignatureSpi abstract methods and supports both signature generation and verification. It supports an algorithm referred to as SHA1withDSA. This algorithm calculates an SHA-1 digest of a sequence of bytes and uses DSA to create and verify DSA digital signatures. DSA is described in FIPS PUB 186. The Signature class is a factory class, and Signature objects are created with the static getInstance() method:

Signature sign = Signature.getInstance("DSA");

A second form of the method allows a particular service provider to be specified.

The methods of Signature are as follows:

  • static Signature getInstance(String algorithm)—Creates a Signature object that implements the specified signature algorithm.

  • static Signature getInstance(String algorithm, String provider)—Creates a Signature object that implements the specified signature algorithm and is supplied from the specified provider (if available).

  • void setParameter(AlgorithmParameterSpec params)—Initializes the signature engine with the specified parameter set.

  • void setParameter(String param, Object value)—This method has been deprecated in JDK 1.2. Use the previous form of setParameter().

  • Object getParameter(String param)—This method has been deprecated in JDK 1.2.

  • Provider getProvider()—Returns the provider of the Signature object.

  • String getAlgorithm()—Returns the name of the algorithm used with the Signature object.

  • void initSign(PrivateKey privateKey)—Initializes the Signature object for signing with the specified private key.

  • void initSign(PrivateKey privateKey, SecureRandom random)—Initialize the Signature object for signing with the specified private key and source of randomness.

  • void initVerify(PublicKey publicKey)—Initializes the Signature object for verification with the specified public key.

  • void update(byte b)—Updates the data to be signed or verified by a byte.

  • void update(byte[] data)—Updates the data to be signed or verified using the specified byte array.

  • void update(byte[] data, int off, int len)—Updates the data to be signed or verified using the specified portion of the byte array.

  • byte[] sign() —Returns the signature that has been calculated as a byte array.

  • int sign(byte[] outbuf, int offset, int len) —Stores the calculated signature in outbuf and returns the length of the signature in bytes.

  • boolean verify(byte[] signature)—Verifies the specified signature and returns a boolean value indicating the success or failure of the verification.

  • String toString()—Returns a representation of the Signature object.

  • Object clone()—Provides support for implementing the Cloneable interface.

The Signature class also defines the protected state variable, which can be in one of the three states, UNINITIALIZED, SIGN, and VERIFY.

Creating and Verifying Digital Signatures

The SignApp program of Listing 7.4 shows how signatures are created and verified. It takes a text string as an argument

java com.jaworski.security.handbook.SignApp "This is a test."

and produces the following output:

Generating key pair ...
Generating signature ...

Message: This is a test.
Private key:
MIIBTAIBADCCASwGByqGSM44BAEwggEfAoGBAOcC6xh0xr7EX6GtF7KwkrwutkQ1
uz8Y7yn2i+v1/X1gmsSKLqBBpIuJCKvPiZkepWUNmQlqCfX3YKUu9a4ULOEXzDDj
Gn7tAeZF8fJqybtjljQy2G77ymXievqQ1SXTC8ksYl1Og0iAK29hQ2zvCCMx+eGu
8CEUjuYwsDV//ybXAhUA6BMUoT4DcaUi40c9nYZ6mqn0HncCgYEAg4IN8kl0qLaE
LZ20GZnvngOY4MxozByPMrzsmXIFRIPUZsZbosCXfHp8xso3pz5DJfMtX10sUnxK
uUYCiQ4j6GxqHvr3qt9QT03eP/bXx5DG0LmZ6s1wdIzJI6bibMYZtvTyP4VPsgVj
yGiFX/uAZDELX3PYcmjZ94rBhjV9j+0EFwIVAI5j5RYwxYQEBbBtFFGlyOd4XE2V
Public key:
MIIBtzCCASwGByqGSM44BAEwggEfAoGBAOcC6xh0xr7EX6GtF7KwkrwutkQ1uz8Y
7yn2i+v1/X1gmsSKLqBBpIuJCKvPiZkepWUNmQlqCfX3YKUu9a4ULOEXzDDjGn7t
AeZF8fJqybtjljQy2G77ymXievqQ1SXTC8ksYl1Og0iAK29hQ2zvCCMx+eGu8CEU
juYwsDV//ybXAhUA6BMUoT4DcaUi40c9nYZ6mqn0HncCgYEAg4IN8kl0qLaELZ20
GZnvngOY4MxozByPMrzsmXIFRIPUZsZbosCXfHp8xso3pz5DJfMtX10sUnxKuUYC
iQ4j6GxqHvr3qt9QT03eP/bXx5DG0LmZ6s1wdIzJI6bibMYZtvTyP4VPsgVjyGiF
X/uAZDELX3PYcmjZ94rBhjV9j+0DgYQAAoGAX1ZnfHw1ixOqxnF4uZ9qSo4mfAbD
saPS1Ts+AOXq77tOL3NWDjSZJOGQVkfua5+K0F76FI6tm/zpOlo2u1HurLELL+t4
g6OwMzkVztbWfW5JagHZwdnbs87jtyu2TwjuOLCGJrp82uCXXW+zU+Jdy0/6487l
ZSfTdGK+EQ7eocM=


Signature:
MC4CFQDa3LzcBGuxW+HnSYcQ2nGY4Z+vPwIVAMLI+KF/DknI7tJ55D8K7DnN45tf


Verifying signature ...
Signature verified!

The program uses the NIST Digital Signature Algorithm for signing and is organized according to the generateKeyPair(), performSigning(), and performVerification() methods. The generateKeyPair() method returns a KeyPair object that is used in both performSigning() and performVerification(). The performSigning() method returns a byte array representation of a Signature object. The performVerification() method verifies this signature.

The generateKeyPair() method creates a KeyPairGenerator object for DSA and generates a KeyPair. This process can take a few minutes on a slow computer.

The performSigning() method creates a Signature object and then initializes it, via initSign(), for signing using the private key of the KeyPair object produced by generateKeyPair(). It then updates the signature via the update() method and creates the final signature via the sign() method. This signature is returned as the result of performSigning().

The performVertification() method verifies the signature that was returned by performSigning(). It uses the public key of the KeyPair object created by generateKeyPair().

This method creates a Signature object that uses DSA and initializes it for verification using the public key. It invokes the update() method of Signature to create a new signature and the verify() method to perform the signature verification.

Example 7.4. The SignApp Program

package com.jaworski.security.handbook;

import java.security.*;

public class SignApp {
 static String errorMessage =
  "Usage: com.jaworski.security.handbook.SignApp testString";
 public static void main(String[] args) {
  if(args.length < 1)
   System.out.println(errorMessage);
  else{
   String alg = "DSA";
   KeyPair keyPair = generateKeyPair(alg);
   byte[] signature = performSigning(args[0],alg,keyPair);
   performVerification(args[0],alg,signature,keyPair.getPublic());
  }
 }
 static KeyPair generateKeyPair(String alg) {
  try {
   KeyPairGenerator kg = KeyPairGenerator.getInstance(alg);
   System.out.println("Generating key pair ...");
   KeyPair keyPair = kg.genKeyPair();
   return keyPair;
  } catch(Exception e) {
   System.out.println(e);
   System.exit(0);
  }
  return null;
 }
 static byte[] performSigning(String s,String alg,KeyPair keyPair) {
  try {
   Signature sign = Signature.getInstance(alg);
   PrivateKey privateKey = keyPair.getPrivate();
   PublicKey publicKey = keyPair.getPublic();
   sign.initSign(privateKey);
   System.out.println("Generating signature ...");
   sign.update(s.getBytes());
   byte[] b = sign.sign();
   System.out.println("
Message: "+s);
   System.out.println("Private key:
"+
    Conversion.byteArrayToBase64String(privateKey.getEncoded()));
   System.out.println("Public key:
"+
    Conversion.byteArrayToBase64String(publicKey.getEncoded()));
   System.out.println("Signature:
"+
    Conversion.byteArrayToBase64String(b));
   return b;
  } catch(Exception e) {
   System.out.println(e);
   System.exit(0);
  }
  return null;
 }
 static void performVerification(String s, String alg,
   byte[] signature, PublicKey publicKey) {
  try {
   Signature sign = Signature.getInstance(alg);
   System.out.println("
Verifying signature ...");
   sign.initVerify(publicKey);
   sign.update(s.getBytes());
   if(sign.verify(signature))
    System.out.println("Signature verified!");
   else
    System.out.println("Signature NOT verified!");
  } catch(Exception e){
   System.out.println(e);
   System.exit(0);
  } 
 }
}

SignedObject

The SignedObject class provides the capability to create signed objects whose integrity can be verified. It provides a copy of a Serializable object and a signature of the serialized form of the object. The signature is created from a Signature object and signing (private) key that is provided to the SignedObject constructor:

Signature sign = Signature.getInstance("SHA1withDSA ");
SignedObject so = new SignedObject(objectToBeSigned, privatekey, sign);

The object's signature is verified using the verify() method. This method takes a PublicKey and Signature object as arguments. The getObject() method returns the original object that was signed:

Signature sign = Signature.getInstance("SHA1withDSA ");
if(so.verify(publickey, sign)) myObject = so.getObject();

The methods of SignedObject are summarized as follows:

  • boolean verify(PublicKey verificationKey, Signature verificationSignature) —Verifies that the signature in the SignedObject is valid for the object stored inside. It bases verification on the verification key and verification signature object.

  • String getAlgorithm()—Returns the name of the signature algorithm.

  • Object getObject()—Returns the object that was signed.

  • byte[] getSignature()—Returns the signature on the signed object as a byte array.

An example of using SignedObject is provided in the following section.

Using SignedObject

The SignedObjectApp program of Listing 7.5 shows how SignedObjects are created and verified. It produces the following output:

Generating key pair ...
Creating signed object ...

Verifying signature ...
Signature verified!

The key pair generation can take some time on a slow computer.

The SignedObjectApp program creates a DSA KeyPair object and a Vector that serves as the object that is signed. These objects are passed to createSignedObject(), which creates and returns a SignedObject based on the Vector that is signed using the private key of the KeyPair. The performVerification() method then verifies the signature of the SignedObject.

The createSignedObject() method creates a Signature object and passes the Vector, private key, and Signature object to the SignedObject constructor. The SignedObject instance that is created is then returned.

The performVerification() method creates a Signature object for verification purposes and passes this object and the public key of the key pair to the verify() method of SignedObject. The verify() method verifies the Signature using the public key.

Example 7.5. The SignedObjectApp Program

package com.jaworski.security.handbook;

import java.security.*;
import java.util.*;

public class SignedObjectApp {
 public static void main(String[] args) {
  String alg = "DSA";
  KeyPair keyPair = generateKeyPair(alg);
  Vector v = new Vector();
  v.add("This is a test!");
  SignedObject so = createSignedObject(v,alg,keyPair);
  performVerification(so,alg,keyPair.getPublic());
 }
 static KeyPair generateKeyPair(String alg) {
  try {
   KeyPairGenerator kg = KeyPairGenerator.getInstance(alg);
   System.out.println("Generating key pair ...");
   KeyPair keyPair = kg.genKeyPair();
   return keyPair;
  } catch(Exception e) {
   System.out.println(e);
   System.exit(0);
  }
  return null;
 }
 static SignedObject createSignedObject(Vector v,String alg,KeyPair keyPair) {
  try {
   Signature sign = Signature.getInstance(alg);
   System.out.println("Creating signed object ...");
   SignedObject so =
    new SignedObject(v, keyPair.getPrivate(), sign);
   return so;
  } catch(Exception e) {
   System.out.println(e);
   System.exit(0);
  }
  return null;
 }
 static void performVerification(SignedObject so, String alg,
   PublicKey publicKey) {
  try {
   System.out.println("
Verifying signature ...");
   Signature sign = Signature.getInstance(alg);
   if(so.verify(publicKey, sign))
    System.out.println("Signature verified!");
   else
    System.out.println("Signature NOT verified!");
  } catch(Exception e){
   System. out.println(e);
   System.exit(0);
  } 
 }
}

Signer

The Signer class, a deprecated subclass of Identity, was used to support JDK 1.1 signatures. This class has been replaced by the Keystore class in JDK 1.2. Both Signer and KeyStore are covered in Chapter 6.

SignatureException

The SignatureException class is a subclass of GeneralSecurityException. SignatureException is used to represent exceptions that occur during signature generation and verification. This class is thrown by methods and constructors of Signature, SignatureSpi, and SignedObject.

Summary

This chapter covered the classes used to create message digests, MACs, and digital signatures. It also provided practical examples of using the classes to create and verify these objects. In the next chapter, "The Java Cryptography Extension," you'll cover the details of the Java Cryptography Extension (JCE) and learn how to develop a service provider.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.191.22