10.3. The KeyFactory ClassAlthough 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:
10.3.1. Using the KeyFactory classThe KeyFactory class is an engine class, which provides the typical method of instantiating itself:
A key factory presents the following public methods:
We'll defer examples of these methods until we discuss the KeySpec class later. 10.3.2. Implementing a Key FactoryLike all engines, the key factory depends on a service provider interface class: the KeyFactorySpi class (java.security.KeyFactorySpi):
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:
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:
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 SpecificationsImporting and exporting a key are based on classes that implement the KeySpec interface (java.security.spec.KeySpec):
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 classEarlier, 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):
An encoded key specification can be operated on via these methods:
There are two core classes that provide a concrete implementation of this class (both of which are in the java.security.spec package):
Both of these classes are constructed by passing in the encoded data:
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 interfaceIn 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):
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:
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:
The only methods of this class are used to retrieve those parameters:
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):
Instances of these classes are constructed by providing all parameters:
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 ExampleAs 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 Definitionpublic 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.
We can import this key as follows: Class Definitionpublic 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. |
|