< Day Day Up > |
The J2EE and J2SE APIs and runtime environments are shipped with security- related classes. The set of core classes in the Java 2 platform can be divided into two subsets :
The first set of cryptography-related core classes is part of the JCA; the second set, part of the JCE. Together, the JCA and the JCE provide a platform-independent cryptography API. Originally, the JCE was released separately as a standard extension to the Java 2 SDK, in accordance with the U.S. export control regulations. Starting with the Java 2 SDK V1.4, the JCE is shipped as part of the core classes in the package javax.crypto and its subpackages javax.crypto.interfaces and java.crypto.spec . However, at that time, only a weak-encryption version of the JCE could be exported outside the United States, whereas a strong-encryption version can now be exported too, as long as proper protection mechanisms are in place. [1]
11.1.1 Terms and DefinitionsA few terms need to be explained in order to become familiar with the JCA and JCE. These terms are engine, algorithm, and provider.
From this brief discussion, one can see that to form a complete provider package, cryptographic solutions require a whole collection of tools and functions, which include not only the encryption algorithms themselves but also functions for message digests, certificate management, and key generation. 11.1.2 The Principles of JCA and JCEThe JCA and the JCE are frameworks for accessing and developing cryptographic functionality for the Java platform. This functionality encompasses the parts of the Java 2 security API related to cryptography. The JCA and the JCE were designed around four principles: implementation independence, implementation interoperability, algorithm independence, and algorithm extensibility. 11.1.2.1 Implementation IndependenceImplementation independence allows a Java program to use cryptographic functions without having to deal with their implementation. This is achieved by using a provider-based architecture. The JCA and the JCE allow any number of vendors to register their own implementations of the algorithms. For example, if a particular application uses the MD5 implementation supplied by provider A and if it is later decided that the implementation supplied by provider B would be more appropriate, the application code does not need to be changed. Providers can be configured declaratively . Therefore, the choice of one provider over another does not influence the code of an application (see Figure 11.1). Figure 11.1. Implementation Independence
The provider infrastructure permits implementations of various algorithms to be found at runtime, without any changes to the code. Thanks to the principle of implementation independence, providers may be updated transparently to the application: for example, when faster or more secure versions are available. 11.1.2.2 Implementation InteroperabilityImplementation interoperability means that various implementations can work with one another, use one another's keys, or verify one another's signatures. For example, if user Alice signs a document using a program that relies on the DSA implementation supplied by provider A , user Bob can verify the authenticity of that signature with his own program, even if it relies on the DSA implementation supplied by provider B (see Figure 11.2). Similarly, for the same algorithm, a key generated by one provider would be usable by another. Figure 11.2. Implementation Interoperability
11.1.2.3 Algorithm IndependenceAlgorithm independence is achieved by defining types of cryptographic services and introducing classes that provide the functionality of these cryptographic services. These classes are called engine classes . An engine class defines API methods that allow applications to access the specific type of cryptographic service it provides. The implementations supplied by providers implement the corresponding SPI classes. Examples of engine classes are the MessageDigest , Signature , and KeyFactory classes in package java.security ; the corresponding SPI classes are MessageDigestSpi , SignatureSpi , and KeyFactorySpi , still in package java.security . Representing all functions of a given type by a generic engine class masks the idiosyncrasies of the algorithm behind a standardized Java class behavior. Thanks to the principle of algorithm independence, implementations of various algorithms providing the same cryptographic functions must expose the same API. For example, an implementation of the MD5 algorithm and an implementation of SHA-1 need to expose the same API because, even though MD5 and SHA-1 are different algorithms, they both represent the same cryptographic service (see Figure 11.3). Application code invokes the MessageDigest API class, specifying the desired algorithm. The MessageDigest API class transparently invokes the MessageDigestSpi class supplying the implementation for the specified algorithm. Figure 11.3. Algorithm Independence
As Figure 11.3 shows, application code needs to interact only with API engine classes. Providers transparently supply the various cryptographic service implementations through the SPI provider classes. If an application needs to generate MD5 message digests, the getInstance() factory method in the MessageDigest API can be invoked, specifying, for example, that the MD5 algorithm should be used. If the application developer later decides that SHA-1 should be used in place of MD5, the call to getInstance() should be changed to reflect the requirement for SHA-1, but all the other MessageDigest method calls can stay the same. 11.1.2.4 Algorithm ExtensibilityAlgorithm extensibility (Figure 11.4) means that new algorithms that fit in one of the supported engine classes can easily be added. For example, if a new message-digest algorithm is invented and an implemenentation of that algorithm becomes available, that implementation can be plugged into the JCA and JCE frameworks as long as it is compliant with the MessageDigest API. Figure 11.4. Algorithm Extensibility
11.1.3 JCA and JCE ProvidersThe concept of provider is essential in the JCA and the JCE. In this section, we look at the design behind the concept of provider and study how to manage providers in the Java language. 11.1.3.1 DesignA framework that supports multiple underlying implementation modules needs to be coupled with the supported modules in some fashion. The coupling can be very rigid or very flexible and capable of selecting more than one module for use. At one extreme is a monolithic framework that is so tightly bound to a single module as to preclude the use of other modules or even different implementations of the same module. At the other end of the spectrum is a highly extensible and configurable framework offering seamless and near-effortless pluggability of different modules and their implementations. The JCA and the JCE are examples of the latter type of framework. They use a CSP infrastructure to support various implementations of cryptographic algorithms and other security mechanisms. The CSP architecture was introduced in the Java 2 SDK V1.2. Modules in a framework provide services that are used by the framework and ultimately by applications that use the framework. Therefore, the framework has to interface with its pluggable modules. The framework/module interface forms the basic coupling between the framework itself and a module. For pluggability, extensibility, and module independence, the JCA and JCE provider architectures use SPIs. An SPI is a set of Java-language interfaces and abstract classes used to provide the implementation of one or more cryptographic services. JCA and JCE providers are pluggable modules, and each of them provides concrete implementations of some SPI methods. The design of the SPI depends on the kind of framework being developed. The design dictates whether a module implements all or a subset of the SPI. The design also determines the granularity of pluggable modules. Object-oriented and Java-language class and interface design principles play a major role in the design of the SPI. The set of SPIs used by the JCA and JCE is very granular. The java.security package and its subpackages contain many SPI interfaces that pluggable JCA security providers can implement. Similarly, the javax.crypto package and its subpackages contain many SPI interfaces that pluggable JCE security providers can implement. 11.1.3.2 ImplementationThe JCA Provider class in the java.security package defines the concept of a provider. This abstract class must be subclassed by specific providers. The constructor of a Provider class sets the values of various properties that are required for the Java security API to look up the algorithms or other facilities implemented by the provider. Each Provider class instance has a case-sensitive name , a version number, and a string description of the provider and its services. These three pieces of information can be obtained by calling the methods getName() , getVersion() , and getInfo() , respectively. Additionally, the Provider class has methods for accessing information about the implementations of the algorithms, such as key generation, conversion and management facilities, signature generation, and message digest creation. A provider is said to be a main provider if it implements all the SPI methods. Every provider must exhibit a master class , which is a subclass of java.security.Provider . The only requirement of a master class is that it must have a default constructor so that it can be loaded by the JCA and JCE infrastructure when the JVM starts up. The essential function of a master class is to define property/value pairs, in which each property is an SPI label and the corresponding value is the name of a class that implements that SPI. For each cryptographic service, a particular implementation is requested and instantiated by calling a getInstance() factory static method on the corresponding engine class, specifying the name of the desired algorithm and, optionally , the name of the Provider whose implementation is desired. If none is specified, getInstance() relies on the java.security.Security class to search the registered providers for an implementation of the requested cryptographic service associated with the named algorithm. In any JVM, providers are installed in a given preference order specified in the java.security file. Listing 11.1 shows the frag ment of a java.security file enumerating all the providers installed in a Java 2 Runtime Environment (J2RE) V1.4. Listing 11.1. Fragment of a java.security File Listing the Providers Installed on a J2REsecurity.provider.1=com.ibm.jsse.IBMJSSEProvider security.provider.2=com.ibm.crypto.provider.IBMJCE security.provider.3=com.ibm.security.jgss.IBMJGSSProvider security.provider.4=com.ibm.security.cert.IBMCertPath This java.security file fragment enables the following four IBM providers:
The order in which the providers are enumerated in the java.security file is also the one in which the Security class searches them when no specific provider is requested. If the implementation is found in the first provider, that implementation is used. If it is not found, an implementation is searched for in the second provider, and so on. If an implementation is not found in any provider, a java.security.NoSuchAlgorithmException is raised. Calls to getInstance() methods that include a Provider argument enable developers to specify from which provider they want an algorithm. A program can also obtain an array of all the installed Provider s using the java.security.Security.getProviders() static method; the program can then choose a Provider from the returned array. Alternatively, it is possible to invoke the Security.getProvider() static method, which returns the Provider with the name specified in the argument or null if the specified Provider is not found. Listing 11.2 enumerates all the providers installed on your Java 2 SDK system and shows for each of them the name, version number, and general information on the cryptographic services supported and the algorithms implemented. Listing 11.2. GetProviderInfo.javaimport java.security.Provider; import java.security.Security; class GetProviderInfo { public static void main(String[] args) { System.out.println ("Providers installed on your system:"); System.out.println("-------------------------------"); Provider[] providerList = Security.getProviders(); for (int i = 0; i < providerList.length; i++) { System.out.println("[" + (i + 1) + "] - Provider name: " + providerList[i].getName()); System.out.println("Provider version number: " + providerList[i].getVersion()); System.out.println("Provider information:\n" + providerList[i].getInfo()); System.out.println("----------------------------"); } } } As you can see, the GetProviderInfo Java program uses the getProviders() method in the Security class and builds an array of Provider objects with all the providers installed on the system. Then, on each Provider object, the program invokes the methods getName() , getVersion() , and getInfo() to get the provider's name, version number, and general information, respectively. Listing 11.3 shows how you can discover some more information by adding the following lines of code to the for loop of GetProviderInfo.java . Listing 11.3. Additional Code to Be Added to GetProviderInfo.javaEnumeration properties = providerList[i].propertyNames(); while (properties.hasMoreElements()) { String key, value; key = (String) properties.nextElement(); value = providerList[i].getProperty(key); System.out.println("Key: " + key + " - Value: " + value); } These additional lines of code make use of the fact that the Provider class extends java.util.Properties and so inherits the propertyNames() method, which returns a java.util.Enumeration object. The while loop goes through all the properties of the Provider objects installed on the system and prints a list of the keys and values, from which you can understand the cryptographic services supported by the providers installed on your system and the algorithms implemented. For this code to work, GetProviderInfo.java must also import java.util.Enumeration . Let us now consider a scenario in which an application needs an implementation of the MD5 message-digest algorithm. The application will typically obtain an instance of the MessageDigest engine class and pass the java.lang.String "MD5" as the argument to the getInstance() factory method: MessageDigest md = MessageDigest.getInstance("MD5"); Internally, the getInstance() method asks the Security class to supply the required object. As no specific Provider has been passed as an additional argument to the getInstance() method, the Security class in turn asks all the providers in the sequence they are listed in the java.security file, until a provider implementing the requested algorithm is found. A provider manages the individual algorithm classes. When the first provider in the list receives the request from the Security class, two things can happen.
If an MD5 implementation has been found by one of the providers in the list, the Security class passes the MD5 implementation to the getInstance() method of the MessageDigest class, which returns a MessageDigest object, md . The MessageDigest object is now ready to be used. For example, if an array of bytes, say, inputData , has to be hashed into a digest using the MD5 algorithm, the update() method for the MessageDigest object will be used: md.update(inputData); To compute the digest value, the digest() method for the MessageDigest object will be used: byte[] digest = md.digest(); This way, we have demonstrated how the provider architecture allows for implementation and algorithm independence in the case of the message digest cryptographic service. The same procedure is adopted with any other cryptographic service, such as digital signature and key-pair generation. Figure 11.5 shows how vendor and algorithm independence are achieved when a particular Java application requests the implementation of a key-pair generation algorithm. Figure 11.5. Implementation and Algorithm Independence
11.1.3.3 Configuration and ManagementProviders can be installed by first copying the provider package into the file system and then configuring the provider. Copy the Provider PackageSimply place the JAR file(s) containing the provider library classes anywhere on the application class path or even on the boot class path. However, the best solution is to supply the provider library as an installed or bundled extension, by placing the JAR file(s) in the extension class path . Configure the ProviderFor this, you simply need to add the provider to your list of approved providers. This can be done in two ways.
11.1.4 Engine and SPI ClassesThe provider architecture of JCA and JCE has been designed to allow implementation and algorithm independence. This way, implementations of various cryptographic services can be found at runtime, without any changes to the code. For this reason, abstract representations of cryptographic services are offered through generic engine classes. The engine classes are the interfaces between the user code and the implementations of the underlying algorithms offered by the installed providers, as shown in Figure 11.6. Figure 11.6. User Code, Engine Classes, and Providers
The following engine classes are defined on the Java 2 platform as part of the JCA framework:
In both the JCA and the JCE, an instance of an engine class encapsulates the implementation of a cryptographic service by one of the providers installed on the Java platform. The engine classes defined on the Java 2 platform as part of the JCE are as follows :
Each engine class can be instantiated by using the getInstance() static factory method. If you pass this method a single argument, it must be the name of the algorithm to be used. In this case, the getInstance() method will ask the Security class to find the first provider in the preference list offering an implementation of that algorithm. Otherwise, you can force this decision and specify two arguments; in this case, along with the algorithm, you will explicitly pass in the Provider name or the Provider instance. An engine class provides the methods to enable applications to access the specific cryptographic service it provides, independent of the particular type of cryptographic algorithm. The MessageDigest engine class, for example, provides access to the functionality for all message-digest algorithms, such as MD5, SHA-1, and so on. The application interfaces supplied by an engine class are implemented in terms of the corresponding SPI. That is, each engine class has a corresponding abstract SPI class, which defines the methods that cryptographic service providers must implement. The name of each SPI class is the same as that of the corresponding engine class, followed by Spi . For example, the SPI class corresponding to the Signature engine class is SignatureSpi . Each SPI class in the Java core API is abstract. It is the responsibility of a provider to subclass it and supply a concrete implementation for it. To supply the implementation of a particular type of service, for a specific algorithm, a provider must subclass the corresponding SPI class and supply implementations for all the abstract methods. By convention, the abstract methods in the SPI class all begin with engine and are declared protected . For example, the SignatureSpi class defines abstract methods, such as engineInitVerify() and engineInitSign() . An instance of an engine class ”the API object obtained by calling the getInstance() factory method on the engine class itself ”encapsulates as a private field an instance of the corresponding SPI class, the SPI object . The implementations of the API object's methods invoke the corresponding methods in the encapsulated SPI object. For example, when called by an application, the digest() method of a MessageDigest API object calls the engineDigest() method on the MessageDigestSpi object encapsulated in it. Similarly, when the updateDigest() method of a MessageDigest API object is called, the method calls the engineUpdateDigest() method on the MessageDigestSpi object encapsulated in it. This scenario is shown in Figure 11.7. Figure 11.7. MessageDigest Engine and SPI Classes
|
< Day Day Up > |