The java.security package defines quite a few classes related to the Java access-control architecture, which is discussed in more detail in Chapter 5, "Java Security". These classes allow Java programs to run untrusted code in a restricted environment from which it can do no harm. While these are important classes, you rarely need to use them.
The more interesting classes are the ones used for authentication. A message digest is a value, also known as cryptographic checksum or secure hash, that is computed over a sequence of bytes. The length of the digest is typically much smaller than the length of the data for which it is computed, but any change, no matter how small, in the input bytes, produces a change in the digest. When transmitting data (a message), you can transmit a message digest along with it. Then, the recipient of the message can recompute the message digest on the received data and, by comparing the computed digest to the received digest, determine whether the message or the digest was corrupted or tampered with during transmission. We saw a way to compute a message digest earlier in the chapter when we discussed streams. A similar technique can be used to compute a message digest for non-streaming binary data:
import java.security.*; // Obtain an object to compute message digests using the "Secure Hash // Algorithm"; this method can throw NoSuchAlgorithmException. MessageDigest md = MessageDigest.getInstance("SHA"); byte[] data, data1, data2, secret; // Some byte arrays initialized elsewhere // Create a digest for a single array of bytes byte[] digest = md.digest(data); // Create a digest for several chunks of data md.reset(); // Optional: automatically called by digest() md.update(data1); // Process the first chunk of data md.update(data2); // Process the second chunk of data digest = md.digest(); // Compute the digest // Create a keyed digest that can be verified if you know the secret bytes md.update(data); // The data to be transmitted with the digest digest = md.digest(secret); // Add the secret bytes and compute the digest // Verify a digest like this byte[] receivedData, receivedDigest; // The data and the digest we received byte[] verifyDigest = md.digest(receivedData); // Digest the received data // Compare computed digest to the received digest boolean verified = java.util.Arrays.equals(receivedDigest, verifyDigest);
A digital signature combines a message-digest algorithm with public-key cryptography. The sender of a message, Alice, can compute a digest for a message and then encrypt that digest with her private key. She then sends the message and the encrypted digest to a recipient, Bob. Bob knows Alice's public key (it is public, after all), so he can use it to decrypt the digest and verify that the message has not been tampered with. In performing this verification, Bob also learns that the digest was encrypted with Alice's private key, since he was able to decrypt the digest successfully using Alice's public key. As Alice is the only one who knows her private key, the message must have come from Alice. A digital signature is called such because, like a pen-and-paper signature, it serves to authenticate the origin of a document or message. Unlike a pen-and-paper signature, however, a digital signature is very difficult, if not impossible, to forge, and it cannot simply be cut and pasted onto another document.
Java makes creating digital signatures easy. In order to create a digital signature, however, you need a java.security.PrivateKey object. Assuming that a keystore exists on your system (see the keytool documentation in Chapter 8, "Java Development Tools"), you can get one with code like the following:
// Here is some basic data we need File homedir = new File(System.getProperty("user.home")); File keyfile = new File(homedir, ".keystore"); // Or read from config file String filepass = "KeyStore password" // Password for entire file String signer = "david"; // Read from config file String password = "No one can guess this!"; // Better to prompt for this PrivateKey key; // This is the key we want to look up from the keystore try { // Obtain a KeyStore object and then load data into it KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType()); keystore.load(new BufferedInputStream(new FileInputStream(keyfile)), filepass.toCharArray()); // Now ask for the desired key key = (PrivateKey) keystore.getKey(signer, password.toCharArray()); } catch (Exception e) { /* Handle various exception types here */ }
Once you have a PrivateKey object, you create a digital signature with a java.security.Signature object:
PrivateKey key; // Initialized as shown previously byte[] data; // The data to be signed Signature s = // Obtain object to create and verify signatures Signature.getInstance("SHA1withDSA"); // Can throw NoSuchAlgorithmException s.initSign(key); // Initialize it; can throw InvalidKeyException s.update(data); // Data to sign; can throw SignatureException /* s.update(data2); */ // Call multiple times to specify all data byte[] signature = s.sign(); // Compute signature
A Signature object can verify a digital signature:
byte[] data; // The signed data; initialized elsewhere byte[] signature; // The signature to be verified; initialized elsewhere String signername; // Who created the signature; initialized elsewhere KeyStore keystore; // Where certificates stored; initialize as shown earlier // Look for a public key certificate for the signer java.security.cert.Certificate cert = keystore.getCertificate(signername); PublicKey publickey = cert.getPublicKey(); // Get the public key from it Signature s = Signature.getInstance("SHA1withDSA"); // Or some other algorithm s.initVerify(publickey); // Setup for verification s.update(data); // Specify signed data boolean verified = s.verify(signature); // Verify signature data
The java.security.SignedObject class is a convenient utility for wrapping a digital signature around an object. The SignedObject can then be serialized and transmitted to a recipient, who can deserialize it and use the verify() method to verify the signature:
Serializable o; // The object to be signed; must be Serializable PrivateKey k; // The key to sign with; initialized elsewhere Signature s = Signature.getInstance("SHA1withDSA"); // Signature "engine" SignedObject so = new SignedObject(o, k, s); // Create the SignedObject // The SignedObject encapsulates the object o; it can now be serialized // and transmitted to a recipient. // Here's how the recipient verifies the SignedObject SignedObject so; // The deserialized SignedObject Object o; // The original object to extract from it PublicKey pk; // The key to verify with Signature s = Signature.getInstance("SHA1withDSA"); // Verification "engine" if (so.verify(pk,s)) // If the signature is valid, o = so.getObject(); // retrieve the encapsulated object.
Copyright © 2001 O'Reilly & Associates. All rights reserved.