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:
Provide an infrastructure for importing and exporting keys according to the specific encoding format or parameters of the key.
The KeyFactory class is an engine class, which provides the typical method of instantiating itself:
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:
Return the provider that implemented this particular key factory.
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.
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.
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.
Return the algorithm this key factory supports.
We'll defer examples of these methods until we discuss the KeySpec class later.
Like all engines, the key factory depends on a service provider interface class: the KeyFactorySpi class (java.security.KeyFactorySpi):
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:
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:
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.
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.
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.
Importing and exporting a key are based on classes that implement the KeySpec interface (java.security.spec.KeySpec):
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.
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):
Provide an object to hold the encoded data of a key.
An encoded key specification can be operated on via these methods:
Return the actual encoded data held by the object.
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):
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:
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.
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):
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:
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:
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:
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):
Provide an object to hold all parameters of a DSA public or private key.
Instances of these classes are constructed by providing all parameters:
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.
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:
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:
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.
Copyright © 2001 O'Reilly & Associates. All rights reserved.