Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

JVM Languages

Java Security Architecture and Extensions


Apr02: Java Security Architecture and Extensions

Application independence of cryptographic primitives

Volker is a member of the staff at the Fraunhofer Institute for Computer Graphics. He can be contacted at [email protected].


The Java Development Kit comes with a number of core packages that comprise the basic support of Java. Included is a reference implementation of the Java Cryptography Architecture (JCA) — a framework that attempts to provide a common interface for accessing basic cryptographic primitives. Such primitives cover digital signatures, certificates, and one-way hash functions.

The JCA is complemented by the Java Cryptography Extension (JCE), which is modeled along the same design principles and provides primitives such as ciphers, key exchange, and keyed hash functions (MACs). Because the reference implementation of the JCE falls under the U.S. Department of Commerce Bureau of Export Administration regulations (http://www.bxa.doc.gov/), uncrippled versions of JCE are not available outside the United States. However, the API documentation is available and a number of international companies have produced cleanroom implementations of this API. These are typically bundled with cryptographic packages implemented along the lines of JCA and JCE. Table 1 lists some available security provider packages.

The goal of JCA/JCE is to provide application independence for particular algorithms and implementations of cryptographic primitives. Ideally, multiple providers should be interchangeable and complement each other, accessible through a well-defined set of API and wrapper classes that comprise the JCA/JCE. Detailed knowledge about the actual provider being used should not be necessary for building cryptographically enhanced applications. In the best of worlds, applications only need to query for an algorithm name and desired key length, and can locate and use this cipher if it is supported by some installed provider.

Unfortunately, we are not in the best of worlds and building industry-strength crypto-enabled software based on the available providers is problematic. Problems are caused by shortcomings in the JCA/JCE, which are in turn exacerbated by sloppy implementation and interpretation of the API and implementation guidelines. In this article, I'll present techniques for building providers in an interoperable fashion, identifying mistakes to avoid. I'll also outline the scope of applications that should be feasible to implement based on a provider-independent layer — in particular, the JCA/JCE. For more detailed information on JCA/JCE, see Java Cryptography, by Jonathan B. Knudsen (O'Reilly & Associates, 1998) and Java Security, by Scott Oaks (O'Reilly & Associates, 1998).

Building on JCA/JCE

Beyond pure cryptography, significant effort is required to make applications communicate securely. Secured messages, as well as the keys and algorithm parameters, need to be encoded for proper decoding. Additionally, alternative algorithms and structures must be identified in a unique way. A number of standards address this, notably the PKCS family that covers — but is not limited to — RSA key representation and encryption, Diffie-Hellman key agreement, password-based encryption, the syntax of cryptographically protected messages, representation of encrypted private keys, and syntax of certificate requests. The PKCS standards are defined in terms of ASN.1 and make use of structures and attributes predefined in ITU-T Recommendations X.501 and X.509, which in turn refer to a vast number of further ISO and ITU standards. Encoding/decoding of the various structures defined in these standards is governed by another ITU-T Recommendation, X.690. Implementing all of these standards can be exhausting work.

Nevertheless, companies are doing just that — and some even provide source code of their implementations. Some of the standards must be dealt with on the level of JCA/JCE providers (for instance, PKCS#1, PKCS#3, PKCS#5, PKCS#8, and X.509 certificates). Others are located above the layer of JCA/JCE (PKCS#7 and PKCS#10, for instance). Still others, such as the X.509 AlgorithmIdentifier, are used both by providers and libraries built on top of the JCA/JCE. From a software designer's perspective, an architecture like Figure 1 is desirable. In other words, the PKCS#7 implementation should not depend upon a particular provider, but instead use the abstractions provided by the JCA/JCE. Any implementation that does not live up to these expectations is either hardwired to a particular provider or must return generic ASN.1 structures for which the application has to do the mapping to JCE/JCA structures.

The first choice is clearly undesirable, since the advantages of the JCA/JCE architecture are negated, and the application becomes dependent on a single provider. The second choice is inefficient. To use new algorithms with a PKCS#7 implementation, adding new providers that implement these algorithms should be all there is to do. Ideally, there would be a Java Extension and API for ASN.1, on which both providers and layers atop the JCA/JCE can build. This would spare providers the effort of reimplementing subsets of ASN.1 and the Distinguished Encoding Rules, which are the default encoding rules for virtually all opaque representations of keys and algorithm parameters in the JCA/JCE.

A truly JCA/JCE-integrated implementation of PKCS#7 and related objects should use the semantics of the JCA/JCE. An implementation of AlgorithmIdentifier should return an instance of java.security.AlgorithmParameters rather than an ASN.1 object; implementations of PKCS#7 SignedData should be able to locate a suitable Signature engine on their own, and implementations of PKCS#7 EncryptedContentInfo should be able to decrypt with little more than a RecipientInfo and private key, to name some examples.

The key to these features is how algorithms and structures are identified throughout the standards. This is done by means of a built-in ASN.1 type, which is the Object Identifier (OID) — a sequence of integers that uniquely identifies an entity such as a company, algorithm, or data structure. The common notation for OIDs is to write the succession of its integers separated by dots. Each successive integer stands for a node in the tree of all registered OIDs. For instance, RSA Laboratories is registered under OID 1.2.840.113549; starting with this prefix, RSA can define OIDs of its own choosing. For instance, the PKCS#1 Standard is denoted by 1.2.840.113549.1.1 and the RSA algorithm defined in PKCS#1 is denoted by 1.2.840.113549.1.1.1, both chosen by RSA Labs.

Knowing the algorithm being used is sometimes not enough. Algorithms such as symmetric block ciphers in cipher block chaining mode require an initialization vector (IV). Some ciphers support multiple key lengths. Key lengths and IVs are parameters that are required for the correct initialization and operation of such ciphers. For this reason, cryptographic algorithms are identified in X.509 and the PKCS family of standards by means of an AlgorithmIdentifier, which contains the OID of the algorithm in question as well as the parameters, if any. Listing One is the ASN.1 structure of the AlgorithmIdentifier type.

The PKCS#7 SignerInfo structure (see Listing Two) utilizes two AlgorithmIdentifiers — one for the digest algorithm that is used to reduce/hash a message of variable length to a digest value of fixed length, and another for the raw signature/encryption algorithm used to transform the digest. Both jointly define the signature algorithm. For instance, the Digital Signature Standard (DSS) defines a way of producing digital signatures based on the Secure Hash Algorithm (SHA) and the Digital Signature Algorithm (DSA). The AlgorithmIdentifiers in a SignerInfo specifying a DSS signature thus contain the OIDs (multiple alternative OIDs exist; for simplicity, I only give one per algorithm) 1.2.840.10040.4.1 (DSA) and 1.3.14.3.2.26 (SHA1). The combined signature scheme DSS=SHA1/DSA is also identified by a number of alternative OIDs of which one is, for instance, 1.2.840.10040.4.3.

However, implementing SignerInfo and AlgorithmIdentifier in Listings One and Two can lead to the following problems.

JCA/JCE Details and Interpretation

Security providers are at the heart of the architecture. They register the various cryptographic engine classes that may be requested by means of the JCA and JCE API. Table 2 lists the engine types covered by JCA/JCE. Each particular engine is located through a hash map that maps the engine type and algorithm name to the class that implements the according primitive.

In addition to engine classes, provider packages (should) contain transparent representations of keys and algorithm parameters, and opaque representations. Transparent representations provide a common view on implementation-specific details of keys and parameters (such as the modulus and exponent of an RSA public key) by means of a well-known interface. KeySpecs, with the exception of EncodedKeySpecs, are transparent representations of key material, and AlgorithmParameterSpecs are transparent representations of algorithm parameters.

Opaque representations hide details but provide a container for the encoded representation of keys and parameters, which is required to exchange such data. For instance, the primary encoding of RSA keys is that described in PKCS#1 and PKCS#8. Classes that implement the Key interface are opaque representations of key material. Likewise, classes inheriting from AlgorithmParametersSpi represent opaque representations of algorithm parameters.

Applications needing to work with a transparent representation of parameters of algorithm Xi need to import a class XiParameterSpec at compile time. This is clearly a limitation because once algorithm Xi is broken, the code is wasted and replacing Xi with another algorithm requires a modification, recompilation, and redistribution of the application. An alternative approach is to work on opaque representations. For keys, this is not problematic; all engine types that require keys take Key objects rather than KeySpecs, and all engine types that generate keys return Key objects as well. Generators of keys and parameters can be initialized by means of an abstract notion of strength. It is up to the implementations to generate any algorithm parameters that cannot be derived from this strength parameter.

Algorithm parameters are treated more stepmotherly. The engines Signature, Mac, and KeyAgreement do not support initialization with opaque representations. There is a way out of this dilemma: The AlgorithmParameters class declares a method getParameterSpec(Class paramSpec), which returns an AlgorithmParameterSpec instance. The only problem is that the understanding of how this method should behave is generally counter to the problem at hand. Among behaviors some implementations exhibit are:

  • Ignore paramSpec and return an instance of a hardcoded class Y.
  • If the name of paramSpec equals the name of a hardcoded class Y, then return an instance of Y. Otherwise, throw an exception.

  • If a hardcoded class Y is either the same as or a superclass or superinterface of paramSpec, then return an instance of Y; otherwise, throw an exception.

Leaving the first option aside, getting an AlgorithmParameterSpec from an AlgorithmParameters instance thus requires knowledge of the class or interface (or subclass or subinterface) of the class supported by that instance. This is counterintuitive to what the JDK API documentation says about this method: "paramSpec identifies the specification class in which the parameters should be returned." Thus, I suggest this alternative behavior: If this instance supports an AlgorithmParameterSpec class Y that can be casted to paramSpec, then return an instance of Y; otherwise, throw an exception.

This is both a more faithful and useful interpretation of the method's documentation. In particular, this interpretation lets you pass AlgorithmParameterSpec.class as paramSpec and get a valid default AlgorithmParamertersSpec in return, which can be used to initialize engines that do not support opaque parameter representations. (The same argument holds for implementations of KeyFactory and SecretKeyFactory; both provide methods for converting opaque key representations into transparent ones.) Listing Three is a correct implementation.

Engine implementations are identified by three parameters: the engine type, algorithm name, and provider name. If a provider name is not given, installed providers are searched for a matching engine implementation in the order of preference (the order in which they are registered). For this to work, providers declare the supported engine types and algorithms by means of properties — key/value pairs of type String that are stored in a hash table. The key is the engine type and algorithm name, and the value is the fully qualified name of the class that implements that algorithm. Table 3 lists the syntax of these properties.

The substitution pattern <stdalg> denotes the standard algorithm name as specified in the Java Cryptography Architecture and Extension API Specification & Reference and the corresponding document for the JCE. If a standard name has not been specified for the algorithm in question, then a new name must be chosen, which should be modeled along the lines of the standard names. The second line in Table 3 is an alias scheme that can be used to define alternative names for engine implementations. The alias must be mapped to a standard name. Aliases are resolved at most once and resolving is done only in the scope of the provider that defines the alias.

Aliases are used, for instance, to map OIDs to the standard algorithm name of the engine that implements the algorithm referred to by the OID (see Table 4). Likewise, a special form of aliases maps a standard algorithm name of an engine to a preferred OID for that algorithm. However, this type of mapping is inefficient because the image of the mapping is defined in the key and all entries in the provider hash map must be searched for a matching value/key pattern. Additionally, the JCE/JCA specification defines the alternative "slashed" name form <digestalg>/<sigalg> for signature algorithms. Both <digestalg> and <sigalg> stand for the standard name of the digest and raw signature/encryption algorithm name used in the combined signature scheme.

Now, again consider the ASN.1 structure SignerInfo that defines a signature algorithm in terms of two OIDs — one for the digest algorithm and another for the raw signature/encryption algorithm that together comprise the signature algorithm to use for verifying a signature. Signature engines designed according to the JCA already combine the digesting and encryption/verification step. Consequently, signature engines are requested by means of the standard name or an OID of the combined sequence. Implementations of SignerInfo that fall back to proprietary OID mappings might not support the complete suite of algorithms supported by the installed providers.

A solution that takes OIDs declared by the individual installed providers into account is more desirable at this point. The approach that I take exploits the slashed form of signature engine names. The OIDs extracted from the SignerInfo are first resolved to the standard names of the corresponding MessageDigest and KeyPairGenerator engines using the alias mechanism. This requires that OIDs are set up as aliases for said engine types. Next, the standard names are combined to the slashed form, which is resolved to the standard name of the Signature engine. This name is then used to request the Signature engine implementation. The reverse process is similar. The standard signature algorithm name is resolved to its slashed form, which is split into the digest algorithm and cipher name. Both are mapped to the respective OIDs using the alias form for preferred OIDs. The resolving is done over the mappings of all installed providers and resolved combinations are kept in a cache. All required mappings are "learned" from the installed providers in this way. This scheme works only if providers support appropriate declaration of OIDs and slashed forms, and the naming discipline is kept by the providers.

A special name form is supported for cipher transformations; the syntax for transformations is <cipher>[/<mode>/<padding>] (square brackets denote optional elements). <cipher> is the name of the cipher algorithm, <mode> the name of the operation mode to use, and <padding> the padding scheme to use for padding the cipher text to the block size of a block cipher. This name form is parsed by the Cipher class in its getInstance(String transformation [,provider]) method. The question that remains is which name to use when requesting a matching AlgorithmParameterGenerator instance for a given cipher transformation. The padding scheme is not significant for choosing between different algorithm parameters. My recommendation is to use <cipher>/ <mode> from the returned name first, then try <cipher> in case the first attempt fails.

Conclusion

I've outlined here a piece of software (PKCS standards support) that can be implemented in a provider- and algorithm-independent way based on the JCA/JCE. Some of its features cannot be implemented in a straightforward way due to limitations of the JCA/JCE algorithm naming scheme. However, there is a feasible solution based on the definition of appropriate aliases that depends on the strict adherence of providers to the specifications of the JCA/JCE, in particular, to the naming and aliasing schemes. Sun included prominent notice in the specification documents that the aliasing scheme may change or even be eliminated in the future. However, this mechanism is significant for the transparent mapping of OIDs and algorithm names and is fundamental to the transparent resolution of the signature engine identification problem. Improvements of the aliasing scheme should provide a more efficient reverse mapping of algorithm names to the preferred OID.

Furthermore, Java security provider packages must put more emphasis on parameter handling and algorithm initialization issues. I've experimented with several Java security provider packages and particularly observed some deficiencies (please note that I've consulted for the developers of the CDC provider, which avoids the following deficiencies):

  • OID mappings for symmetric ciphers are not defined.
  • OID mappings for AlgorithmParameters engines are not defined.

  • Implementations of AlgorithmParameterGeneratorSpi are not provided for symmetric ciphers.

  • Providers rarely implement AlgorithmParametersSpi classes for symmetric ciphers.

  • Method Cipher.getParameters is not properly supported in many Cipher implementations.

  • Default conversion of opaque parameters to parameter specs is not supported properly.

Implementations of AlgorithmParameterGeneratorSpi are particularly important because they are a sine qua non for transparent initialization of ciphers in a way that is independent of a particular provider and algorithm. In this vein, the opaque initialization implementation (source code available electronically; see "Resource Center," page 5) shows how cipher algorithms can be initialized transparently.

Likewise, the engine-name resolution tool (also available electronically) provides forward and reverse mapping of engine names to OIDs, including the resolution of signature engine names to raw cipher and digest OIDS, and vice versa.

Finally, a sample provider class with extensive and accurate declaration of OIDs, aliases, and parameter engines is also available electronically. The full code — including sample implementations of algorithm parameter generators and parameter representations — is available at http://www.semoa.org/, along with the source code of a JCE cleanroom implementation with accurate alias resolving and class loading and an implementation of ASN.1, DER, PKCS#7, X.501 names and further elements of PKCS and ITU standards. The PKCS#7 implementation uses the engine-name resolution tool and does not depend on any particular security provider.

DDJ

Listing One

AlgorithmIdentifier ::= SEQUENCE {
  algorithm OBJECT IDENTIFIER,
  parameters ANY DEFINED BY algorithm OPTIONAL
}

Back to Article

Listing Two

SignerInfo ::= SEQUENCE {
  version Version,
  issuerAndSerialNumber IssuerAndSerialNumber,
  digestAlgorithm DigestAlgorithmIdentifier,
  authenticatedAttributes [0] IMPLICIT Attributes OPTIONAL,
  digestEncryptionAlgorithm DigestEncryptionAlgorithmIdentifier,
  encryptedDigest EncryptedDigest,
  unauthenticatedAttributes [1] IMPLICIT Attributes OPTIONAL, 
}

Back to Article

Listing Three

public class MyAlgorithmParametersSpi extends AlgorithmParametersSpi
 { 
   private MyAlgorithmParameterSpec mySpec;
   public AlgorithmParameterSpec engineGetParameterSpec(Class paramSpec)
     throws InvalidParameterSpecException
     {
      if (paramSpec.isAssignableFrom(MyAlgorithmParameterSpec.class))
          return mySpec;
      throw new InvalidParameterSpecException("Unsupported parameter spec!");
     }
}

Back to Article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.