Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Professional Java.JDK.5.Edition (Wrox)

.pdf
Скачиваний:
39
Добавлен:
29.02.2016
Размер:
12.07 Mб
Скачать

Chapter 13

Calculating and Verifying Message Digests

The MessageDigest engine class takes an arbitrary length byte array as input and calculates a fixedlength hash value, known as a message digest. This is a one-way operation. It is impossible to take a message digest and derive the original input. If this was possible, then the world would have the best compression algorithm in existence, which a guy I know actually tried to implement in high school. This is a vital aspect of a message digest because it keeps the original input out of the picture. Additionally, with the complexity of the message digest algorithms, it is computationally infeasible to find two sets of input that hash to the exact same value. Therefore, you can view a message digest as a fingerprint of data because each input set hashes to an (almost) unique value.

Let’s take a look at using the factory creation method in action. This is the same across all engine classes, so it will be described in detail here but glossed over for the other engine classes. Each engine class has three static methods that conform to the following signatures:

static [engine class name] getInstance(String algorithm) static [engine class name] getInstance(String algorithm, String provider) static [engine class name] getInstance(String algorithm, Provider provider)

The second two forms of the getInstance method allow you to specify a particular provider. The last form allows you to pass in an instance of a provider, and the second form lets you just use the name of a provider. All strings, including algorithm, are case-insensitive. The [engine class name] is replaced with the actual class name of the engine class.

The SUN package comes with two message digest algorithms: MD5 and SHA-1. The MD5 algorithm accepts input and generates a 128-bit message digest for the given input. For those familiar with MD4, the MD5 algorithm is slightly slower than MD4 but has greater assurance of security. One key benefit to the MD5 algorithm is that it can be coded in a fairly straightforward manner, not needing any complicated or large lookup tables. Although secure, it has actually been discovered that it is computationally feasible to find two sets of input that hash to the same value. This violates one of the principles of message digests. Due in part to this fact, SHA-1 is also available. SHA-1, short for Secure Hash Algorithm, was developed by the NSA and first published in 1995. It is based on some of the same principles as MD5, but produces a message digest that is 160 bits long. The maximum input size SHA-1 can take is in the neighborhood of 2 quintillion bytes (2^64 bits).

After invoking the getInstance factory method, an initialized MessageDigest is available. The next step is to provide the MessageDigest object with the input and then ask it to calculate the message digest. There are three methods available to pass input data to the MessageDigest:

void update(byte input) void update(byte[] input)

void update(byte[] input, int offset, int len)

The first form accepts a single byte of input. The second takes an array of bytes, and the length of the array is used as the length of the input. The last form takes an array of bytes, but it allows for the calculation of a message digest based on a subset of the array starting at position offset. The input size is described by len.

586

Java Security

There are three methods that calculate the message digest, which is then returned as an array of bytes:

byte[] digest()

byte[] digest(byte[] input)

int digest(byte[] buf, int offset, int len)

The first digest method calculates the message digest based on the input already passed in via one of the update methods. The second form is a convenience method that returns a message digest based on input passed in to the method. The third form is not a convenience method. It calculates the message digest based on the input set via one of the update methods and then stores the message digest in the buf byte array that is passed in to the method. The len parameter dictates the maximum length available for the message digest, and offset dictates where in the array the message digest should start getting written. The return value is how many bytes were stored in buf.

You can use the MessageDigest engine class to ensure the integrity of data. Say you’re writing the security and data integrity component of a system that is used globally. You want to ensure that data is not altered. One way to accomplish this is to store a collection of message digests that correspond to sensitive data that is communicated across the globe. These message digest values are stored in a base system and then the message digest can be recalculated when each piece of data arrives at its destination. A component can be developed to look up the message digests from the base system (because they are small and shouldn’t be communicated with the data) and compare them to a newly calculated message digest. Here’s an example implementation of a class that instantiates and computes the message digest and then compares it to an already looked up message digest value:

import java.security.MessageDigest;

import java.security.NoSuchAlgorithmException;

public class MessageDigestExample { public static void main(String args[])

{

try {

MessageDigest sha = MessageDigest.getInstance(“SHA-1”); byte[] data1 = {65,66,67,68,69};

byte[] data2 = {70,71,72,73,74};

sha.update(data1);

sha.update(data2);

byte[] msgDigest = sha.digest();

//Can also combine the final update with digest like this:

//byte[] msgDigest = sha.digest(data2);

System.out.println(“--- Message Digest ---”); for(int i=0; i<msgDigest.length; i++) {

System.out.print(msgDigest[i] + “ “);

}

System.out.println(“”);

} catch(NoSuchAlgorithmException nsae) { System.out.println(“Exception: “ + nsae); nsae.printStackTrace();

}

}

}

587

Chapter 13

The SHA-1 algorithm is specified in the call to getInstance, returning an initialized MessageDigest object that computes the message digest according to the SHA-1 algorithm. The update method is invoked twice, simulating a multipart operation. The message digest that is calculated is a series of numbers shown in the following output:

--- Message Digest ---

-97 103 -17 -58 -81 -87 95 26 -17 -101 51 81 -42 -80 29 126 5 -111 -73 72

This array of numbers can be recomputed and compared on the recipient’s side to ensure the data is the same that was originally communicated.

Digital Signing and Verification of Data

Digitally signing data is accomplished using a private key, and the verification of that signature is done with the public key. This ensures that the data originated from the specific person that signed it with their private key, much like signing a credit card receipt. The private key is used to sign a collection of bytes, and a short, fixed-length signature is generated (much like a message digest). This signature can then be verified using the public key. This process is illustrated in Figure 13-1. This is a primarily programmatic view of using the DSA algorithm. In actuality, the DSA algorithm is used with a message digest algorithm such as MD5 or SHA-1 (to which you already have access through the MessageDigest engine class). The actual message digest becomes input to the DSA algorithm along with the private key. On the other side, the data is then encoded into a message digest again and serves as input to DSA along with the public key in order to verify the integrity of the data.

 

 

 

DATA

GENERATION OF A

 

 

 

(array of bytes)

DIGITAL SIGNATURE

 

INPUT

 

 

 

 

 

 

 

 

 

 

 

 

 

 

TO

 

 

 

 

 

 

 

SIGNATURE

 

 

 

 

 

 

 

 

 

 

 

 

 

 

ALGORITHM

 

Signature

 

 

 

PRIVATE

(such as DSA)

 

outputs

 

Digital

 

 

Engine Class

 

 

KEY

 

 

 

 

Signature

 

 

Implementation

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

resulting digital signature

 

 

VERIFICATION OF

 

is input for signature

 

 

 

 

 

DIGITAL SIGNATURE

 

 

verification

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

PUBLIC

 

 

 

Signature

output

 

TRUE if verification of

 

 

 

 

digital signature

 

 

 

Engine Class

 

 

KEY

input

 

 

 

 

succeeds;

 

 

Implementation

 

 

 

 

 

 

 

 

 

FALSE otherwise

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Figure 13-1

588

Java Security

Much like message digests, there are two vital principles for a Digital Signature Algorithm. The first principle is that the public key that corresponds to the private key can be used to verify the integrity of the data. The second is that the digital signature and the public key do not reveal anything about the private key. The actual Signature object can be in one of three states. Consult the following table for the list of states that an object of the Signature class can assume.

Signature State

Description

 

 

UNINITIALIZED

The state assumed immediately after creation.

SIGN

Signifies the object is initialized for signing. Set after a call to initSign.

VERIFY

Signifies the object is initialized for verifying a signature. Set after a call

 

to initVerify.

 

 

The SUN package comes with an implementation of the Digital Signature Algorithm (DSA). DSA is part of the Digital Signature Standard (DSS) that was developed by the NSA in 1991. Either SHA-1 or MD5 can be used with the DSA algorithm. Hopefully, the value of engine classes is making itself apparent. It becomes easy to combine a message digest function with a digital signature function. Just like the MessageDigest engine class, the Signature engine class has the same three getInstance methods. An instance of a Signature class must be initialized after creation using the following method in order to prepare it to digitally sign data:

final void initSign(PrivateKey privateKey)

After this method is called, the Signature class assumes the SIGN state. The next step is to send data to the Signature object and actually sign it. This is accomplished by the update and sign methods:

final void update(byte b) final void update(byte[] data)

final void update(byte[] data, int offset, int len)

The first form accepts a single byte of data. The second takes an array of bytes, and the length of the array is used as the length of the data. The last form takes an array of bytes, but it allows for the calculation of a signature based on a subset of the array starting at position offset. The data size is described by len:

final byte[] sign()

final int sign(byte[] outbuf, int offset, int len)

The first form of the sign method returns the signature in an array of bytes. The second form places the signature in the outbuf array starting at offset and going for a maximum length of len. The value returned is how many bytes were stored in the outbuf array. After a sign method returns, the Signature object is left in the SIGN state and is still configured with the programmed private key. Call initSign again to utilize a different private key.

The other operation that the Signature engine class supports is verifying data. The Signature object must first be set to verify data by invoking an initVerify method:

final void initVerify(PublicKey publicKey)

final void initVerify(Certificate certificate)

589

Chapter 13

Either a public key object or a certificate can be used to verify a digital signature. After initVerify is invoked, the Signature object assumes the VERIFY state. The update methods are used to send data into the Signature object to verify. Their usage does not differ from passing in data for signing. A verify method is then invoked to determine if the signature generated from the data and public key match the private key:

final boolean verify(byte[] signature)

final boolean verify(byte[] signature, int offset, int length)

The digital signature takes the form of a byte array. The second form of verify is used to specify the location (at offset) and the length (specified by length) of the signature in the byte array. If it all matches up, verify returns true. However, if the public key does not match the signature or the signature is invalid, false is returned. After verify returns, the Signature object is left in the VERIFY state still programmed with the public key that was passed in to initVerify. Call initVerify again to use a different public key.

One common use of public and private keys is signing and then verifying the source of communication. For example, assume you work for a government contractor and are tasked with constructing a secure communication system that is essentially secure e-mail. The secure e-mail client must have the capability to digitally sign messages going out and also verify messages that are delivered. The details of generating and managing keys are saved for subsequent discussion. Assume the keys are available. You might develop a utility class listed in the following example to assist with the signing and verifying of secure communication:

import java.security.Signature; import java.security.KeyPair; import java.security.PublicKey; import java.security.PrivateKey;

import java.security.NoSuchAlgorithmException; import java.security.InvalidKeyException; import java.security.SignatureException;

public class SignatureExample {

public byte[] signData(byte[] data, PrivateKey key)

{

try {

Signature signer = Signature.getInstance(“SHA1withDSA”);

signer.initSign(key);

signer.update(data);

return(signer.sign());

}catch(NoSuchAlgorithmException nsae) { System.out.println(“Exception: “ + nsae); nsae.printStackTrace();

}catch(InvalidKeyException ike) { System.out.println(“Exception: “ + ike); ike.printStackTrace();

}catch(SignatureException se) { System.out.println(“Exception: “ + se);

se.printStackTrace();

}

return(null);

590

Java Security

}

public boolean verifySig(byte[] data, PublicKey key, byte[] sig)

{

try {

Signature signer = Signature.getInstance(“SHA1withDSA”);

signer.initVerify(key);

signer.update(data);

return(signer.verify(sig));

}catch(NoSuchAlgorithmException nsae) { System.out.println(“Exception: “ + nsae); nsae.printStackTrace();

}catch(InvalidKeyException ike) { System.out.println(“Exception: “ + ike); ike.printStackTrace();

}catch(SignatureException se) { System.out.println(“Exception: “ + se); se.printStackTrace();

}

return(false);

}

public static void main(String args[])

{

SignatureExample sigEx = new SignatureExample(); KeyPairGeneratorExample kpge = new KeyPairGeneratorExample(); KeyPair keyPair = kpge.generateKeyPair(717);

byte[] data = {65,66,67,68,69,70,71,72,73,74}; byte[] digitalSignature = sigEx.signData(data,

keyPair.getPrivate());

boolean verified;

// This verification will succeed

verified = sigEx.verifySig(data, keyPair.getPublic(), digitalSignature);

if(verified) {

System.out.println(“** The digital signature “ + “has been verified”);

} else {

System.out.println(“** The digital signature is “ + “invalid, the wrong “ +

“key was used, or the data has” + “ been compromised”);

}

System.out.println(“”);

//Generate a new key pair. Guaranteed to be different

//and incompatible with first set.

keyPair = kpge.generateKeyPair(517); // This verification will fail

591

Chapter 13

verified = sigEx.verifySig(data, keyPair.getPublic(), digitalSignature);

if(verified) {

System.out.println(“** The digital signature has” + “ been verified”);

} else {

System.out.println(“** The digital signature is “ + “invalid, the wrong “ +

“key was used, or the data “ + “has been compromised”);

}

}

}

The KeyPairGeneratorExample, explained subsequently, is used to obtain a public and private key. The data is signed with the private key and verified with the public key.

Digital Key Creation and Management

There are two representations of keys made available by the security API. Transparent representations of keys allow you to retrieve specific information about the key, such as the algorithm parameter values used to calculate the key. Opaque representations of keys keep these values hidden and only allow you access to the algorithm used to create the key, the encoding used, and the encoded form of the key itself. Transparent representations of keys inherit from a tagging interface called KeySpec. Since this is a tagging interface, no methods are defined inside the interface. Key interfaces provided in the java.security.spec package are listed in the following table.

Key Interface

Description

 

 

DSAPrivateKeySpec

A DSA private key specification

DSAPublicKeySpec

A DSA public key specification

RSAPrivateKeySpec

An RSA private key specification

RSAPrivateCrtKeySpec

An RSA private key specification using Chinese

 

remainder theorem

RSAMultiplePrimePrivateCrtKeySpec

An RSA multiple prime private key specification

 

using the Chinese remainder theorem

RSAPublicKeySpec

An RSA public key specification

EncodedKeySpec

An encoded key specification PKCS8Encoded-

 

KeySpec and X509EncodedKeySpec are two pro-

 

vided implementers.

 

 

As opposed to transparent representations of keys, opaque representations inherit from the Key interface. Unlike the KeySpec interface, the Key interface defines three methods that all concrete implementations must implement. These three methods are described next:

String algorithm()

592

Java Security

The algorithm method returns a string representation of the algorithm used to create the key:

byte[] getEncoded()

The getEncoded method returns the encoded version of the key (which can then be packaged and transmitted) according to a standard encoding format such as X.509 or PKCS #8.

String getFormat()

The getFormat method returns the name of the particular encoding format used to encode the key. The java.security.interfaces package contains 12 interfaces that inherit directly from the Key interface. These are the various types of keys that are standard in the Java API. These are listed in the following table.

Key Interface

Description

 

 

DHPrivateKey

A Diffie-Hellman private key

DHPublicKey

A Diffie-Hellman public key

DSAPrivateKey

A DSA private key

DSAPublicKey

A DSA public key

PBEKey

A PBE (password-based encryption) key, supporting a

 

SALT value

RSAMultiPrimePrivateCrtKey

An RSA multiprime private key using the Chinese remain-

 

der theorem. Consult PKCS#1 for more information.

RSAPrivateCrtKey

An RSA private key using the Chinese remainder theorem.

 

Consult PKCS#1 for more information.

RSAPrivateKey

An RSA private key

RSAPublicKey

An RSA public key

PublicKey

Used as a tagging interface for all public key

 

interfaces/classes

PrivateKey

Used as a tagging interface for all private key

 

interfaces/classes

SecretKey

Used as a tagging interface for all secret key

 

interfaces/classes

 

 

The KeyFactory engine class is used to convert transparent representations of keys to opaque representations and vice versa. The standard getInstance methods are available to create a KeyFactory. There are two methods to convert a transparent representation to an opaque representation: one for public keys and one for private keys. There is one method defined for the reverse operation. These methods are described subsequently:

PublicKey generatePublic(KeySpec keySpec)

PrivateKey generatePrivate(KeySpec keySpec)

593

Chapter 13

The generatePublic and generatePrivate methods take a transparent representation of a key (a class that inherits from KeySpec — directly or indirectly) and return either the opaque representation of the public key or the opaque representation of the private key:

KeySpec getKeySpec(Key key, Class keySpec)

The getKeySpec method accepts the opaque representation of the key through the key parameter and a class that specifies which key specification class to convert the key to and return.

From more of a client perspective, the KeyPair class and KeyPairGenerator and KeyStore engine classes are used to create, store, and manage public/private keys and certificates. The KeyPair class defines the following two methods:

PrivateKey getPrivate()

PublicKey getPublic()

The first method returns the private key currently stored, and the second returns the public key. The KeyPairGenerator engine class is used to generate these pairs of private and public keys and uses the KeyPair class to store them.

The KeyPairGenerator engine class generates a pair of keys in either an algorithm-independent manner or an algorithm-specific manner. Which of these is used depends on how the KeyPairGenerator is initialized. The following two methods are for algorithm-independent initialization. Because all algorithms use the basic concepts of size and randomness, this initialization is available when initialization based on a specific algorithm isn’t necessary:

void initialize(int keysize, SecureRandom random)

void initialize(int keysize)

The meaning of the keysize parameter varies for each algorithm. Other algorithm parameters are given preconfigured parameters. For example, a DSA algorithm might assign its parameters different values based on the specified keysize. If a random number generator is not passed in, randomness is generated via a default system generator:

void initialize(AlgorithmParameterSpec params, SecureRandom random)

void initialize(AlgorithmParameterSpec params)

These forms of initialize perform the initialization based on specific parameters that are passed through the params parameter. If a random number generator is not passed in, randomness is generated from the system:

KeyPair generateKeyPair()

This method creates and returns a KeyPair object. Each call to this method returns a separate and distinct pair of keys.

Here’s an example implementation of a method that utilizes the KeyGenerator class to generate a private key and public key and store them in a KeyPair object (this is used in other examples in this chapter):

import java.security.KeyPairGenerator; import java.security.KeyPair;

import java.security.SecureRandom;

594

Java Security

import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PublicKey;

import java.security.PrivateKey;

public class KeyPairGeneratorExample { public KeyPair generateKeyPair(long seed)

{

try {

//Get a DSA key generator from first

//provider that provides it

KeyPairGenerator keyGenerator =

KeyPairGenerator.getInstance(“DSA”);

//Get a random number generator using

//algorithm SHA1PRNG from the SUN provider package. SecureRandom rng =

SecureRandom.getInstance(“SHA1PRNG”, “SUN”);

//Configure RNG and initialize key pair generator rng.setSeed(seed);

keyGenerator.initialize(1024, rng);

return(keyGenerator.generateKeyPair());

}catch(NoSuchProviderException nspe) { System.out.println(“Exception: “ + nspe); nspe.printStackTrace();

}catch(NoSuchAlgorithmException nsae) {

System.out.println(“Exception: “ + nsae); nsae.printStackTrace();

}

return(null);

}

public static void main(String args[])

{

KeyPairGeneratorExample kpge = new KeyPairGeneratorExample();

KeyPair kp = kpge.generateKeyPair(717);

System.out.println(“-- Public Key ----”);

PublicKey pubKey = kp.getPublic();

System.out.println(“

Algorithm=” + pubKey.getAlgorithm());

System.out.println(“

Encoded=” + pubKey.getEncoded());

System.out.println(“

Format=” + pubKey.getFormat());

System.out.println(“\n-- Private Key ----”);

PrivateKey priKey = kp.getPrivate();

System.out.println(“

Algorithm=” + priKey.getAlgorithm());

System.out.println(“

Encoded=” + priKey.getEncoded());

System.out.println(“

Format=” + priKey.getFormat());

}

}

595

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]