Book Home Java Security Search this book

10.3. The KeyFactory Class

Although there are times when you'll generate your own keys, they are more often obtained electronically. The next engine and related set of classes we'll examine show us how to import and export keys. The source or destination of these keys is not specified by any of these classes--you may have read the data from a file, or from a socket, or you may have typed it in manually. The classes in this section merely enable you to convert a key object to a known external representation and to perform the reverse conversion.

Key factories are available only in Java 1.2. Exporting keys in 1.1 is simple: the encoded bytes of the key can be obtained and transmitted in any manner that is convenient. But importing keys in 1.1 is very difficult, because there is no way to take the encoded bytes and produce a key from them. As a fallback measure, you can serialize a key object to export it and then deserialize that data to import the key, although that's not something we generally recommend (see Section 10.5, "Keys, Certificates, and Object Serialization" later in this chapter).

There are two external representations by which a key may be transmitted--by its encoded format, or by the parameters that were used to generate the key. Either of these representations may be encapsulated in a key specification, which is used to interact with the KeyFactory class (java.security.KeyFactory) that actually imports and exports keys:

public class KeyFactory figure

Provide an infrastructure for importing and exporting keys according to the specific encoding format or parameters of the key.

10.3.1. Using the KeyFactory class

The KeyFactory class is an engine class, which provides the typical method of instantiating itself:

public static final KeyFactory getInstance(String alg) figure
public static final KeyFactory getInstance(String alg, String provider) figure

Create a key factory capable of importing and exporting keys that were generated with the given algorithm. The class that implements the key factory comes from the named provider or is located according to the standard rules for provider engines. If a key factory that implements the given algorithm is not found, a NoSuchAlgorithmException is generated. If the named provider is not found, a NoSuchProviderException is generated.

A key factory presents the following public methods:

public final Provider getProvider() figure

Return the provider that implemented this particular key factory.

public final PublicKey generatePublic(KeySpec ks) figure
public final PrivateKey generatePrivate(KeySpec ks) figure

These methods are used to import a key: they create the key based on the imported data that is held in the key specification object. If the key cannot be created, an InvalidKeySpecException is thrown.

public final KeySpec getKeySpec(Key key, Class keySpec) figure

This method is used to export a key: it creates a key specification based on the actual key. If the key specification cannot be created, an InvalidKeySpecException is thrown.

public final Key translateKey(Key key) figure

Translate a key from an unknown source into a key that was generated from this object. This method can be used to convert the type of a key that was loaded from a different security provider (e.g., a DSA key generated from the XYZ provider--type com.XYZ.DSAPrivateKey--could be converted to a DSA key generated from the Sun provider--type sun.security.pro-vider.DSAPrivateKey). If the key cannot be translated, an InvalidKeyException is generated.

public final String getAlgorithm() figure

Return the algorithm this key factory supports.

We'll defer examples of these methods until we discuss the KeySpec class later.

10.3.2. Implementing a Key Factory

Like all engines, the key factory depends on a service provider interface class: the KeyFactorySpi class (java.security.KeyFactorySpi):

public abstract class KeyFactorySpi figure

Provide the set of methods necessary to implement a key factory that is capable of importing and exporting keys in a particular format.

However, since the KeyFactory class did not exist in 1.1, its SPI is unrelated in the class hierarchy. Implementing a key factory therefore requires that we subclass the SPI rather than subclassing the KeyFactory class directly. The KeyFactorySpi class is required to implement a key factory because the KeyFactory class contains only this constructor:

protected KeyFactory(KeyFactorySpi keyFacSpi, Provider provider, String algorithm)

Construct a key factory based on the given factory service provider class that is implemented by the given provider and that provides keys of the given algorithm.

This constructor is called by the Security class itself; all we need to do is ensure that the class we register with the security provider interface is a subclass of the KeyFactorySpi class.

The KeyFactorySpi class contains the following methods; since each of these methods is abstract, our class must provide an implementation of all of them:

protected abstract PublicKey engineGeneratePublic(KeySpec ks) figure
protected abstract PrivateKey engineGeneratePrivate(KeySpec ks) figure

Generate of the public or private key. Depending on the key specification, this means either decoding the data of the key or regenerating the key based on specific parameters to the key algorithm. If the key cannot be generated, an InvalidKeyException should be thrown.

protected abstract KeySpec engineGetKeySpec(Key key, Class keySpec) figure

Export the key. Depending on the key class specification, this means either encoding the data (e.g., by calling the getEncoded() method) or saving the parameters that were used to generate the key. If the specification cannot be created, an InvalidKeySpecException should be thrown.

protected Key engineTranslateKey(Key key)figure

Perform the actual translation of the key. This is typically performed by translating the key to its specification and back. If the key cannot be translated, an InvalidKeyException should be thrown.

Although we show how to use a key factory later, we won't show how to implement one; the amount of code involved is large and relatively uninteresting. However, the online examples do contain a sample key factory implementation if you're interested in seeing one.

10.3.3. Key Specifications

Importing and exporting a key are based on classes that implement the KeySpec interface (java.security.spec.KeySpec):

public interface KeySpec figure

Identify a class as one that is able to hold data that can be used to generate a key.

The KeySpec interface is an empty interface; it is used for type identification only. This interface in turn forms the basis of two interfaces, each of which handles one method of importing a key.

10.3.3.1. The EncodedKeySpec class

Earlier, we mentioned that the Key class must provide a getEncoded() method for the key that outputs a series of bytes in a format specific to the type of key; this format is generally part of the specification for the key algorithm. For DSA keys, for example, the encoding format might be PKCS#8 or X.509. An encoded key specification holds the encoded data for a key and is defined by the EncodedKeySpec class (java.security.spec.EncodedKeySpec):

public abstract class EncodedKeySpec implements KeySpec figure

Provide an object to hold the encoded data of a key.

An encoded key specification can be operated on via these methods:

public abstract byte[] getEncoded() figure

Return the actual encoded data held by the object.

public abstract String getFormat() figure

Return the string that represents the format of the encoded data (e.g., PKCS#8).

There are two core classes that provide a concrete implementation of this class (both of which are in the java.security.spec package):

public class PKCS8EncodedKeySpec extends EncodedKeySpec figure
public class X509EncodedKeySpec extends EncodedKeySpec figure

Provide an implementation of the encoded key specification. The PCKS8 encoded key specification is used for DSA private keys, and the X509 encoded key specification is used for DSA public keys.

Both of these classes are constructed by passing in the encoded data:

public PKCS8EncodedKeySpec(byte data[]) figure
public X509EncodedKeySpec(byte data[]) figure

Construct an encoded key specification object that holds the given encoded data. The format of the data is not checked for validity. The input data is saved within the object to be returned via the getEncoded() method.

Taken together, the methods of these classes allow us to import and export keys. Keys are exported via the getEncoded() method, and they are imported by constructing an object based on the encoded bytes.

10.3.3.2. The AlgorithmParameterSpec interface

In addition to their encoded format, keys are typically able to be specified by providing the parameters to the algorithm that produced the key. Specifying keys in this manner is a function of the AlgorithmParameterSpec interface (java.security.spec.AlgorithmParameterSpec):

public interface AlgorithmParameterSpec figure

Provide an infrastructure for specifying keys based on the parameters used to generate them.

Like the KeySpec interface, this interface provides no methods and is used only for type identification. The DSAParameterSpec class (java.security.spec.DSAParameterSpec) is the single core class that implements this interface:

public class DSAParameterSpec implements AlgorithmParameterSpec, DSAParams figure

Provide a class that holds the parameters used to generate a DSA key.

As we mentioned earlier, there are three parameters that are common to all DSA keys: p, q, and g. Hence, an instance of this class can be constructed as follows:

public DSAParameterSpec(BigInteger p, BigInteger q, BigInteger g) figure

Create an object that holds the common parameters used to generate a DSA key.

The only methods of this class are used to retrieve those parameters:

public BigInteger getP() figure
public BigInteger getQ() figure
public BigInteger getG() figure

Return the parameter held by the specification object.

While those three parameters are common to every DSA key, a DSA public key has an additional parameter (y) and a DSA private key has a different additional parameter (x). Hence, to represent a DSA key fully requires one of these classes (both of which are in the java.security.spec package):

public class DSAPublicKeySpec implements KeySpec figure
public class DSAPrivateKeySpec implements KeySpec figure

Provide an object to hold all parameters of a DSA public or private key.

Instances of these classes are constructed by providing all parameters:

public DSAPublicKeySpec(BigInteger y, BigInteger p, BigInteger q, BigInteger g) figure
public DSAPrivateKeySpec(BigInteger x, BigInteger p, BigInteger q, BigInteger g) figure

Create an object that holds all the parameters used to generate a DSA key.

This final parameter can be retrieved via a class-specific method (getX() or getY() as appropriate).

Once again, these classes in total allow us to export keys (via the various get*() methods) and to import keys via the constructors.

10.3.4. A Key Factory Example

As we mentioned at the beginning of this section, the prime reason for key factories is that they give us the ability to import and export keys. Exporting a key specification is typically done by transmitting the individual data elements of the key specification (those individual elements vary by the type of key). Importing a key specification typically involves constructing the specification with the transmitted elements as parameters to the constructor.

Here's an example using a DSA algorithmic parameter specification. We'll look first at exporting a key:

Class Definition

public class Export {
	public static void main(String args[]) {
		try {
			KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
			kpg.initialize(512, new SecureRandom());
			KeyPair kp = kpg.generateKeyPair();
			Class spec = Class.forName(
							"java.security.spec.DSAPrivateKeySpec");
			KeyFactory kf = KeyFactory.getInstance("DSA");
			DSAPrivateKeySpec ks = (DSAPrivateKeySpec)
									kf.getKeySpec(kp.getPrivate(), spec);
			FileOutputStream fos = new FileOutputStream("exportedKey");
			ObjectOutputStream oos = new ObjectOutputStream(fos);
			oos.writeObject(ks.getX());
			oos.writeObject(ks.getP());
			oos.writeObject(ks.getQ());
			oos.writeObject(ks.getG());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

Two items are interesting in this code. First, one argument to the getKeySpec() method is a class object, requiring us to construct the class object using the forName() method (a somewhat unusual usage). Then, once we have the key specification itself, we have to figure out how to transmit the specification. Since in this case, the specification is an algorithmic specification, we chose to write out the individual parameters from the specification.[3] If we had used an encoded key specification, we simply would have written out the byte array returned from the getEncoded() method.

[3]The DSAPrivateKeySpec class--like all key specification classes--is not serializable itself. But for reasons that we'll discuss later, it's better not to serialize key classes that are to be imported into another Java VM anyway.

We can import this key as follows:

Class Definition

public class Import {
	public static void main(String args[]) {
		try {
			FileInputStream fis = new FileInputStream("exportedKey");
			ObjectInputStream ois = new ObjectInputStream(fis);
			DSAPrivateKeySpec ks = new DSAPrivateKeySpec(
						(BigInteger) ois.readObject(),
						(BigInteger) ois.readObject(),
						(BigInteger) ois.readObject(),
						(BigInteger) ois.readObject());
			KeyFactory kf = KeyFactory.getInstance("DSA");
			PrivateKey pk = kf.generatePrivate(ks);
			System.out.println("Got private key");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

This example is predictably symmetric to exporting a key.



Library Navigation Links

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