Richard G Baldwin (512) 223-4758, baldwin@austin.cc.tx.us, http://www2.austin.cc.tx.us/baldwin/

Security, Introduction to Key Pairs and Digital Signatures

Java Programming, Lecture Notes # 720, Revised 4/18/99.


Preface

Students in Prof. Baldwin's Advanced Java Programming classes at ACC will be responsible for knowing and understanding all of the material in this lesson beginning with the spring semester of 1999.

This lesson was originally written on April 13, 1999 and has been updated several times since.

The programs in this lesson were tested using JDK 1.2 under Win95

Disclaimer

I claim absolutely no expertise in the area of security. I am simply a college professor attempting to gather information about Java on one hand and present it to my students on the other. I disclaim any responsibility for any security problems that may occur as a result of anyone using any of the material in any of my tutorial lessons.

You are responsible for your own actions. With regard to security, you should study not only the material that I will present, but also material provided by others who possess expertise in the security area. Hopefully my material will be useful in getting you started in that direction.

Two good books on security published by O'Reilly & Associates are:

I highly recommend both of these books.

Introduction

Public keys, private keys, and digital signatures

This lesson introduces you to the topic of public and private keys, and the creation and use of digital signatures based on those keys. Three sample programs are presented.

Creating a KeyPair object

The first sample program shows you how to create a KeyPair object containing public and private key components. It also shows you how to extract those components and write them into disk files.

Creating a digital signature

The second program shows you how to extract the private key from the disk file and use it to create a digital signature representing a text document. It also shows you how to write the digital signature into a disk file.

Verifying against the digital signature

The third program shows you how to extract the public key and the digital signature from their disk files. It also shows you how to use them in conjunction with the text file to determine if the text file has become corrupted since the digital signature for that file was created.

Mechanics versus procedures

There are two major aspects of Java and security on the Internet:

What do I mean by overall security procedures? One obvious example is that you should not allow your secret cryptographic keys to be compromised to the enemy (hackers, crackers, virus writers, etc.). However, there are other more subtle operational procedures that are very important to overall security. These procedures require a great deal of thought about who can do what to whom and how can they do it (probably bad grammar).

For the most part, my tutorial lessons will concentrate on the mechanics of using the Java tools and the Java API. I won't attempt to give advice on overall security procedures. Rather, I will leave that to others who have given a great deal of thought to the topic of who can do what to you and how can they do it.

Discussion

The three legs of the security stool

An earlier lesson suggested that when exchanging data electronically, the parties to the communication might be interested in the following three aspects of that communication:

This lesson deals with an area of security where only the first and third items are of concern. The content of the data being exchanged doesn't contain trade secrets, so confidentiality is not an issue. However, both parties are very interested in authentication and integrity.

A hypothetical example

The following is a hypothetical example of such a situation. There are two companies involved in an ongoing series of electronic transactions:

NewCompany is a new company that has the greatest idea ever for a new product for the Internet and only about 20 employees. However, they expect to grow very rapidly.

OldCompany sells office furniture. OldCompany will be the supplier for the office furniture needed by NewCompany. They also expect NewCompany to grow very rapidly, and even though they are currently small, over the next few years, they are expected to become an excellent customer for office furniture.

Electronic purchase orders

Being concerned about saving trees and not wasting paper, NewCompany has decided to submit purchase orders electronically to all of their vendors including OldCompany. The fact that they are purchasing ten new file cabinets is not particularly secret, so they don't have a concern about confidentiality.

Integrity is very important

However, they do want to make certain that some hacker doesn't intercept and modify their purchase order before it reaches OldCompany. They don't relish the idea of a truck showing up at their receiving dock and unloading 100 new file cabinets. Similarly, OldCompany doesn't relish the idea of delivering 100 file cabinets when only ten were really needed. They are not likely to get paid for the extra ninety file cabinets and will likely incur the shipping expense both ways for the extras.

Thus, both companies would like to be able to confirm the integrity of the information contained in the electronic purchase order.

Authentication is also very important

In addition, OldCompany is interested in knowing for sure that the electronic purchase order that they received was really from NewCompany. Thus, they have a very strong interest in authentication. They don't want to ship ten new file cabinets to NewCompany only to learn later that the purchase order was really sent by a hacker as an act of electronic vandalism.

Agree on a plan to use digital signatures

The two companies agree to use a pair of public and private keys along with digital signatures on the purchase order documents to accomplish authentication and integrity. (It is expected that a long-term business relationship will exist covering many orders for more office furniture so the procedure being established should persist over a long period of time).

A key management plan is developed

The secure management and distribution of keys is probably one of the most complex aspects of secure Internet communications. To make things simple in this case, the person responsible for the Purchasing department at NewCompany generates a pair of public and private keys and writes the two keys onto two separate diskettes.

Keep the private key under lock and key

The individual diskettes containing the public and private keys are kept under lock and key at NewCompany along with other sensitive business information.

Send the public key to the supplier

In this case, distribution of the public key is relatively straightforward. A copy of the diskette containing the public key is mailed to the responsible party at OldCompany using registered mail and a letter of transmittal on the letterhead of NewCompany. A couple of supplemental telephone conversations causes the responsible people at OldCompany to be satisfied that the public key that they received in the mail really does belong to NewCompany.

Authenticating the source of a document

From this point forward, whenever OldCompany uses the public key to successfully verify the digital signature of an electronic purchase order created by NewCompany using the corresponding private key, they can be confident of the source of the document. This authenticates the source of the document.

Verifying the integrity of a document

Also, if the document verifies properly, both parties can be confident that the purchase order wasn't tampered with after the digital signature was generated. Thus, verification serves both objectives: the authentication of the source and the integrity of the purchase order.

The three sample programs

The three sample programs in the following sections correspond roughly to the three steps described above:

Program Security05A

Generates and saves public and private keys

This program demonstrates the generation of public and private keys and saves those keys in an external format in disk files.

The program is the first in a group of three programs designed to demonstrate the use of signed documents. The group consists of:

A discussion of the other two programs is provided in later sections of this tutorial.

All three programs were tested using JDK 1.2 and Win95.

Security05A Code Fragments

The first fragment shows

This is pretty standard stuff and is included here only for completeness.

class Security05A {

  public static void main(String[] args) {
  	String publicKeyFile = "Security05.pubKey";
  	String privateKeyFile = "Security05.priKey";
    try{

The alphabet soup

Unless you plan to become an expert in Java security and cryptography (and the alphabet soup used in that technology area), there are many things that you will simply have to accept on faith. For example, in Java Cryptography by Jonathan Knudsen, the author states the following regarding the DSA algorithm:

"DSA stands for Digital Signature Algorithm. It was developed by the NSA and released as a standard by the NIST. It is actually a combination of DSA and SHA-1. You can use any key size from 512 to 1024 bits, in 64-bit increments. The signature size depends on the key size."

Do I doubt that any of the above is true? No.

Can I prove that any of the above is true? No.

Does this concern me? Not particularly, as long as I can find several published references from reliable publishers (such as O'Reilly) recommending that I use the DSA algorithm for signing documents.

Creating a KeyPairGenerator object

Along this line, the following fragment creates a KeyPairGenerator object implementing the DSA algorithm as provided by SUN.

      KeyPairGenerator keyPairGenerator = 
              KeyPairGenerator.getInstance("DSA", "SUN");

The provider concept

What do I mean, "as supplied by SUN?" The structure of the Java security and cryptography classes makes it possible to install class libraries from different providers for the cryptographic algorithms.

Execution of the cryptographic algorithms is very resource intensive. For example, the execution of this small program on my machine requires a noticeable amount of time. Possibly an algorithm supplied by one provider might execute more efficiently than the same algorithm supplied by a different provider.

The default provider is SUN

For many of the algorithms, a default implementation is available and is identified as the SUN implementation in those cases where the identification of the provider is required.

A factory method

The KeyPairGenerator object created above will be required later in the creation of a KeyPair object containing the public and private key components.

The getInstance() method used above is commonly known as a factory method. There are many classes throughout the security and cryptography APIs that cannot be instantiated directly using the new operator. In those cases, factory methods are provided that return objects, or instances of those classes.

More alphabet soup

The term PRNG is alphabet soup for pseudo-random number generator. Here is what Knudsen has to say about the SHA-1 algorithm that will be referenced in the next code fragment.

"SHA-1 stands for Secure Hash algorithm. It was developed by the NIST (National Institute of Standards and Technology) in conjunction with NSA. ..."

The term NSA stands for National Security Agency, an agency of the United States Federal government, described in at least one TV program as an agency that is very interested the electronic communications of others. (I knew that all those hours spent watching the Discovery channel and The Learning Channel on cable TV would pay off someday.)

Knudsen goes on to discuss the pros and cons of the algorithm in terms of other alphabet soup such as MD5, MD4, SHA, and SHA-0.

Using random numbers

The next fragment deals with random numbers. Why do we care about random numbers anyway? Assume that you were to use a simple character substitution algorithm (based on a secret code ring from a cereal box) to encode a large amount of U.S. English text. A code breaker could take advantage of the known probability of occurrence of the various characters in English text to help in breaking the code. In other words, the non-uniform statistical distribution of the use of the various characters in English text make certain aspects of the encoded message predictable (more knowledge gained from watching cable TV). In cryptography, predictability is bad news.

In order to avoid predictability (and probably for other good reasons as well) many cryptographic algorithms make use of random number generators when encrypting data. Knudsen describes the java.security.SecureRandom class as "A cryptographically strong random number engine."

An object of the SecureRandom class

The next fragment creates an object of the SecureRandom class for the SHA1PRNG algorithm from the SUN provider.

      SecureRandom secureRandom = 
             SecureRandom.getInstance("SHA1PRNG", "SUN");

Initializing the KeyPairGenerator object

The KeyPairGenerator class is used to create pairs of public and private keys for signing or encryption. The generator needs to be initialized. One of the two available methods for initializing the generator is shown in the next fragment. After initialization, when the generator is used to create of pair of keys, they will be created for the given strength (first parameter) using the supplied source of random bits (second parameter).

This fragment initializes the key pair generator with strength of 512 using the SecureRandom object created above as the source of random bits.

      keyPairGenerator.initialize(512, secureRandom);

According to Knudsen

"Although the strength of a key almost always refers to its bit length, the interpretation of the strength parameter is algorithm dependent."

Generating the KeyPair object

According to Knudsen,

"The JDK includes a class, java.security.KeyPair, that encapsulates a matched public and private key. It's a very simple class."

Having taken care of all of the preliminary requirements, the following code fragment generates the actual KeyPair object containing the public and private key components.

      KeyPair keyPair = 
                      keyPairGenerator.generateKeyPair();

Getting the individual key objects

The KeyPair class has two methods for returning references to the public and private key objects as shown in the following fragment.

      PrivateKey privateKey = keyPair.getPrivate();
      PublicKey publicKey = keyPair.getPublic();

Saving the public and private keys

At this point, the public and private keys are available and ready for use. However, our objective is not to use them in this program, but rather to provide them for use in another program. One way to save them for use in another program is by putting them in an object of the class KeyStore. That will be the topic for a subsequent lesson.

Another way to make them available to another program would be to write them onto the disk as serialized objects. It would even be possible to encrypt them on the way to the disk if they need to be protected to that extent. I will demonstrate how to save objects using object serialization in a subsequent lesson.

Saving the public key in external encoded form

In this lesson, I will use even a different alternative for providing the keys to another program. I will encode each key into an external encoded form commonly used when a standard representation of the key is needed outside the JVM. Then I will write the encoded form of each key into a disk file.

The following fragment encodes the public key as described above.

      byte[] pubKey = publicKey.getEncoded();

The next fragment saves the encoded public key in a file named Security05.pubKey.

      FileOutputStream publicKeyStream = 
                     new FileOutputStream(publicKeyFile);
      publicKeyStream.write(pubKey);
      publicKeyStream.close();

Saving the private key in external encoded form

Similarly, the following fragment encodes the private key and saves it in a file named Security05.priKey.

      byte[] privKey = privateKey.getEncoded();
      //Save the encoded private key in a file
      FileOutputStream privateKeyStream = 
                    new FileOutputStream(privateKeyFile);
      privateKeyStream.write(privKey);
      privateKeyStream.close();

Program recap

That completes the discussion of the interesting code in this program. A complete listing of the program is provided near the end of the lesson.

To recap, the steps involved in creating a matched pair of public and private keys and saving them in two separate disk files are as follows (the algorithms and parameters listed are those used above, but other possibilities exist as well):

  1. Create a KeyPairGenerator object implementing the DSA algorithm.
  2. Create a SecureRandom object for the SHA1PRNG algorithm.
  3. Initialize the KeyPairGenerator object for strength of 512 using the SecureRandom object.
  4. Generate the pair of keys and encapsulate them in an object of type KeyPair by using the initialized KeyPairGenerator object.
  5. Get references to the PublicKey and PrivateKey components encapsulated in the KeyPair object.
  6. Encode the keys into an encoded form (commonly used for transmission of the key to another party) using the references to the public and private key objects.
  7. Write the encoded versions of the public and private keys into disk files.

Program Security05B

Using a private key to sign a document

This program demonstrates the use of a private key to digitally sign a document.

In a different lesson, I will show how to create message digests. A digest takes an arbitrary amount of input data and produces a fixed-length message that represents data. The fixed-length version is commonly referred to as a digest or a fingerprint. Although it is not guaranteed that the digests for two different documents will be different, the probability that two different documents will produce the same digest is extremely small.

Overall procedure

In reality, the procedure for digitally signing a document is very similar to the process of:

The party receiving the document and the encrypted digest can:

If the two digests match, and if the public key is known to be the public key of the legitimate source of the data, this confirms that the document has not been modified since the original digest was created. Assuming proper control of the keys, it also authenticates the source of the document.

Features of the API make it possible for us to sign documents with a little less work than would be required using the procedures described above. In this lesson, we will do it the easy way.

Security05B Code Fragments

Doing it the easy way

The first fragment shows the beginning of the controlling class, the beginning of the main() method, and the beginning of a try block that wraps the entire program. This is all pretty standard stuff.

class Security05B {

  public static void main(String[] args) {
  	String dataFile = "Security05.txt";
  	String signatureFile = "Security05.sig";
  	String privateKeyFile = "Security05.priKey";

    try{

Getting the private key

The next fragment gets the private key from the disk file where it was deposited by the previous program. The private key is stored in a byte array named privateKeyBytes.

      FileInputStream privateKeyStream = 
                    new FileInputStream(privateKeyFile);
      byte[] privateKeyBytes = 
                 new byte[privateKeyStream.available()];  
      privateKeyStream.read(privateKeyBytes);
      privateKeyStream.close();

Importing the private key

We have now read a series of bytes from a disk file that represents the private key. These bytes were written to the disk file in an external format by the previous program. Now we are getting ready to import the private key using the array of bytes as input. This is a fairly obscure process. This is one of those places where, unless you are an expert in cryptography, you simply need to follow the rules and have faith that they do the job.

The KeyFactory class

First consider the KeyFactory class. A key factory is an engine class capable of translating between (public and private) key objects and their external formats. Keys may be imported or exported using key factories. (I will have more to say about engine classes in a subsequent lesson.)

Key specifications

Key factories operate using key specifications. Keys are imported by invoking the generatePublic() and generatePrivate() methods passing a key specification as a parameter.

(Although we won't be using the export capability here, keys are exported by invoking the getKeySpec() method.)

In order to import the private key, we need a key specification for the key.

The PKCS8EncodedKeySpec class

The next fragment makes use of a class named PKCS8EncodedKeySpec, which extends the class named EncodedKeySpec. Here is what Knudsen has to say about these two classes:

EncodedKeySpec: "This class is used to translate between keys and their external encoded format. The encoded format is always simply a series of bytes, but the format of the encoding of the key information into those bytes may vary depending on the algorithm used to generate the key."

PKCS8EncodedKeySpec: "This class represents the PKCS#8 encoding of a private key; the key is encoded in DER format. This is the class that is typically used when dealing with DSA private keys in a key factory."

DER: "There are a few different ways that ASN.1 data structures can be reduced to a byte stream, and DER (Distinguished Encoding Rules) is one of those methods."

ASN.1: An Abstract Syntax Notation language.

Java Security by Scott Oaks, while discussing the EncodedKeySpec class states,

"An encoded key specification holds the encoded data for a key and is defined by the EncodedKeySpec class."

"The PCKS8 encoded key specification is used for DSA private keys and the X509 encoded key specification is used for DSA public keys."

Getting a PKCS8EncodedKeySpec object

The next fragment creates a new PKCS8EncodedKeySpec object to "hold" the encoded data bytes for the private key read from the disk file in the previous code.

      PKCS8EncodedKeySpec privateKeySpec = 
                new PKCS8EncodedKeySpec(privateKeyBytes);

Getting a KeyFactory object

Next I need to create a KeyFactory object for the DSA algorithm from the SUN provider.

      KeyFactory keyFactory = 
                    KeyFactory.getInstance("DSA", "SUN");

Do the import operation

Now that I have the key specification that is holding the encoded key data for the private key, and I have a KeyFactory object for the correct algorithm, I can combine them to import the private key by invoking the generatePrivate() method and passing the specification as a parameter.

      PrivateKey privateKey = 
              keyFactory.generatePrivate(privateKeySpec);

Recap the steps

At this point, I have the private key in an internal format that I can use to create the desired signature. However, before going further, it might be useful to recap the steps involved in importing the private key from the disk file.

  1. Read the disk file containing the (externally) encoded key into an array of bytes.
  2. Instantiate a new object of type PKCS8EncodedKeySpec passing the array of bytes to the constructor (Use X509EncodedKeySpec for a public key).
  3. Get a KeyFactory object for the correct algorithm and the correct provider.
  4. Invoke the generatePrivate() method on the factory object passing the specification object as a parameter (use generatePublic() for a public key).

Create a Signature object

The next fragment creates an object of type Signature implementing the SHA1withDSA algorithm(s) as provided by SUN.

This object is ultimately used to create and then encrypt a digest of a document as described earlier. Two algorithms must be specified,

Why specify SHA-1withDSA?

SHA-1 is an algorithm commonly used to create digests.

DSA is an algorithm commonly used for encryption.

We can probably conclude that the parameter format "SHA1withDSA" means digest using the SHA-1 algorithm and encrypt using the DSA algorithm.

      Signature signatureObj = 
             Signature.getInstance("SHA1withDSA", "SUN"); 

What is the Signature object used for?

A Signature object can be used to either

Initializing the Signature object

It must be initialized for the intended purpose. The initSign() method is used to initialize it for signing a document, and the initVerify() method is used to initialize it for verification of a signature.

In both cases, the key to be used in the encryption (or decryption) must be passed as a parameter. Pass a private key for signing and pass a public key for verification.

The next fragment initializes the Signature object for signing using the private key imported above.

      signatureObj.initSign(privateKey);

Feeding a hungry Signature object

There are two more steps involved in creating the signature. The first step is to feed all of the data comprising the document into the Signature object by invoking its update() method repeatedly. This is probably analogous to the process of creating the digest. However, the digest itself is not accessible.

Using sign() to finish the task

After all of the data has been fed into the signature object, the next step is to create the encrypted signature by invoking the sign() method on the Signature object. This is probably analogous to the process of encrypting the digest.

Get ready to read the raw document from the disk

The next fragment performs the preliminary work in getting ready to read the document from its disk file in order to feed it to the Signature object. This is plain-vanilla Java I/O material.

      FileInputStream dataStream = 
                           new FileInputStream(dataFile);
      BufferedInputStream bufDataStream = 
                     new BufferedInputStream(dataStream);

Feeding the Signature object using the update() method

The next fragment

The data is fed into the update() method in chunks of 1024 bytes each except that the last chunk may be less than 1024 bytes. Each time update() is called, len bytes beginning with element zero in the input array named buffer are fed into the method.

Then the input stream is closed.

      byte[] buffer = new byte[1024]; 
      int len;

      while (bufDataStream.available() != 0){
        len = bufDataStream.read(buffer);
        signatureObj.update(buffer,0,len);
      }//end while

      bufDataStream.close();

Finishing the job with the sign() method

Finally, the next fragment performs the final step in creating the encrypted signature by invoking the sign() method on the Signature object which now contains the digest. This method call returns the encrypted signature as an array of bytes.

      byte[] theSignature = signatureObj.sign();

Saving the encrypted signature

The objective is to produce an encrypted signature that can be provided to a different program. The next fragment uses standard I/O techniques to write the encrypted signature into a disk file.

      FileOutputStream sigStream = 
                     new FileOutputStream(signatureFile);
      sigStream.write(theSignature);
      sigStream.close();

That is the end of the interesting code. The remaining code can be viewed in the complete listing of the program near the end of the lesson.

Another recap

To recap, the following steps are involved in creating the digital signature for a document and writing it into an output file:

  1. Get a Signature object that implements the correct algorithms for creating the digest and for encrypting the digest.
  2. Initialize the Signature object for signing using the private key.
  3. Feed all of the bytes of the document being signed to the update() method of the Signature object to produce the digest of the document.
  4. Invoke the sign() method on the Signature object to encrypt the digest and return the encrypted version as an array of bytes.
  5. Write the array of bytes into a disk file.

Program Security05C

Verifying a signed document with a public key and a signature

This program demonstrates the use of a public key and a digital signature to verify a signed document.

This process is similar to the process of

However, the API provides classes and methods to make it possible to accomplish this with less work than would be the case were we to implement the above procedure.

Security05C Code Fragments

Doing it the easy way again

The first code fragment shows the typical beginning of everything. Nothing new or exciting here.

class Security05C {

  public static void main(String[] args) {
    String pubKeyFile = "Security05.pubKey";
    String signatureFile = "Security05.sig";
    String dataFile = "Security05.txt";

    try{

Importing the public key

As with the previous program, a major part of the program involves importing the public key from the disk file and converting it into an internal key format suitable for use.

You will note that this code is almost identical to the code in the previous program that imported the private key. The differences are highlighted in boldface in the following code fragment.

The differences

The differences are:

      //Get the encoded public key from a file
      FileInputStream keyStream = 
                         new FileInputStream(pubKeyFile);
      byte[] keyBytes = new byte[keyStream.available()];  
      keyStream.read(keyBytes);
      keyStream.close();

      //Create a new X509EncodedKeySpec with the 
      // encoded key.
      X509EncodedKeySpec pubKeySpec = 
                        new X509EncodedKeySpec(keyBytes);
      
      //Create a KeyFactory object for the DSA algorithm
      // from the SUN provider.
      KeyFactory keyFactory = 
                    KeyFactory.getInstance("DSA", "SUN");
      //Generate a public key object from the key
      // specification
      PublicKey pubKey = 
                   keyFactory.generatePublic(pubKeySpec);

Now I have a public key

At this point, I have the public key imported and converted to internal format, ready for use.

Read the encrypted digital signature

The next step is to read the digital signature being used for verification from the disk file into an array of bytes. This process uses standard I/O procedures as shown in the next fragment.

      FileInputStream sigStream = 
                      new FileInputStream(signatureFile);
      byte[] signature = new byte[sigStream.available()]; 
      sigStream.read(signature );
      sigStream.close();

I need a Signature object

As in the previous program, I need a Signature object that implements the SHA1withDSA algorithms for creating a digest of the document being verified and for decrypting the signature.

      Signature sigObj = 
             Signature.getInstance("SHA1withDSA", "SUN");

Initialize for verification using a public key

This time, I will initialize the Signature object for verification using the public key (as opposed to initialization for signing using the private key as in the previous program).

      sigObj.initVerify(pubKey);

Two more steps required

There are two more steps required to produce a digest of the document being verified and to compare it with a decrypted version of the signature that was received:

Feed the Signature object

The next fragment accomplishes the first of these two steps by reading the document file from the disk and feeding it to the update() method in chunks of 1024 bytes each. This is essentially the same as in the previous program.

      FileInputStream dataStream = 
                           new FileInputStream(dataFile);
      BufferedInputStream bufDataStream = 
                     new BufferedInputStream(dataStream);
      byte[] buffer = new byte[1024];
      int len;
      while (bufDataStream.available() != 0) {
        len = bufDataStream.read(buffer);
        sigObj.update(buffer, 0, len);
      }//end while

      bufDataStream.close();

Perform the verification

The next fragment verifies a digest of the document against the digest produced by decrypting the signature using the public key. (The variable named signature that is passed to the verify() method is a reference to a byte array containing the encrypted signature.)

If they match, the verify() method returns true. Otherwise it returns false. The result is saved in the boolean variable named isVerified for display later.

      boolean isVerified = sigObj.verify(signature);

Displaying the results of the verification

The last interesting fragment displays the result of the verification process. The remainder of the code can be viewed in the complete listing of the program presented near the end of the lesson.

      System.out.println(
                      "Data is verified? " + isVerified);

What about the export laws, cryptography, and encrypted signature data

It is my understanding that even though this process performs a hard encryption of the signature, the export of these techniques does not violate the U.S. export laws. Exporting the capability to encrypt the message itself apparently does violate those laws.

Apparently the difference has to do with whether the encryption is being used to hide the contents of the message, or simply to authenticate the source and verify the contents of the message.

The final recap

To recap, the process for verifying a document against an encrypted digital signature when the document, the signature, and the public key are provided as disk files involves the following steps:

  1. Read the disk file containing the (externally) encoded public key into an array of bytes.
  2. Instantiate a new object of type X509EncodedKeySpec passing the array of bytes to the constructor.
  3. Get a KeyFactory object for the correct algorithm and the correct provider.
  4. Invoke the generatePublic() method on the factory object passing the specification object as a parameter.
  5. Read the digital signature from the disk file into an array of bytes.
  6. Get a Signature object that implements the correct algorithms for creating a new digest and for decrypting the signature into an old digest.
  7. Initialize the Signature object for verification using the public key.
  8. Feed all of the bytes of the document being verified to the update() method of the Signature object to produce a new digest of the document.
  9. Invoke the verify() method on the Signature object to decrypt the old signature into a digest and to compare the old digest with the new digest.

Note that the division of labor between the update() and verify() methods may not be exactly as indicated here, but the net result is the same.

Program Listings

Complete listings of all three programs are contained in this section.

 /*File Security05A.java
Rev 4/13/99
Demonstrates the generation of public and private keys
and the saving of those keys in an external format in
disk files.

First in a group of three programs designed to 
demonstrate the use of signed documents. The group 
consists of:
Security05A.java
Security05B.java
Security05C.java

Tested using JDK 1.2 and Win95.
**********************************************************/

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

class Security05A {

  public static void main(String[] args) {
  	String publicKeyFile = "Security05.pubKey";
  	String privateKeyFile = "Security05.priKey";

    try{

      //Create a KeyPairGenerator object implementing
      // the DSA algorithm, as supplied from SUN
      KeyPairGenerator keyPairGenerator = 
              KeyPairGenerator.getInstance("DSA", "SUN");
      //Create a SecureRandom object for the SHA1PRNG 
      // algorithm, as supplied from SUN
      SecureRandom secureRandom = 
             SecureRandom.getInstance("SHA1PRNG", "SUN");
      //Initialize the key pair generator with a strength
      // of 512, the SecureRandom object and a default
      // parameter set.
      keyPairGenerator.initialize(512, secureRandom);
      //Generate a key pair object.
      KeyPair keyPair = 
                      keyPairGenerator.generateKeyPair();
      //Get references to the private and public key
      // components of the key pair object.
      PrivateKey privateKey = keyPair.getPrivate();
      PublicKey publicKey = keyPair.getPublic();


      //Encode the public key into an external encoded 
      // form used when a standard representation of the
      // key is needed outside the Java Virtual Machine,
      // as when transmitting the key to some other
      // party.
      byte[] pubKey = publicKey.getEncoded();
      //Save the encoded public key in a file
      FileOutputStream publicKeyStream = 
                     new FileOutputStream(publicKeyFile);
      publicKeyStream.write(pubKey);
      publicKeyStream.close();
      
      //Encode the private key into an external encoded 
      // form.
      byte[] privKey = privateKey.getEncoded();
      //Save the encoded private key in a file
      FileOutputStream privateKeyStream = 
                    new FileOutputStream(privateKeyFile);
      privateKeyStream.write(privKey);
      privateKeyStream.close();
    }catch(Exception e){System.out.println(e);}
  }//end main
}//end class Security05A

.

 /*File Security05B.java
Rev 4/13/99
Demonstrates the use of a private key to sign a document.

Second in a group of three programs designed to 
demonstrate the use of signed documents. The group 
consists of:
Security05A.java
Security05B.java
Security05C.java

Tested using JDK 1.2 and Win95.
**********************************************************/

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

class Security05B {

  public static void main(String[] args) {
  	String dataFile = "Security05.txt";
  	String signatureFile = "Security05.sig";
  	String privateKeyFile = "Security05.priKey";

    try{
      //Get the private key from the disk file.
      FileInputStream privateKeyStream = 
                    new FileInputStream(privateKeyFile);
      byte[] privateKeyBytes = 
                 new byte[privateKeyStream.available()];  
      privateKeyStream.read(privateKeyBytes);
      privateKeyStream.close();
      
      //Create a new PKCS8EncodedKeySpec with the
      // encoded key.
      PKCS8EncodedKeySpec privateKeySpec = 
                new PKCS8EncodedKeySpec(privateKeyBytes);

      //Create a KeyFactory object for the DSA algorithm
      // from the SUN provider.
      KeyFactory keyFactory = 
                    KeyFactory.getInstance("DSA", "SUN");
      //Generate a private key object from the key 
      // specification
      PrivateKey privateKey = 
              keyFactory.generatePrivate(privateKeySpec);

      //Create a Signature object implementing the 
      // SHA1withDSA algorithm, as supplied from SUN
      Signature signatureObj = 
             Signature.getInstance("SHA1withDSA", "SUN"); 
      //Initialize the Signature object for signing based
      // on the private key. 
      signatureObj.initSign(privateKey);

      //Get the data to be signed
      FileInputStream dataStream = 
                           new FileInputStream(dataFile);
      BufferedInputStream bufDataStream = 
                     new BufferedInputStream(dataStream);
      byte[] buffer = new byte[1024];
      int len;
      while (bufDataStream.available() != 0){
        len = bufDataStream.read(buffer);
        //Update the data to be signed, using len
        // elements from the specified array of bytes,
        // starting at 0
        signatureObj.update(buffer,0,len);
      }//end while

      bufDataStream.close();

      //Get the signature of all the data that was
      // updated.
      byte[] theSignature = signatureObj.sign();
    
      //Save the signature in a file
      FileOutputStream sigStream = 
                     new FileOutputStream(signatureFile);
      sigStream.write(theSignature);
      sigStream.close();

    } catch (Exception e) {System.err.println(e);}
  }//end main
}//end class Security05B

.

 /*File Security05C.java
Rev 4/13/99
Demonstrates the use of a public key and a digital 
signature to verify a signed document.

Third in a group of three programs designed to 
demonstrate the use of signed documents. The group 
consists of:
Security05A.java
Security05B.java
Security05C.java

Tested using JDK 1.2 and Win95.
**********************************************************/

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

class Security05C {

  public static void main(String[] args) {
    String pubKeyFile = "Security05.pubKey";
    String signatureFile = "Security05.sig";
    String dataFile = "Security05.txt";

    try{
      //Get the encoded public key from a file
      FileInputStream keyStream = 
                         new FileInputStream(pubKeyFile);
      byte[] keyBytes = new byte[keyStream.available()];  
      keyStream.read(keyBytes);
      keyStream.close();

      //Create a new X509EncodedKeySpec with the 
      // encoded key.
      X509EncodedKeySpec pubKeySpec = 
                        new X509EncodedKeySpec(keyBytes);
      
      //Create a KeyFactory object for the DSA algorithm
      // from the SUN provider.
      KeyFactory keyFactory = 
                    KeyFactory.getInstance("DSA", "SUN");
      //Generate a public key object from the key
      // specification
      PublicKey pubKey = 
                   keyFactory.generatePublic(pubKeySpec);

      //Get the signature from a file
      FileInputStream sigStream = 
                      new FileInputStream(signatureFile);
      byte[] signature = new byte[sigStream.available()]; 
      sigStream.read(signature );
      sigStream.close();

      //Create a Signature object implementing 
      // SHA1withDSA,as supplied from SUN
      Signature sigObj = 
             Signature.getInstance("SHA1withDSA", "SUN");
      //Initialize the Signature object for verification
      // with the public key
      sigObj.initVerify(pubKey);

      //Get the data file to which the signature applies
      FileInputStream dataStream = 
                           new FileInputStream(dataFile);
      BufferedInputStream bufDataStream = 
                     new BufferedInputStream(dataStream);
      byte[] buffer = new byte[1024];
      int len;
      while (bufDataStream.available() != 0) {
        len = bufDataStream.read(buffer);
        //Update the data to be verified, using len
        // elements from the specified array of bytes,
        // starting at 0
        sigObj.update(buffer, 0, len);
      }//end while

      bufDataStream.close();
      
      //Verify the data against the signature based on
      // the public key that was used to initialize the
      // signature object.  Result is true or false.
      boolean isVerified = sigObj.verify(signature);

      //Display results of verification.
      System.out.println(
                      "Data is verified? " + isVerified);

    } catch (Exception e) {System.err.println("" + e);}

  }//end main()
}//end class Security05C

-end-