< 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 ClassThis 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 InterfaceThe 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:
11.2.3 The PublicKey and PrivateKey Interfaces in Package java.securityThe 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 ClassThe 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 ClassThe 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 ClassThe 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
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 InitializationKeyPairGenerator 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 InitializationKeyPairGenerator 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 ClassA 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 Classimport 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 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 ClassAn 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.
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
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 Classimport 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 ClassThe 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.
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.
Once a Signature object has been initialized, you can either sign data or verify the signature of previously signed data.
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
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 Generationimport 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:
Signature verification consists of similar phases.
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:
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 Verificationimport 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.
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 Generationimport 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:
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 Verificationimport 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.
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.securityThe 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.
11.2.11 The java.security.SignedObject ClassSignedObject 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.
11.2.12 The java.security.spec PackageThis 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 .
11.2.13 The java.security.cert PackageThis 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).
11.2.14 The java.security.interfaces PackageThis package contains only interfaces, which are used for generating DSA and RSA keys.
|
< Day Day Up > |