11.2 The JCA API

 < Day Day Up > 

This section explains how to use the most important JCA API classes from within Java programs in a J2EE environment.

11.2.1 The java.security.SecureRandom Class

This class provides a cryptographically strong pseudorandom number generator (PRNG). The package java.security also offers the class SecureRandomSpi , which defines the SPI for SecureRandom . This SPI can be used for more advanced SecureRandom implementations , such as true random number generator (TRNG). Let us consider the following instruction:

 SecureRandom r = new SecureRandom(); 

This instruction obtains a SecureRandom object containing the implementation from the highest-priority installed security provider that has a SecureRandom implementation. Another way to instantiate a SecureRandom object is via the static method getInstance() , supplying the algorithm and optionally the provider implementing that algorithm:

 SecureRandom random =    SecureRandom.getInstance("IBMSecureRandom", "IBMJCE"); 

The SecureRandom implementation attempts to completely randomize the internal state of the generator itself unless the caller follows the call to the getInstance() method with a call to the setSeed() method. When calling setSeed() , the given seed supplements, rather than replaces , the existing seed. Thus, repeated calls are guaranteed never to reduce randomness.

The sample code in Listing 11.8 on page 402 shows how to use the SecureRandom class to initialize a KeyPairGenerator object. The examples in Listing 11.4 on page 395 and Listing 11.5 on page 395 demonstrate how to make calls to setSeed() .

11.2.2 The java.security.Key Interface

The package java.security offers several interfaces and classes to provide key generation and management. The Key interface is the top-level interface for all opaque keys. In fact, this interface defines the functionality shared by all opaque key objects. Because the key representation offered by the Key interface is opaque, this interface defines only three methods getAlgorithm() , getEncoded() , and getFormat() which correspond to the three characteristics of any opaque key:

  1. An algorithm. The key algorithm is usually an encryption or asymmetric operation algorithm, such as DSA or RSA. The name of the algorithm of a Key is obtained calling the getAlgorithm() method.

  2. An encoded form. This is an external encoded form for the Key . An external encoded format is used when a standard representation of the key is needed outside the JVM, as when transmitting the key to another party. The key is encoded according to a standard format, such as X.509 or PKCS#8 and is returned using the getEncoded() method as an array of bytes.

  3. A Format. The name of the format of the encoded key is returned by the getFormat() method as a String object.

11.2.3 The PublicKey and PrivateKey Interfaces in Package java.security

The PrivateKey and PublicKey interfaces in package java.security extend the Key interface and contain no methods or constants but merely serve to group and provide type safety forall public- and private-key interfaces. The specialized public- and private-key interfaces, such as the DSAPublicKey and DSAPrivateKey interfaces in the java.security.interfaces package, extend PublicKey and PrivateKey , respectively. All the sample code presented in Section 11.2.9 on page 400 shows how to use the PublicKey and PrivateKey interfaces.

11.2.4 The java.security.KeyFactory Class

The java.security package also contains classes to manage keys and key pairs; the KeyFactory class, for example, is used to convert keys into key specifications (and vice versa), and the KeyFactorySpi class is used to define the SPI for the KeyFactory class. A representation of key material is opaque if it does not expose any direct access to the key material fields.

A KeyFactory is bidirectional. This means that it allows you to build an opaque key object from given key material (specification) or to retrieve the underlying key material of a key object in a suitable format. A KeyFactory object can be created using the static KeyFactory.getInstance() method. From a key specification, you can generate the keys by using the generatePublic() and generatePrivate() methods. To get the key specification from a KeyFactory , you can use the getKeySpec() method. Sample code using the KeyFactory class is available in Listing 11.9 on page 406.

11.2.5 The java.security.KeyPair Class

The KeyPair class is used to hold a public key and a private key. The class provides getPrivate() and getPublic() methods to get the private and the public keys, respectively. This class does not enforce any security and, when initialized , should be treated like a PrivateKey . Sample code using the KeyPair class is shown in Listing 11.8 on page 402.

11.2.6 The java.security.KeyPairGenerator Class

The KeyPairGenerator class is used to generate key pairs, whereas the KeyPairGeneratorSpi class is used to define the SPI for the KeyPairGenerator class. A KeyPairGenerator object can be created using the getInstance() static factory method for the KeyPairGenerator class. A KeyPairGenerator object must be initialized before it can generate keys. To do this, four methods are provided, all of them called initialize() but each having a different signature. The four initialize() methods allow you to

  • Generate a key pair in an algorithm-independent manner or in an algorithm-specific manner

  • Provide a specific source of randomness or use the java.security.SecureRandom implementation of the highest-priority installed provider as the source of randomness

When you initialize a key pair in an algorithm-independent manner, you specify the key size . If you initialize in an algorithm-specific way, you supply the AlgorithmParameterSpec to the generator.

In the algorithm-independent case (Listing 11.4), it is up to the provider to determine the algorithm-specific parameters to be associated with each of the keys. The provider may use precomputed parameter values or may generate new values.

Listing 11.4. Algorithm-Independent Key-Pair Initialization
 KeyPairGenerator kGen = KeyPairGenerator.getInstance("DSA"); SecureRandom rd =    SecureRandom.getInstance("IBMSecureRandom", "IBMJCE"); rd.setSeed(userSeed); kGen.initialize(1024, rd); 

In the algorithm-specific case, the user supplies the parameters to initialize a key pair (Listing 11.5).

Listing 11.5. Algorithm-Specific Key-Pair Initialization
 KeyPairGenerator kGen = KeyPairGenerator.getInstance("DSA"); DSAParameterSpec dsaSpec = new DSAParameterSpec(p, q, g); SecureRandom rd =    SecureRandom.getInstance("IBMSecureRandom", "IBMJCE"); rd.setSeed(userSeed); kGen.initialize(dsaSpec, rd); 

Note that java.security.KeyPairGenerator , java.security.SecureRandom , and java.security.spec.DSAParameterSpec must be explicitly imported in order for the preceding code fragments to work properly. Listing 11.8 on page 402 presents a complete example of KeyPairGenerator use.

11.2.7 The java.security.KeyStore Class

A keystore is a database of private keys and their associated certificates or certificate chains, which authenticate the corresponding public keys. The JCA provides the KeyStore API to manage keystores and to represent in-memory collections of keys and certificates, whereas the KeyStoreSpi class defines the SPI for the KeyStore class. You can generate a KeyStore object by using the static getInstance() factory method. When generating a KeyStore object, you must specify the KeyStore type as a parameter to the getInstance() method. The type indicates the format in which certificates and keys are stored in the keystore.

The default keystore implementation, provided in the reference implementation of Java 2 SDK, is a flat file, using a proprietary keystore type or format, named Java keystore (JKS). This format protects the integrity of the entire keystore with a keystore password. A hash value of the entire keystore is used to protect the keystore from alteration. The keystore itself is not encrypted; the hash value is used only to detect whether the keystore has been altered by unauthorized entities. However, each private key in the keystore is encrypted with a separate password, which may be identical to the keystore password. With the JCE keystore (JCEKS) implemented by IBM and shipped as part of the IBM JCE, the keystore itself is encrypted. Other common implementations use the PKCS#12 standard (see Section 12.1.7 on page 437).

To load a KeyStore object from a java.io.InputStream , the load() method is provided. The store() method can be used to store the keystore to a java.io.OutputStream . Additionally, the KeyStore class exposes methods to get and set keys and certificates from a given keystore. For instance, aliases() lists all the alias names in the keystore, deleteEntry() deletes the entry identified by a specific alias from the keystore, and getKey() gets the key associated with a given alias from the keystore.

Although the Java 2 SDK offers the keytool utility to manage keystores, sometimes it is necessary to manage a keystore programmatically. For example, a J2EE application may need to access a keystore to obtain the public key of an entity to verify a digital signature. Listing 11.6 shows how to load a KeyStore object, get a digital certificate with alias marco from it, and store that certificate into another KeyStore object. The code comments explain the operations in detail.

Listing 11.6. How to Use the KeyStore Class
 import java.security.KeyStore; import java.security.cert.X509Certificate; import java.io.FileInputStream; import java.io.FileOutputStream; // Other code goes here... // Create the KeyStore object KeyStore ks1 = KeyStore.getInstance("JKS", "IBMJCE"); String keypass = "marcop"; char[] pwd = keypass.toCharArray(); // Load the keystore from the file system FileInputStream fis1 = new FileInputStream("keystore1"); ks1.load(fis1, pwd); // Get the certificate from the keystore with alias marco X509Certificate cert =     (X509Certificate) ks1.getCertificate("marco"); // Store the same certificate in the other keystore. // Create the KeyStore object. KeyStore ks2 = KeyStore.getInstance("JKS", "IBMJCE"); FileInputStream fis2 = new FileInputStream("keystore2"); // Load the keystore. ks2.load(fis2, pwd); // Insert the certificate in the keystore. ks2.setCertificateEntry("marco", cert); // Store the keystore. FileOutputStream fos2 = new FileOutputStream("keystore2"); ks2.store(fos2, pwd); // Other code goes here... 

The purpose of this code fragment is to show how to use the Java security API to manage keystores. For simplicity, we have assumed the following.

  • The keystore from which the certificate is loaded is called keystore1 , and the one to which the certificate is stored is called keystore2 . Both of these files are assumed to be located in the directory from which the program is launched. The file names are hard-coded.

  • Both keystores are of type JKS.

  • All passwords are set to marcop , and this value is hard-coded. Because the source code of servlets and enterprise beans is not accessible from the client, there are no risks of decompilation attacks.

  • In keystore1 , the key pair and the certificate wrapping the public key are associated with an alias called marco .

The code fragment in Listing 11.6 loads keystore1 , gets the certificate associated with the alias marco , inserts this certificate into keystore2 , and stores the destination keystore into the file system. Listing 11.10 on page 408 provides another example showing how to use the KeyStore class.

11.2.8 The java.security.MessageDigest Class

An example of an engine class is the java.security.MessageDigest class, which provides access to message-digest algorithms. The MessageDigest class provides the functionality of cryptographically secure message digests, such as SHA-1 or MD5, whereas the MessageDigestSpi class defines the SPI for the MessageDigest class. The package java.security also offers the DigestInputStream and DigestOutputStream classes for reading and writing digest information.

As we explained in Section 10.2.2.4 on page 356, a cryptographically secure message digest takes arbitrary-sized inputa byte arrayand generates a fixed- sized outputthe message digest , or hash . A digest has the following properties.

  • It should be computationally infeasible to find two messages that hash to the same value.

  • The digest must not reveal anything about the input that was used to generate it.

With the MessageDigest class, as with all the other engine classes, you can obtain an object by using the getInstance() static factory method. You must supply either the algorithm or the algorithm and the provider. The following line of code constructs a MessageDigest object initialized to use the SHA-1 algorithm:

 MessageDigest md = MessageDigest.getInstance("SHA1"); 

A caller may optionally specify the name of a provider, which will guarantee that the implementation of the algorithm requested is from the named provider, as in the following line of code:

 MessageDigest md = MessageDigest.getInstance("SHA1", "IBMJCE"); 

The JCA is case insensitive with regard to algorithm names. Therefore, passing the parameter "SHA1" or "Sha1" would have still returned a MessageDigest object initialized to use the SHA-1 algorithm.

The MessageDigest class provides an update() method that is used to update MessageDigest objects with the data to be digested. Finally, the data is digested using the digest() method. Figure 11.8 summarizes the sequence of actions that need to be taken on a MessageDigest object to compute the digest of a message, as well as the corresponding methods that need to be invoked in order for those actions to take place.

Figure 11.8. Actions Taken on a MessageDigest Object and Corresponding Methods

graphics/11fig08.gif

Note that a MessageDigest object starts out initialized. In other words, a call to getInstance() returns an initialized MessageDigest object. As Figure 11.8 shows, no additional initialization is needed, and the object is ready to use.

The code fragment in Listing 11.7, which could be embedded in any J2EE application, shows how to use the MessageDigest class in practice.

Listing 11.7. How to Use the MessageDigest Class
 import java.security.MessageDigest; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.BufferedInputStream import java.io.BufferedOutputStream; // Other code goes here... // Obtain a MessageDigest object initialized to use. MessageDigest md = MessageDigest.getInstance("SHA1"); // Get the file to be digested. File inputFile = new File("C:\CONFIG.SYS"); FileInputStream cfis = new FileInputStream(inputFile); BufferedInputStream cbis = new BufferedInputStream(cfis); byte[] cbuff = new byte[1024]; while (cbis.available() != 0) {    int len = cbis.read(cbuff);    // Update the digest with the data to be digested    md.update(cbuff, 0, len); } cbis.close(); cfis.close(); // Finally, calculate the digest. byte[] digest = md.digest(); // Write the digest information to a file. File outputFile = new File("C:\digest.txt"); FileOutputStream cfos = new FileOutputStream(outputFile); BufferedOutputStream cbos = new BufferedOutputStream(cfos); cbos.write(digest); cbos.close(); cfos.close(); // Other code goes here... 

The code in Listing 11.7 calculates the digest of the contents of the file C:\CONFIG.SYS and stores it in the file C:\digest.txt .

11.2.9 The java.security.Signature Class

The java.security.Signature class is an engine class that is designed to provide the functionality of a cryptographic digital-signature algorithm, such as RSA or DSA. As we explained in Section 10.3.3 on page 370, a cryptographically secure signature algorithm takes arbitrary-sized input and a private key and generates a relatively short, often fixed-sized, string of bytes, called the signature , with the following properties.

  • Given the public key corresponding to the private key used to generate the signature, it should be possible to verify the authenticity and integrity of the input.

  • The signature and the public key do not reveal anything about the private key that was used to generate the signature.

A Signature object can be used to sign data, as well as to validate a signature of signed data.

Signature objects are stateful. This means that a Signature object is always in a particular state; only a single type of operation may be performed until the state is changed. States are represented as final static fields of type int . The three states Signature objects may have are UNINITIALIZED , SIGN , and VERIFY . When the Signature object is created, it is in the UNINITIALIZED state. As we will see shortly, the Signature class defines two initialization methods, initSign() and initVerify() , which change the state to SIGN and VERIFY , respectively.

To obtain a Signature object for signing or verifying a signature, the first step is to create a Signature instance. As with all engine classes, the way to get a Signature object for a particular type of signature algorithm is to call the getInstance() static factory method on the Signature class, as in the following code line:

 Signature signature = Signature.getInstance("SHA1withDSA"); 

A caller may optionally specify the name of a provider, which will guarantee that the implementation of the algorithm requested is from the named provider, as in the following line of code:

 Signature signature = Signature.getInstance("SHA1withDSA", "IBMJCE"); 

Unlike a MessageDigest object, when a Signature object is created, it is in the UNINITIALIZED state and must be initialized before it can be used. The initialization method depends on whether the object is first going to be used for signing or for verification.

  • If the Signature object is going to be used for signing, it must first be initialized with the private key of the entity whose signature is going to be generated. This initialization is done by calling the method initSign() , which takes a java.security.PrivateKey object as a parameter. When it is called, this method puts the Signature object in the SIGN state.

  • If instead the Signature object is going to be used for verification, it must first be initialized with the public key of the entity whose signature is going to be verified . This initialization is done by calling the method initVerify() , which takes a java.security.PublicKey object as a parameter. When it is called, this method puts the Signature object in the VERIFY state.

Once a Signature object has been initialized, you can either sign data or verify the signature of previously signed data.

  • If it has been initialized for signing, which implies that it is in the SIGN state, the Signature object can be supplied the data to be signed. This is done by making one or more calls to the update() method. Calls to this method should be made until all the data to be signed has been supplied to the Signature object. At this point, to generate the signature, it is enough to call the sign() method, which takes no parameters and returns an array of bytes representing the digital signature of all the data updated.

  • Once data has been signed, the Signature class can be used again, this time to verify the signed data. To start the process, it is necessary to use a Signature object implementing the same algorithm implemented by the Signature object that was used to sign the data. This new Signature object must have been initialized for signature verification, which implies that it is in the VERIFY state. The data to be verified, as opposed to the signature itself, is supplied to this Signature object by making one or more calls to one of the update() methods. Calls to this method should be made repeatedly until all the data has been supplied to the Signature object. At this point, the signature can then be verified by calling the verify() method, which takes as its only parameter an array of bytes representing the digital signature associated with the data updated. This method returns a boolean value: true if the signature was verified and false if not.

Figure 11.9 summarizes the various phases involved in the use of the Signature class, showing what actions need to be taken to sign data or verify a signature, along with the corresponding methods.

Figure 11.9. Actions Taken on a Signature Object and Corresponding Methods

graphics/11fig09.gif

The code fragment shown in Listing 11.8 demonstrates how to create a key pair, use the private key in the pair to sign a document, and store the signature and the public key in two separate files.

Listing 11.8. How to Use the Signature Class for Signature Generation
 import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.SecureRandom; import java.security.PublicKey; import java.security.PrivateKey; import java.security.Signature; import java.io.FileInputStream; import java.io.BufferedInputStream; import java.io.FileOutputStream // Other code goes here... // Create the key pair. KeyPairGenerator kpg = KeyPairGenerator.getInstance    ("DSA", "IBMJCE"); SecureRandom r = new SecureRandom(); kpg.initialize(1024, r); KeyPair kp = kpg.generateKeyPair(); // Get the generated keys. PrivateKey priv = kp.getPrivate(); PublicKey publ = kp.getPublic(); // Intialize the Signature object with the private key. Signature dsasig = Signature.getInstance     ("SHA1withDSA", "IBMJCE"); dsasig.initSign(priv); // Get the file to be signed. FileInputStream fis = new FileInputStream(inputFile); BufferedInputStream bis = new BufferedInputStream(fis); byte[] buff = new byte[1024]; int len; // Update the Signature object with the data to be signed. while (bis.available() != 0) {    len=bis.read(buff);    dsasig.update(buff, 0, len); } // Close the BufferedInputStream and the FileInputStream. bis.close(); fis.close(); // Get the signature as a byte array. byte[] realSignature = dsasig.sign(); // Write the signature to a file. FileOutputStream fos = new FileOutputStream(signatureFile); fos.write(realSignature); fos.close(); // Write the public key to a file. byte[] pkey = publ.getEncoded(); FileOutputStream keyfos = new FileOutputStream(publicKeyFile); keyfos.write(pkey); keyfos.close(); // Other code goes here... 

This code fragment could be embedded in any J2EE application that needs to sign a file. The comments embedded in the code explain what the code does. A detailed explanation follows.

In this program, a KeyPairGenerator generates keys for the DSA signature algorithm. The KeyPairGenerator class is used to generate a public- and private-key pair. In general, KeyPairGenerator s are constructed using one of the two getInstance() factory methods provided in the KeyPairGenerator class. A KeyPairGenerator for a particular algorithm creates a public- and private-key pair that can be used with this algorithm and also associates algorithm-specific parameters with each of the generated keys. In the code of Listing 11.8, we generate a KeyPairGenerator object by implementing the DSA algorithm provided by the IBM provider:

 KeyPairGenerator kpg =    KeyPairGenerator.getInstance("DSA", "IBMJCE"); 

Then, we initialize the KeyPairGenerator with a random number. The source of randomness is an instance of the SecureRandom class. This class provides a cryptographically strong random-number generator. To get an instance of this class, you can use the getInstance() method, specifying the secure random generation algorithm and the provider that supplies it:

 SecureRandom r =    SecureRandom.getInstance("IBMSecureRandom", "IBMJCE"); 

Another option, which is the option selected for this example, is to call the SecureRandom constructor directly:

 SecureRandom r = new SecureRandom(); 

This instruction obtains a SecureRandom object containing the implementation from the highest-priority installed provider that has a SecureRandom implementation.

We can now create the key pair by using the generateKeyPair() method on the KeyPairGeneration object. The key size is set to 1,024 bits:

 kpg.initialize(1024, r); KeyPair kp = kpg.generateKeyPair(); 

The private and the public keys can be retrieved by using the getPrivateKey() and the getPublicKey() methods of the KeyPair class, respectively:

 PrivateKey priv = kp.getPrivate(); PublicKey publ = kp.getPublic(); 

The Signature object is generated by using the getInstance() factory method of the Signature class. We need to provide the signing algorithm and the provider name. Then, we associate the private key to be used for signing by using the initSign() method:

 Signature dsasig = Signature.getInstance("SHA1withDSA", "IBMJCE"); dsasig.initSign(priv); 

Next, we get the file to be signed. The signature can be generated by using the sign() method after all the data has been updated. As we have explained, once generated, a Signature object has three phases. For signing data:

  1. It must be initialized with a private key, using the initSign() method.

  2. It must be updated with the data to be signed, using the update() method:

     dsasig.update(buff, 0, len); 
  3. The final phase is to sign the data, using the sign() method:

     byte[] realsignature = dsasig.sign(); 

Signature verification consists of similar phases.

  1. The initialization is done with the public key rather than the private key and is performed by calling initVerify() rather than initSign() .

  2. The update is done with the data to be verified rather than the data to be signed.

  3. The sign() method is replaced by the verify() method.

The final step is to save the signature generated and the public key to two files. We need to get the public key in its encoded format before writing it to the file. This can be done using the getEncoded() method provided in the Key interface:

 byte[] pkey = publ.getEncoded(); 

Note that the names of the three files used in the code fragment of Listing 11.8 on page 402 should be defined somewhere else in the program:

  1. The variable inputFile indicates the input file to be signed.

  2. The variable signatureFile indicates the file where the signature will be written.

  3. The variable publicKeyFile indicates the file where the public key will be written.

It is not necessary that the signatureFile and the publicKeyFile files exist. The code creates them automatically.

Once generated, the three files can be sent to the receiver, who will verify the signature through a J2EE application containing the code fragment shown in Listing 11.9.

Listing 11.9. How to Use the Signature Class for Signature Verification
 import java.security.Signature; import java.security.KeyFactory; import java.security.PublicKey; import java.security.spec.X509EncodedKeySpec; import java,io.FileInputStream; import java.io.BufferedInputStream; // Other code goes here... // inputFile is the file whose contents have been signed. FileInputStream fis = new FileInputStream(inputFile); // signatureFile is the file containing the signature. FileInputStream sfis = new FileInputStream(signatureFile); // publicKeyFile is the file containing the signer's public // key. FileInputStream pfis = new FileInputStream(publicKeyFile); // Get the public key of the sender. byte[] encKey = new byte[pfis.available()]; pfis.read(encKey); pfis.close(); X509EncodedKeySpec pubKeySpec =    new X509EncodedKeySpec(encKey); KeyFactory keyFac = KeyFactory.getInstance("DSA", "IBMJCE"); PublicKey pubKey = keyFac.generatePublic(pubKeySpec); // Get the signature from signatureFile. byte[] sigToVerify = new byte[sfis.available()]; sfis.read(sigToVerify); sfis.close(); // Initialize the Signature object for signature verification. Signature sig =    Signature.getInstance("SHA1withDSA", "IBMJCE"); sig.initVerify(pubKey); // Update the Signature object with the data to be verified. BufferedInputStream buf = new BufferedInputStream(fis); byte[] buff = new byte[1024]; int len; while(buf.available() != 0) {    len = buf.read(buff);    sig.update(buff, 0, len); } buf.close(); fis.close(); // Verify the signature. boolean verifies = sig.verify(sigToVerify); if (verifies)    System.out.println("Signature verification succeeded."); else    System.out.println("Signature verification failed."); // Other code goes here... 

This code fragment could be embedded in any J2EE application that needs to verify the digital signature of a file.

The comments embedded in the code explain what the code does. A detailed explanation follows.

First, we must import the encoded public-key bytes from the file containing the public key and convert them to a PublicKey . Hence, we read the key bytes, instantiate the DSA public key by using the KeyFactory class, and generate the key from it:

 byte[] encKey = new byte[pfis.available()]; pfis.read(encKey); pfis.close(); X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(encKey); KeyFactory keyFac = KeyFactory.getInstance("DSA", "IBMJCE"); PublicKey pubkey = keyFac.generatePublic(pubKeySpec); 

The X509EncodedKeySpec class represents the Distinguished Encoding Rules (DER) encoding of a public or private key, according to the format specified in the X.509 standard. The public key can be created from it using the KeyFactory class. This class is used to convert keysopaque cryptographic keys of type Key into key specificationstransparent representations of the underlying key materialand vice versa. We specify the DSA key algorithm and the IBM provider and use the generatePublic() method to generate the PublicKey .

The rest of the code fragment is similar to what needs to be done to sign the contents of a file (see Listing 11.8 on page 402). The only difference is that this time, the Signature is initialized with this PublicKey in place of the PrivateKey , and the sign() method is replaced by the verify() method.

The three files inputFile , signatureFile , and publicKeyFile , used in the code fragment of Listing 11.9 on page 406, should be defined somewhere else in the program. This time, all three files must exist in advance and should be the same files used by the code fragment of Listing 11.8. This way, we are simulating a scenario in which a sender generates a signature and then sends the original file to a receiver, along with the signature and the public key.

The expected output should be as follows.

  • If none of the three files has been altered after the signature was applied, the program should display the following:

     Signature verification succeeded. 
  • If you change the contents of any of the three files, the program displays the following message:

     Signature verification failed. 

    In particular, if you modify the signature file so that it no longer respects the signature format, the code fragment of Listing 11.9 will throw a java.security.SignatureException .

This scenario demonstrates how you can successfully use the Java 2 APIs in a J2EE environment to send documents with proof of data integrity and authenticity.

If you wish to load the keys from a keystore rather than generating them, you can use the code fragment in Listing 11.10 in place of the one shown in Listing 11.8.

Listing 11.10. Using Signature and KeyStore for Signature Generation
 import java.security.KeyStore; import java.security.Signature; import java.security.PrivateKey; import java.security.cert.X509Certificate; import java.io.FileInputStream import java.io.BufferedInputStream; import java.io.FileOutputStream; // Other code goes here... // Access the default keystore in the user home directory. String s1 = System.getProperty("user.home"); String s2 = System.getProperty("file.separator"); FileInputStream fisk = new FileInputStream(s1 + s2 +    ".keystore"); KeyStore ks = KeyStore.getInstance("JKS", "IBMJCE"); // Access the private key and the certificate of the signer. String keypass = "marcop"; char[] pwd = new char[keypass.length()]; keypass.getChars(0, keypass.length(), pwd, 0); ks.load(fisk, pwd); String alias = "marco"; PrivateKey priv = (PrivateKey) ks.getKey(alias, pwd); X509Certificate cert = (X509Certificate)    ks.getCertificate(alias); // Intialize the Signature object. Signature sig =    Signature.getInstance("SHA1withDSA", "IBMJCE"); sig.initSign(priv); // Get the file to be signed. FileInputStream fis = new FileInputStream(inputFile); BufferedInputStream bis=new BufferedInputStream(fis); byte[] buff = new byte[1024]; int len; // Update the Signature object with the data to be signed. while (bis.available() != 0) {    len = bis.read(buff);    sig.update(buff, 0, len); } // Close the BufferedInputStream and the FileInputStream. bis.close(); fis.close(); // Produce the actual signature. byte[] realSignature = sig.sign(); // Write the signature to a file. FileOutputStream fos = new FileOutputStream(signatureFile); fos.write(realSignature); fos.close(); // Write the certificate to a file. byte[] encCert = cert.getEncoded(); FileOutputStream certfos = new FileOutputStream(certFile); certfos.write(encCert); certfos.close(); // Other code goes here... 

This code fragment could be embedded in any J2EE application that needs to sign the contents of a file with a private key obtained from a keystore. The comments embedded in the code explain what the code does.

You can see that this code fragment is very similar to the one shown in Listing 11.8 on page 402. The only difference here is that, in place of generating keys, we load an existing keystore and use keys already created and present in it. The program is configured to retrieve the keystore from the user home directory. System variables are used to grant code portability across the platforms.

You can generate a keystore by using the -genkey option of the keytool command line utility. When you run this sample code, ensure that you have generated a keystore called .keystore on your user home directory. This file name and location are the default for the keystore creation performed by the -genkey command of the keytool utility.

This code fragment also shows how to get the certificate associated with a specified alias and save it into a file so that it can be sent to the receiver for verification. The alias here is set to marco .

We generate the KeyStore object by using the getInstance() factory method for the KeyStore class. The implementation we use is JKS, and the provider is the IBM provider:

 String s1 = System.getProperty("user.home"); String s2 = System.getProperty("file.separator"); FileInputStream fisk = new FileInputStream(s1 + s2 + ".keystore"); KeyStore ks = KeyStore.getInstance("JKS", "IBMJCE"); 

Next, we load the keystore, using the load() method, and supply the keystore password, which is hard-coded as marcop :

 ks.load(fisk, pwd); 

Finally, we get the PrivateKey , with the getKey() method, and the Certificate , with the getCertificate() method, associated with the intended alias:

 PrivateKey priv = (PrivateKey) ks.getKey(alias, pwd); X509Certificate certs = (X509Certificate) ks.getCertificate(alias); 

Note that three pieces of information should be provided by the program embedding this code fragment:

  1. The input file to be signed, indicated by the inputFile variable

  2. The file where the signature will be written, indicated by the signatureFile variable

  3. The file where the certificate will be written, indicated by the certFile variable

Note that it is not necessary that the signature file or the file to which the certificate is exported exist. The program creates them automatically.

At this point, the three files can be sent to the receiver, who will use the code fragment in Listing 11.11 to verify the signature.

Listing 11.11. Signature and X509Certificate Used for Signature Verification
 import java.security.Signature; import java.security.PublicKey import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.io.FileInputStream; import java.io.BufferedInputStream; // Other code goes here... // inputFile is the file whose contents have been signed. FileInputStream fis = new FileInputStream(inputFile); // signatureFile is the file containing the signature. FileInputStream sfis = new FileInputStream(signatureFile); // certFile is the file containing the signer's certificate. FileInputStream cfis = new FileInputStream(certFile); // Get the certificate from the certificate file. CertificateFactory cf =    CertificateFactory.getInstance("X.509"); X509Certificate cert =    (X509Certificate) cf.generateCertificate(cfis); cfis.close(); // Get the public key from the certificate. PublicKey pubkey = cert.getPublicKey(); // Get the signature from the signature file. byte[] sigToVerify = new byte[sfis.available()]; sfis.read(sigToVerify); sfis.close(); // Generate a Signature object by calling the getInstance() // factory method. Signature sig =    Signature.getInstance("SHA1withDSA", "IBMJCE"); // Initialize the Signature object for Signature verification. dsasig.initVerify(pubkey); // Update the Signature object with the data to be verified. BufferedInputStream buf = new BufferedInputStream(fis); byte[] buff = new byte[1024]; int len; while (buf.available() != 0) {    len = buf.read(buff);    dsasig.update(buff, 0, len); } buf.close(); fis.close(); // Verify the signature. boolean verifies = dsasig.verify(sigToVerify); if (verifies)    System.out.println("Signature verification succeeded."); else    System.out.println("Signature verification failed."); // Other code goes here... 

This code fragment is similar to the one shown in Listing 11.9 on page 406. However, instead of retrieving the signer's public key from a file for signature verification, this code shows how to retrieve the signer's public key from the signer's X.509 certificate, using the getPublicKey() method in class X509Certificate . The public key is then used to initialize the Signature object for signature verification.

The three files inputFile , signatureFile , and certFile , used in the code fragment of Listing 11.11, should be defined somewhere else in the program. Note that this time, all three files must exist in advance and should be the same files used by the code fragment of Listing 11.10 on page 408. This way, we are simulating a scenario in which a sender generates a signature and then sends the original file to a receiver, along with the signature and the certificate.

The expected output should be as follows.

  • If none of the three files has been altered after the signature was applied, the program should display the following:

     Signature verification succeeded. 
  • If you change the contents of any of the three files, the program displays the following message:

     Signature verification failed. 

    In particular, if you modify the signature file so that it no longer respects the signature format, the code fragment of Listing 11.9 on page 406 will throw a java.security.SignatureException .

This example demonstrates how you can successfully integrate the Java 2 APIs with local security structures, such as keystores and certificates, to send documents with proof of data integrity and authenticity.

11.2.10 The AlgorithmParameters and A lgorithmParameterGenerator Classes in Package java.security

The AlgorithmParameters class is an engine class that provides an opaque representation of cryptographic parameters. An opaque representation of cryptographic parameters is one in which you have no direct access to the parameter fields; you can get only the name of the algorithm associated with the parameter set and some kind of encoding for the parameter set itself. By contrast, with a transparent representation of cryptographic parameters , you can access each value individually, through one of the get methods defined in the corresponding specification class. However, you can call the AlgorithmParameters.getParameterSpec() method to convert an AlgorithmParameters object to a transparent specification.

The package java.security also provides the AlgorithmParameterGenerator and AlgorithmParameterGeneratorSpi classes.

  • The AlgorithmParameterGenerator class is used to generate a set of parameters to be used with a certain algorithm. Parameter generators are constructed using the getInstance() factory methods.

  • The AlgorithmParameterGeneratorSpi class defines the SPI for the AlgorithmParameterGenerator class.

11.2.11 The java.security.SignedObject Class

SignedObject is a class for creating runtime Object s whose integrity cannot be compromised without being detected . Specifically , a SignedObject contains another serializable Object and its signature. A SignedObject is a deep copy , in serialized form, of an original Object . Once the copy is made, further manipulation of the original Object has no side effect on the copy and vice versa.

To create a SignedObject , it is first necessary to generate a java.security.Signature object by calling the getInstance() method in the Signature class. A SignedObject can then be constructed by calling the constructor for the SignedObject class, passing it the Object to be signed, a PrivateKey for signing, and the Signature object previously constructed. The constructor of the SignedObject class automatically initializes the Signature object for signing. Therefore, there is no need to call the initSign() method on the Signature object.

Having received a SignedObject , the integrity of the Object that it wraps can be verified by calling the verify() method on the SignedObject itself. This method requires a PublicKey and a Signature object for Signature verification. For this verification process to succeed, the PublicKey passed to the verify() method must be the one that corresponds to the PrivateKey that was used to construct the SignedObject . The verify() method of the SignedObject class automatically initializes the Signature object for signature verification. Therefore, there is no need to call the initVerify() method on the Signature object.

For flexibility reasons, the constructor and verify() method of the SignedObject class allow for customized Signature engines, which can implement signature algorithms that are not installed formally as part of a CSP. However, it is crucial that the programmer writing the verifier code be aware of what Signature engine is being used, as its own implementation of the verify() method is invoked to verify a signature. In fact, a malicious Signature may choose to always return true on verification in an attempt to bypass a security check.

Potential applications of SignedObject include the following.

  • It can be used internally to any Java runtime as an unforgeable authorization token one that can be passed around without fear that the token can be maliciously modified without being detected.

  • It can be used to sign and serialize data for storage outside the Java runtime: for example, storing critical access-control data on disk.

  • Nested SignedObject s can be used to construct a logical sequence of signatures, resembling a chain of authorization and delegation.

11.2.12 The java.security.spec Package

This package contains classes and interfaces for key specifications and algorithm parameter specifications. For example, DSAPrivateKeySpec , which is a specification class for keys using the DSA algorithm, defines getX() , getP() , getQ() , and getG() methods to access the private key x ; and the DSA algorithm parameters, used to calculate the key: the prime p , the subprime q , and the base g . This is contrasted with an opaque representation of key material, as defined by the Key interface discussed in Section 11.2.2 on page 393, in which you have no direct access to the key-material fields.

This package contains key specifications for DSA public and private keys, RSA public and private keys, PKCS#8 private keys in DER-encoded format, and X.509 public and private keys in DER-encoded format (see Listing 11.9 on page 406). The package also provides an algorithm parameter specification class, DSAParameterSpec , which specifies the set of parameters used with the DSA algorithm. An example of DSAParameterSpec use is provided in Listing 11.5 on page 395.

The interfaces provided are AlgorithmParameterSpec and KeySpec .

  • The AlgorithmParameterSpec interface is a specification of cryptographic parameters. It groups all parameter specifications. All parameter specifications, such as the DSAParameterSpec class provided in the same package, must implement it. This interface does not contain any methods or constants. Its only purpose is to group, and provide type safety for, all parameter specifications.

  • The KeySpec interface is a specification of the key material that constitutes a cryptographic key. All key specifications must implement this interface, which does not contain any methods or constants. In fact, the only purpose of the KeySpec interface is to group, and provide type safety for, all key specifications.

11.2.13 The java.security.cert Package

This package provides classes to manage and handle digital certificates and CRLs, and provides separate classes for managing X.509 certificates and X.509 CRLs (see Section 10.3.4 on page 372).

  • The abstract Certificate class can be used to manage various types of identity certificates, whereas the abstract X509Certificate class, which extends Certificate and implements the X509Extension interface, is specifically for X.509 certificates. Sample code using the X509Certificate class is given in Listing 11.6 on page 396, Listing 11.10 on page 408, and Listing 11.11 on page 411.

  • The CRL class is an abstraction of a CRL (see Section 10.3.4 on page 372). CRLs have different formats but important common uses. For example, all CRLs share the functionality of listing revoked certificates and can be queried on whether they list a given certificate. Specialized CRL types can be defined by subclassing this abstract class. An example is the X509CRL class, which extends CRL and implements the X509Extension interface. An X509CRLEntry class is provided for a revoked certificate entry in an X.509 CRL.

  • The java.security.cert package also provides a CertificateFactory class to generate certificates and CRL objects from their encodings, and a CertificateFactorySpi class to define the SPI for the CertificateFactory class. CertificateFactory objects can be instantiated using the getInstance() method. Then, the generateCertificate() and the generateCRL() methods can be used to create a Certificate and a CRL object, respectively. An example of CertificateFactory use is offered by the code fragment in Listing 11.11 on page 411.

11.2.14 The java.security.interfaces Package

This package contains only interfaces, which are used for generating DSA and RSA keys.

  • The DSAKey , DSAPrivateKey , and DSAPublicKey interfaces provide the standard interfaces to DSA keys. The package also provides a DSAKeyPairGenerator interface for generating DSA key pairs and a DSAParams interface for generating a DSA-specific set of key parameters, which define a DSA key family.

  • The RSAPublicKey and RSAPrivateKey interfaces are for RSA keys. The package also contains a class named RSAPrivateCrtKey , which is the interface to an RSA private key, as defined in the PKCS#1 standard (see Section 12.1.1 on page 435), using the Chinese Remainder Theorem (CRT) information values.

 < Day Day Up > 


Enterprise Java Security. Building Secure J2EE Applications
Enterprise Javaв„ў Security: Building Secure J2EEв„ў Applications
ISBN: 0321118898
EAN: 2147483647
Year: 2004
Pages: 164

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net