Chapter 6. Key Management and Digital Certificates

In Chapter 5,"Introduction to Cryptography," you learned the basics of cryptography. You saw how cryptographic algorithms use keys to convert plaintext into ciphertext. You also learned about secret- and public-key cryptography.

In this chapter, you'll cover key management, which is the heart of any cryptographic system. You'll learn how keys are securely generated, stored, and distributed. You'll also learn how the key management of the Java Security API changed from JDK 1.1 to JDK 1.2. When you finish this chapter, you'll have a solid understanding of the key management classes and interfaces of the Java Security API.

Importance of Key Management

Key management is one of the most challenging aspects of setting up a cryptographic security system. In order for a cryptosystem to work and be secure, each of its users must have a set of secret keys (in a secret-key system) and or a public-private key pair (in a public-key system). This involves generating the keys and securely distributing them to the users or providing the users with a way of generating the keys. It also involves providing the users with the capability to securely store and manage secret and private keys. In public-key systems, key management includes the capability to verify and manage the public keys of other users who are signed in the form of digital certificates. Figure 6.1 provides an overview of these key management activities.

Key management is an essential part of any cryptographic security system.

Figure 6.1. Key management is an essential part of any cryptographic security system.

Although the key management activities shown in Figure 6.1 seem to be fairly simple and straightforward, a number of security issues are involved with these activities:

  • If secret or private keys are not securely generated, it might be possible for an adversary to guess a key by attacking the key generation algorithm. For example, if a key generation algorithm is used with the same seed to generate two or more keys, it might be possible to use the value of one key to guess the values of the other keys.

  • There is a significant tradeoff when determining whether to allow users to generate their own keys. If a user is able to securely generate his own key, he does not need to trust the organizations and individuals that generated and distributed the key. If the organization to which the user belongs is not able to decrypt the user's data or communications, the organization must trust that the user is using his key(s) in an appropriate manner. If a user is not able to securely generate his own key, the security of the user and the organization as a whole is jeopardized.

  • If a user is not able to securely store his keys, the user's keys might be compromised. Another party might be able to decrypt the user's data and communications or falsely sign objects as originating from the user. This is especially significant if the user's keys are insecurely stored on a computer that is accessible via the Internet.

  • If the key management system employed by the user (and organization) causes the user to easily lose track of his keys and does not support key recovery, important information that is encrypted might be rendered inaccessible. In many cases, this is equivalent to having the data destroyed as the result of flaws in the key management system.

  • If the key management system does not allow a user to verify the public keys of other users, it might be possible for one user to masquerade as another. For example, user A might substitute his public key for that of user B in order to read secret information that is being sent to user B. Digital certificates provide a countermeasure to this vulnerability.

The above issues, as a minimum, must be considered when selecting and implementing a key management system.

Key Representation

The Key interface of the java.security package is used to represent a key. It extends the java.io.Serializable interface to enable keys to be serialized and stored in files or transmitted via streams. The Key interface defines the following three methods:

  • getAlgorithm()—Returns the name of the algorithm with which the key is used. Every Key object is associated with a unique algorithm, such as DES, DSA, and RSA.

  • getEncoded()—Returns the key as a byte array in the key's primary encoding format, such as RAW or PKCS#8. If the key does not support encoding, the null value is returned.

  • getFormat()—Returns the format in which the key is encoded. For example, RAW or PKCS#8.

You'll see an example of how these methods are used in the KeyGeneratorApp program of the next section.

Note

PKCS

PKCS stands for Public Key Cryptography Standard. These standards are developed by RSA Data Security, Inc. and are available from http://www.rsasecurity.com/rsalabs/pkcs or http://www.rsa.com.

The Key interface is extended by the PrivateKey and PublicKey interfaces of java.security and the SecretKey interface of the javax.crypto package. PrivateKey and PublicKey are used to identify the private and public keys of a public-key algorithm. SecretKey is used to reference a symmetric (secret) key, such as a DES key. None of the three interfaces defines any methods or constants. Their purpose is simply to type keys as private, public, or secret.

The PrivateKey interface is extended by the DSAPrivateKey and RSAPrivateKey interfaces of java.security.interfaces and the DHPrivateKey interface of javax.crypto.interfaces. They are used to represent private keys that are used in the DSA and RSA algorithms and the Diffie-Hellman key agreement algorithm. You'll learn more about the Diffie-Hellman algorithm later in this chapter in the section, "Key Agreement."

The DSAPrivateKey, RSAPrivateKey, and DHPrivateKey interfaces provide methods for accessing the private key and other values. For example, RSAPrivateKey provides the getModulus() method, even though the modulus is not private. RSAPrivateKey is further extended by RSAPrivateCrtKey, which provides access to the Chinese Remainder Theorem (CRT) coefficient, the values p and q, and other values used to set up an RSA system. The Chinese Remainder Theorem and its application to RSA is described in PKCS#1.

The PublicKey interface is extended by the DSAPublicKey and RSAPublicKey interfaces of java.security.interfaces and the DHPublicKey interface of javax.crypto.interfaces. These interfaces provide methods for accessing public-key values and are analogous to DSAPrivateKey, RSAPrivateKey, and DHPrivateKey. Note that there is no public-key analog to RSAPrivateCrtKey.

Both DSAPrivateKey and DSAPublicKey extend the DSAKey interface of java.security.interfaces. DSAKey defines the getParams() method, which returns a DSAParams object. DSAParams provides methods for accessing the g, p, and q values of a DSA key pair. Refer to Chapter 5 for more information about these values.

In a similar fashion, DHPrivateKey and DHPublicKey extend the DHKey interface of javax.crypto.interfaces. DHKey defines the getParams() method, which returns a DHParameterSpec object. DHParameterSpec is part of the javax.crypto.spec package and provides methods for accessing the l, p, and g values of a Diffie-Hellman key pair. These values are discussed later in this chapter in the section, "Key Agreement."

Figure 6.2 summarizes the relationships between the key interfaces described in this section. In addition to these interfaces, the KeyPair class of java.security is used to encapsulate a public-private key pair. It implements java.io.Serializable and provides the getPublic() and methodgetPrivate() methods for accessing the individual keys of the key pair.

Key interfaces of the Java Security API.

Figure 6.2. Key interfaces of the Java Security API.

Key Generation

The first step in secure key management is to generate keys securely. If the output of a key generation algorithm can be easily predicted, it is possible for an adversary to guess the keys that you are using. The Security API provides the capability to securely generate both public-private key pairs and secret keys. Keys are generated using key generator classes. The KeyPairGenerator class of java.security is used to generate KeyPair objects that can be used in public-key algorithms. The KeyGenerator class of java.security is used to generate secret keys for use in algorithms, such as DES or DESede.

The KeyPairGenerator Class

The KeyPairGenerator class is an abstract class. Instances of KeyPairGenerator are created using the static getInstance() method. This method takes a String object that identifies the algorithm name as a parameter. A second version of this method also allows the provider to be identified. A provider is a collection of packages that supply a set of cryptographic algorithms. The Security API provides a default provider, referred to as Sun. The JCE and the Cryptix packages are also providers and are referred to as SunJCE and Cryptix.

For example, if you have the Cryptix provider installed, you can create a KeyPairGenerator object for RSA using:

KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA","Cryptix");

The getInstance() method throws a NoSuchAlgorithmException exception if the algorithm cannot be found for the specified provider.

Note

Factories

In your study of Java, you'll often come across classes whose instances are not created by a constructor. Instead, the class typically provides static methods for creating object instances. These types of classes are referred to as factory classes or factories. The methods, such as getInstance(), that are used to create instances of the class are referred to as factory methods.

After you create a KeyPairGenerator object using getInstance(), you need to initialize it using the initialize() method. The initialize() method has a void return type and takes on the following forms:

  • initialize(int keysize)—This is the simplest form. You only need to specify the size of the key (in bits) that will be generated by the KeyPairGenerator. Make sure that you pick a size that is appropriate for the type of algorithm you are using. For example, DSA keys must be between 512 and 1024 bits and a multiple of 64. A SecureRandom object is automatically generated for use in initializing the KeyPairGenerator object.

    The SecureRandom object is used to randomize the KeyPairGenerator so that it does not generate in a predictable fashion. The SecureRandom class is covered in the section, "Secure Random Numbers and Key Generation," later in this chapter.

  • initialize(int keysize, SecureRandom random)—In this form, you supply your own SecureRandom object. If you use this form, you do not have to depend on Sun's SecureRandom object to generate your keys.

  • initialize(AlgorithmParameterSpec params)—This form supplies an AlgorithmParameterSpec object that enables the KeyPairGenerator to be initialized in an algorithm-specific fashion. The AlgorithmParameterSpec interface does not define any methods or constants. It simply identifies an object that is used as a parameter to a cryptographic algorithm. To use this form of initialize(), you need to know more detail about the way in which the algorithm is implemented by the cryptographic provider. This form of initialize() automatically generates a SecureRandom object for randomizing the KeyPairGenerator object.

  • initialize(AlgorithmParameterSpec params, SecureRandom random)—This form is similar to the last, but it enables the user to supply his own SecureRandom object. It provides you with the most control over how a KeyPairGenerator is initialized.

After you've initialized a KeyPairGenerator object, you can create KeyPair objects using the genKeyPair() method. For example, the following code shows how a pair of 512-bit key objects are created for use with the DSA algorithm:

KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(512);
KeyPair keys = kpg.genKeyPair();

A complete example of key pair generation is provided by the KeyGeneratorApp program of Listing 6.1.

Note

Time Requirements for Key Generation

Key generation is a computationally intensive process. Depending on the size of the key that you are generating and your computer's performance, key generation can take up to a minute (or longer).

Note

Service Provider Interface Classes

The KeyPairGenerator class is a subclass of KeyPairGeneratorSpi. The "Spi" is used to indicate that it is a service provider interface class. The service provider interface class defines abstract methods that must be implemented by a service provider in order to provide an implementation of a key pair generator.

The KeyGenerator Class

The KeyGenerator class of java.security is similar to the KeyPairGenerator class of java.security in the way that it is used to generate keys. However, KeyGenerator generates a single SecretKey object instead of a KeyPair object. SecretKey objects are used in symmetric encryption algorithms, such as DES, DESede, and IDEA.

A KeyGenerator object is created using a getInstance() method in the same way as KeyPairGenerator. For example, the following creates a DES KeyGenerator object:

KeyGenerator kg = KeyGenerator.getInstance("DES);

KeyGenerator objects are initialized via their init() method, which corresponds to the initialize() method of KeyPairGenerator. The init() method has the following five forms:

  • init(int keysize)—Initializes the KeyGenerator object for a key of a specified bit-size (for example, 56 bits for DES) using the default SecureRandom object.

  • init(int keysize, SecureRandom random)—Initializes the KeyGenerator object for a key of a specified bit-size using a user-supplied SecureRandom object.

  • init(SecureRandom random)—Initializes the KeyGenerator object using a user- supplied SecureRandom object. A bit size that is appropriate for the algorithm is used.

  • init(AlgorithmParameterSpec params)—Initializes the KeyGenerator using implementation-specific algorithm parameters.

  • init(AlgorithmParameterSpec params, SecureRandom random)—Initializes the KeyGenerator using implementation-specific algorithm parameters and a user-supplied SecureRandom object.

After you've initialized the KeyGenerator, you use its generateKey() method to generate a SecretKey object. For example, the following generates a 168-bit SecretKey object for DESede:

KeyGenerator kg = KeyGenerator.getInstance("DESede");
kg.init(168);
SecretKey skey = kg.generateKey();

Note that DESede also can be used with an 112-bit key.

The KeyGeneratorApp Program

The KeyGeneratorApp program shown in Listing 6.1 illustrates the use of the KeyPairGenerator and KeyGenerator classes. It takes an algorithm name and a key size as arguments and generates a key or key pair that is appropriate for the algorithm and key size. For example, the following generates a 56-bit DES key.

java com.jaworski.security.handbook.KeyGeneratorApp DES 56
Algorithm: DES
Key size: 56
Secret key in RAW format:
C0nZkh9iJrY=

The key is displayed in Base64 notation. Refer to Chapter 5 if you are unfamiliar with Base64. You'll need to use the com.jaworski.security.handbook package that was introduced in Chapter 5 in order to compile KeyGeneratorApp.

You can also use KeyGeneratorApp to generate public key pairs. The following generates a pair of 512-bit DSA keys:

java com.jaworski.security.handbook.KeyGeneratorApp DSA 512
Algorithm: DSA
Key size: 512
Private key in PKCS#8 format:
MIHGAgEAMIGoBgcqhkjOOAQBMIGcAkEA/KaCzo4Syrom78z3EQ5SbbB4sF7ey80e
tKII864WF64B81uRpH5t9jQTxeEu0ImbzRMqzVDZkVG9xD7nN1kuFwIVAJYu3cw2
nLqOuyYO5rahJtk0bjjFAkBnhHGyepz0TukaScUUfbGpqvJE8FpDTWSGkx0tFCcb
njUDC3H9c9oXkGmzLik1Yw4cIGI1TQ2iCmxBblC+eUykBBYCFD69FUYsLXc8WkW8
mUSfx1aYuUI9

Public key in X.509 format:
MIHwMIGoBgcqhkjOOAQBMIGcAkEA/KaCzo4Syrom78z3EQ5SbbB4sF7ey80etKII
864WF64B81uRpH5t9jQTxeEu0ImbzRMqzVDZkVG9xD7nN1kuFwIVAJYu3cw2nLqO
uyYO5rahJtk0bjjFAkBnhHGyepz0TukaScUUfbGpqvJE8FpDTWSGkx0tFCcbnjUD
C3H9c9oXkGmzLik1Yw4cIGI1TQ2iCmxBblC+eUykA0MAAkAx/giYWj84hB87YaIe
O44cZfUVY8gFJZhpF1PV/enrRu8xThK2YemTQOC7htnJaPnS3lGQ3QdFz7OJiOPo
afnE

Example 6.1. The KeyGeneratorApp Program

package com.jaworski. security.handbook;

import java.security.*;
import javax.crypto.*;
public class KeyGeneratorApp {
 // The algorithm's name
 private String algorithm = "";
 // The size of the key (in bits) to generate
 private int keySize = 0;
 // Placeholder for secret keys
 private SecretKey skey = null;
 // Placeholder for public key pairs
 private KeyPair keys = null;

 public static void main(String[] args) {
  if(args.length != 2) {
   String err = "Usage: com.jaworski.security.handbook";
   err += ".KeyGeneratorApp algorithmName keySize";
   System.out.println(err);
   System.exit(0);
  }
  // Convert the key size to an int value
  int size = (new Integer(args[1])).intValue();
  // Create a KeyGeneratorApp object
  KeyGeneratorApp app = new KeyGeneratorApp(args[0],size);
  // Generate the key or key pair
  app.generate();
  // Display the key or key pair
  app.display();
 }
 public KeyGeneratorApp(String algorithm, int keySize) {
  this.algorithm = algorithm;
  this.keySize = keySize;
 }
 private void generate() {
  try {
   // Try to generate a public key pair
   KeyPairGenerator kpg = KeyPairGenerator.getInstance(algorithm);
   kpg.initialize(keySize);
   keys = kpg.genKeyPair();
  }catch(NoSuchAlgorithmException ex1) {
   // Try to generate a secret key
   try {
    KeyGenerator kg = KeyGenerator. getInstance(algorithm);
    kg.init(keySize);
    skey = kg.generateKey();
   }catch(NoSuchAlgorithmException ex2) {
    System.out.println("Algorithm not supported: "+algorithm);
    System.exit(0);
   }
  }
 }
 private void display() {
  if(skey != null) {
   // Display the secret key
   System.out.println("Algorithm: "+algorithm);
   System.out.println("Key size: "+keySize);
   System.out.println("Secret key in "+skey.getFormat()+" format:");
   // Use Base64 encoding
   String skeyString =
    Conversion.byteArrayToBase64String(skey.getEncoded());
   System.out.println(skeyString);
  }else if(keys != null) {
   // Display the public key pair
   System.out.println("Algorithm: "+algorithm);
   System.out.println("Key size: "+keySize);
   PrivateKey priKey = keys.getPrivate();
   PublicKey pubKey = keys.getPublic();
   System.out.println("Private key in "+priKey.getFormat()+" format:");
   // Use Base64 encoding
   String priKeyString =
    Conversion.byteArrayToBase64String(priKey.getEncoded());
   System.out.println(priKeyString);
   System.out.println("Public key in "+pubKey.getFormat()+" format:"); 
   String pubKeyString =
    Conversion.byteArrayToBase64String(pubKey.getEncoded());
   System.out.println (pubKeyString);
  }
 }
}

Secure Random Numbers and Key Generation

Random numbers are important in cryptography. They are typically used to make the results of operations, such as key generation, difficult to predict. Unfortunately, computers do not generally produce truly random numbers. Instead, random-number–generation algorithms are used produce streams of pseudorandom numbers. Some pseudorandom number generators (PRNG) are more random (and therefore, less predictable) than others. A higher degree of randomness is achieved by seeding the PRNG with a randomly-selected value and using a cryptographically strong algorithm. A cryptographically strong algorithm is one that has the property that it is computationally difficult to determine any values produced by the algorithm without knowledge of the seed.

Note

Seeds

A seed is a value that is used to initialize a pseudorandom number generation algorithm.

Note

java.util.Random is Not Random

The java.util.Random class is a PRNG, but it is not secure. Random produces a predictable sequence of numbers that can be guessed without the seed value. In addition, the default seed is generated from the current time, which is also predictable.

The SecureRandom class of java.security can be used to access the secure PRNG algorithms of different package providers. It provides the following constructors:

  • SecureRandom()—Creates a SecureRandom object that is initialized with a seed value that is randomly generated by a thread-timing algorithm. This seed might not be secure —that is, it might be predictable.

  • SecureRandom(byte[] seed)—Creates a SecureRandom object that is seeded using the supplied byte array.

In general, if you want a higher degree of security, you should seed the SecureRandom object yourself. If the security of your random number generation isn't of paramount importance, you can trust Sun's seed generation algorithm. You can also reseed a SecureRandom object using the setSeed(long seed) or setSeed(byte[] seed) methods.

The SecureRandom class also provides two static (factory) methods for creating SecureRandom objects:

  • getInstance(String algorithm)—Returns a SecureRandom object that implements the specified algorithm.

  • getInstance(String algorithm, String provider)—Returns a SecureRandom object from the identified provider that implements the specified algorithm.

The above methods do not seed the SecureRandom objects that they create. However, if a SecureRandom object is used before it is seeded, the object seeds itself using an internal seed-generation algorithm.

Sun provides a default SecureRandom algorithm that is named SHA1PRNG. This algorithm follows Appendix G.7 of IEEE standard 1363. It generates a random number sequence that is 64-bits of the SHA-1 hash value of the seed value and a 64-bit counter. The counter is incremented by one to produce subsequent values in a random stream. It is implemented by the sun.security.provider.SecureRandom class. You can create an instance of this algorithm using

SecureRandom srnd = SecureRandom.getInstance("SHA1PRNG");

After you've created and seeded a SecureRandom object, you can use it to generate random values using the next() and nextBytes() methods:

  • next(int numBits)—Overrides the next() method of java.util.Random, allowing the inherited methods of Random, such as nextInt(), to be used securely. The value of numBits is between 0 and 32 inclusive and identifies the number of bits in the result that are to be randomly generated.

  • nextBytes(byte[] randomBytes)—Generates random bytes and puts them in the randomBytes array.

The SecureRandomApp program of Listing 6.2 shows how the SecureRandom class is used. It takes the number of random bytes to generate, followed by an optional seed value, as arguments. It displays the random bytes using Base64 notation. For example, the following generates 100 random bytes using the seed value of 8975247094543084:

java com.jaworski.security.handbook.SecureRandomApp 100 8975247094543084
1jR9VZdQQN5OaeB939/seLCtC6MhUEvsg7eXLh+5Ye+uPzh5/k08IM0Vaj+Mz/PB
1/VTwBem3GRs2lR4a8cyEkbCs9STwa7ppbHdQBnlXBQLf67AiMgQnzpwS2lkhrHA
dXNAyw==

Try using this program with and without seed values. You'll notice that when the program is forced to seed itself, it runs much more slowly. That's because the seed generation algorithm is very computationally intensive.

Example 6.2. The SecureRandomApp Program

package com.jaworski.security.handbook;
import java.security.*;
import javax.crypto.*;

public class SecureRandomApp {
 public static void main(String[] args)
   throws NoSuchAlgorithmException {
  if(args.length == 0) {
   String err = "Usage: com.jaworski.security.handbook";
   err += ".SecureRandomApp numBytes [seed]";
   System.out.println(err);
   System.exit(0);
  }
  // Converts args[0] to an int value
  int numBytes = (new Integer(args[0])).intValue();
  // Converts args[1] to a long value
  long seed = 0l;
  if(args.length > 1) seed = (new Long(args[1])).longValue();
  // Create a SecureRandom object
  SecureRandom srand = SecureRandom.getInstance("SHA1PRNG");
  // Seed it
  if(seed != 0l) srand.setSeed(seed);
  // Generate numBytes random bytes
  byte[] bytes = new byte[numBytes];
  srand.nextBytes(bytes);
  // Display the bytes
  String s = Conversion.byteArrayToBase64String(bytes);
  System.out.println(s);
 }
}

Key Translation

The Key interface and its subinterfaces provide a way to encapsulate keys while they are stored in a Java program. Because Key implements the java.io.Serializable interface, Key objects can be written to streams. This allows keys to be stored in files or sent out over network connections. However, storing or transmitting keys as Key objects poses some problems if they are to be used in applications that are not written in Java. For example, you can create a DES or DSA key that is used in a Java application that might need to be made available to an encryption algorithm that is written in another language or that is implemented in hardware or firmware.

Key translators provide the capability to translate keys from Java objects to a Java-independent array of bytes. There are three primary key translator classes: java.security.KeyFactory, javax.crypto.SecretKeyFactory, and javax.crypto.spec.SecretKeySpec. These classes may be used to translate Key objects into java.security.spec.KeySpec objects. The KeySpec interface does not define any classes or methods. It is used to identify a class as providing an external representation of a Key object. The KeySpec interface is implemented by the following classes of java.security.spec and javax.crypto.spec:

  • java.security.spec.DSAPrivateKeySpec —Provides access to the values (g, p, q, and x) of a DSA private key.

  • java.security.spec.DSAPublicKeySpec—Provides access to the values (g, p, q, and y) of a DSA public key.

  • java.security.spec.EncodedKeySpec—Provides access to the values (as a byte array) of an encoded public or private key.

  • java.security.spec.RSAPublicKeySpec—Provides access to the values (modulus and exponent) of an RSA public key.

  • java.security.spec.RSAPrivateKeySpec—Provides access to the values (modulus and exponent) of an RSA private key.

  • java.security.spec.RSAPrivateCrtKeySpec—Provides access to the values (p, q, private and public exponents, and Chinese Remainder Theorem coefficients) of an RSA private key.

  • javax.crypto.spec.DESedeKeySpec—Provides access to the values (as a byte array) of a DESede (Triple DES) secret key.

  • javax.crypto.spec.DESKeySpec—Provides access to the values (as a byte array) of a DES secret key.

  • javax.crypto.spec.DHPrivateKeySpec—Provides access to the values (g, p, and x) of a Diffie-Hellman private key.

  • javax.crypto.spec.DHPublicKeySpec—Provides access to the values (g, p, and y) of a Diffie-Hellman public key.

  • javax.crypto.spec.PBEKeySpec—Provides access to the password (as a char array) of a password-based encryption key.

  • javax.crypto.spec.SecretKeySpec—Provides access to a secret key in a provider- independent manner.

In some cases, the values returned by a KeySpec object can also be obtained by the Key object that implements the algorithm-specific key.

The KeyFactory Class

The KeyFactory class provides the capability to translate between KeySpec objects and PublicKey and PrivateKey objects. Translation is supported in a bidirectional manner. Instances of KeyFactory are created using the getInstance() factory method. This method takes an algorithm name and an optional provider name and produces a KeyFactory object that supports the specified algorithm and provider. After you've created a KeyFactory object, you can translate between KeySpec, PublicKey, and PrivateKey objects using the following methods:

  • getKeySpec(Key key, Class keySpec)—Returns a KeySpec object corresponding to the specified Key and KeySpec class.

  • generatePrivate(KeySpec keySpec)—Returns the PrivateKey object corresponding to the supplied KeySpec object.

  • generatePublic(KeySpec keySpec)—Returns the PrivateKey object corresponding to the supplied KeySpec object.

  • translateKey(Key key)—Translates a Key object from one provider into a Key object that is used with the KeyFactory.

In addition, the getAlgorithm() and getProvider() methods return the algorithm and Provider object associated with the KeyFactory class.

The DSAKeyTranslatorApp program of Listing 6.3 shows how the KeyFactory class can be used to translate DSA PrivateKey and PublicKey objects into DSAPrivateKeySpec and DSAPublicKeySpec objects. It then displays the values associated with these KeySpec objects:

C:jdk1.2.2comjaworskisecurityhandbook>java com.jaworski.security.handbook
The KeyFactory Class.DSAKeyTranslatorApp

DSA Private Key

x = 397480743646798716881501857599780760012942323174

DSA Public Key

g = 5421644057436475141609648488325705128047428394380474376
The KeyFactory Class 834667300766108262613900542681289080713724597310673074119355136085795982097390
The KeyFactory Class 670890367185141189796

p = 1323237689519861240754793071826743575772852702962340
The KeyFactory Class 887224515603975771302903636871914645218604120423735052178524033704875207146279
The KeyFactory Class 8273003935646236777459223

q = 857393771208094202104259627990318636601332086981

y = 1035330373894177285405842560959848832825611548643000
The KeyFactory Class 976296872480125362277083073724612877886879121275235854365316117646399506807057å0812429772438851594321128

The KeyFactory Class

Example 6.3. The DSAKeyTranslatorApp Program

package com.jaworski.security.handbook;

import java.security.*;
import java.security.spec.*;

public class DSAKeyTranslatorApp {
 public static void main(String[] args)
   throws NoSuchAlgorithmException, InvalidKeySpecException {
  // Generate a 512-bit DSA key pair
  KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
  kpg.initialize(512);
  KeyPair keys = kpg.genKeyPair();
  // Obtain the private and public keys
  PrivateKey priKey = keys.getPrivate();
  PublicKey pubKey = keys.getPublic();
  // Convert the PrivateKey into a DSAPrivateKeySpec object
  KeyFactory kf = KeyFactory.getInstance("DSA");
  DSAPrivateKeySpec dsaPriKeySpec = (DSAPrivateKeySpec)
   kf.getKeySpec(priKey,DSAPrivateKeySpec.class);
  // Convert the PublicKey into a DSAPublicKeySpec object
  DSAPublicKeySpec dsaPubKeySpec = (DSAPublicKeySpec)
   kf.getKeySpec(pubKey,DSAPublicKeySpec.class);
  // Print the keys
  System.out.println("
DSA Private Key");
  System.out.println("
x = "+dsaPriKeySpec.getX());
  System.out.println("
DSA Public Key");
  System.out.println("
g = "+dsaPubKeySpec.getG());
  System.out.println("
p = "+dsaPubKeySpec.getP());
  System.out.println("
q = "+dsaPubKeySpec.getQ());
  System.out.println("
y = "+dsaPubKeySpec.getY());
 }
}

The SecretKeyFactory Class

The SecretKeyFactory class of javax.crypto is similar to the KeyFactory class of java.security except that it supports key translation for SecretKey objects. Its getInstance(), getAlgorithm(), and getProvider() methods work in the same manner as those of KeyFactory. In addition to these methods, it supports the following:

  • generateSecret(java.security.spec.KeySpec keySpec)—Returns a SecretKey object that corresponds to the supplied KeySpec.

  • getKeySpec(SecretKey key, java.lang.Class keySpec)—Returns a KeySpec object corresponding to the supplied SecretKey object and the KeySpec class.

  • translateKey(SecretKey key)—Returns a SecretKey object that corresponds to the SecretKeyFactory. This method may be used to translate keys between provider formats.

The JCE 1.2 SecretKeyFactory implementation provides support for both DES and DESede.

Key Agreement

Key agreement, key exchange, or key distribution is the process by which multiple communicating parties can come to agreement about which keys to use to carry out encrypted (or signed) communication.

In public key systems, key agreement is greatly simplified. The communicating parties publish their public keys or send them to the other parties. The public keys are typically distributed using digital certificates. The certificates are used by the recipients to authenticate that the public key they've received actually belongs to the party that sent or published the key. After a party's public key is received, all communication with that party is encrypted using the public key.

Key agreement in secret-key systems is much more complicated. Typically, a unique secret-key is used between pairs of communicating parties, and that key is limited to a single session or a fixed period of time (day, week, month, and so on). In an organization with 1000 users, whose users, on average, communicate with a subset of 20 other users, about 1020 different keys will be needed to support secret communication. In addition, these keys must be changed at the end of the key's useful period (session, day, week, month, and so on). That's a lot of keys to be distributed!

The key distribution problem can be ameliorated by having groups of users share the same key and by extending the key lifetime to a longer period. However, both of these measures weaken the security of the overall system. In addition, neither solves the fundamental problem of how to physically distribute the keys. In some organizations, keys are physically distributed by courier and are loaded into cryptographic devices manually or via electronic keying devices. However, in most cases, the best approach to secret key distribution is to use a key agreement protocol.

A key agreement protocol uses a public-key algorithm to enable two or more communicating parties to arrive at the same secret key. Typically, The secret key is used to enable secret communication for a single session (or fixed part of a session). This key is referred to as a session key.

For example, in a simple key agreement protocol, one party might simply encrypt a session key using the public key of another party and then send the encrypted key to that party. Secret communication takes place when the other party receives and decrypts the session key. Although this approach works and is in practice today, an even more efficient approach to key agreement was developed by Whitfield Diffie and Martin Hellman. It is known as the Diffie-Hellman key agreement protocol. This protocol has the advantage that none of the communicating parties needs to have any knowledge (including the public key of the other parties) in advance. I'll summarize how the protocol works for two communicating parties, which is its typical use. However, it is easily generalized to three or more parties.

Figure 6.3 summarizes the operation of the Diffie-Hellman key agreement protocol. Parties A and B use a common modulus p and base g, so that g is primitive modulus p. This means that for any integer k between 1 and p-1, there exists an integer n such that k = gn mod p. The values of p and g can be published or simply exchanged between A and B. For security reasons, the value of p should be chosen so that it is fairly large (512 bits or more).

How Diffie-Hellman key agreement works.

Figure 6.3. How Diffie-Hellman key agreement works.

Given that both sides agree on p and g, each party picks a random integer x and calculates gx. That is, party A picks xA and calculates yA = gxA mod p, and party B picks xB and calculates yB = gxB mod p. Then, A and B send the calculated values (yA and yB) to each other. Due to the difficulty of calculating discrete logarithms over a large finite field, it is computationally infeasible for someone to calculate xA from yA or xB from yB.

After party A receives yB from B, he calculates yBxA mod p, which equals g(xA xB) mod p. Party B calculates yAxB mod p, which equals g(xA xB) mod p. In this manner, both parties arrive at the same session value, which can then be used to calculate the session key value using a standard convention.

Note

Diffie-Hellman Terminology

The values xA and xB are referred to as the private keys of A and B. The values yA and YB are referred to as their public keys. However, none of these values are used to perform encryption.

Note

N-Party Diffie-Hellman Key Agreement

A key agreement between three or more parties can be reached using the Diffie-Hellman approach if each of the parties sends its public key value (directly or indirectly) to each of the other parties. The final key value is g(x1 x2 .. xn) mod p where x1, x2, ..., xn are the private key values selected by each of the n parties involved in the key exchange.

Simple Key Management for Internet Protocols (SKIP)

The modulus p and base SKIP (Simple Key Management for Internet Protocols) g of the Diffie-Hellman key agreement protocol have been standardized in SKIP. SKIP (http://www.skip.org) is an Internet standard that allows hosts to agree on a session key for the encryption of Internet Protocol (IP) datagrams. It is typically used to support Virtual Private Networks (VPNs). SKIP uses the following (1224 bit) modulus:

F488FD584E49DBCD20B49DE49107366B336C380D451D0F7C88B31C7C5B2D8EF6F3C923C043F0A55
Simple Key Management for Internet Protocols (SKIP) B188D8EBB558CB85D38D3
34FD7C175743A31D186CDE33212CB52AFF3CE1B1294018118D7C84A70A72D686C40319C807297ACA950CD9969FABD00A509B
Simple Key Management for Internet Protocols (SKIP) 0246D3083D66A45D419F9C7CBD894B221926BAABA25EC355E92F78C7

The value 2 is used as the base value g. This simplifies the calculation of the values that are exchanged between parties. However, the selection of 2 as the base in no way weakens the security of the key agreement protocol, which is entirely dependent upon the modulus and the randomness in which the values XA and XB are generated.

The KeyAgreementApp program of Listing 6.4 provides an example of using the SKIP modulus and base values.

JCE Support for Key Agreement

The JCE supports key agreement via the KeyAgreement class of javax.crypto. This class is a factory class and supports protocol- and algorithm-independent key agreements. Its getInstance() method allows a key agreement algorithm and an optional provider to be specified. The JCE provides support for the Diffie-Hellman key agreement protocol. A Diffie-Hellman KeyAgreement object is created as follows:

KeyAgreement ka = KeyAgreement.getInstance("DH");

In order for two or more parties to come to a key agreement, each party must create and initialize a KeyAgreement object. A KeyAgreement object is initialized by invoking the object's init() method with the party's private key (that is, x value in Diffie-Hellman). The init() method has four forms that allow a combination of SecureRandom and AlgorithmParameterSpec objects to be specified in the KeyAgreement. The SecureRandom object is used to select a specific source of randomness. The AlgorithmParameterSpec object is used to specify additional algorithm-specific parameters to be used in the key agreement protocol.

Having created and initialized a KeyAgreement object, each of the parties involved in the key agreement execute the different phases of the key agreement. These phases will vary with the specific key agreement protocol that is being used. In a Diffie-Hellman key agreement between two parties there is only one phase. In this phase, the public key (y value) of the other party is used to update the KeyAgreement object. The doPhase() method takes a public key value that is received from another communicating party and a boolean value as its arguments.The boolean value indicates whether or not the phase being completed is the last phase of the agreement.

After a party completes the last phase of the key agreement protocol (using doPhase()), it can generate the secret that was communicated during the key agreement. This is accomplished using the generateSecret() method. Three forms of this method are provided:

  • byte[] generateSecret()—Returns the secret as a byte array.

  • int generateSecret(byte[] sharedSecret, int offset)—Puts the secret in a byte array at the specified offset. Returns an int value that identifies the number of bytes that were copied into the array.

  • SecretKey generateSecret(String algorithm)—Returns the secret as a SecretKey object of the specified algorithm.

The first two forms return the secret as a byte array. You have to then create a SecretKey using the byte array. The third form is more convenient, allowing you to directly return the SecretKey object that is to be used as the session key. The KeyAgreementApp program of Listing 6.4 illustrates the former approach. The DESKeyAgreementApp program of Listing 6.5 modifies KeyAgreementApp to generate a DES session key.

In addition to the other methods described in this section, the KeyAgreement class provides the getAlgorithm() and getProvider() methods, which return the algorithm name and provider of the key agreement protocol.

Examples of Implementing Key Agreements

The KeyAgreementApp program of Listing 6.4 shows how the KeyAgreement class can be used to carry out a Diffie-Hellman key agreement.

When you run the program, it generates results of the following form. The specific keys that are generated vary with each program invocation. These results identify the public key (y values) used by parties 1 and 2 and the shared (secret) key values that each party generated. As you can see from the output, both parties came to an agreement:

java com.jaworski.security.handbook.KeyAgreementApp
1 is generating a key pair
1 is creating a key agreement object
1 is using 336347909672688904690708374688570192569329418716187132867688464112178
16969808908995560628502711079104982452456962041229390969666864769508847770736557
55776748551435566862547886071883549944659809884215619441521684400690198460511914
4190392372461736598143901524889654419445048399065602417743141670769954458898192
for its public key
2 is generating a key pair
2 is creating a key agreement object
2 is using 523919959999872313317711814736224264283916290585774948339702148121655
05028839986611598332478322502025273634994211413767373455213083289045507717383026
33382867884541461287178750384382950818933648377962018038766577522049763488257862
7464091638859497411470972910780412214060015845644108117201074941733712346980930
for its public key
1 is using 162674881037209112318119317929093456737908880546545101084305753586887
80413969210484870490440170617184583400181301385191257361884845873927654370470276
26745380182951511168234786963778578100323441878072599985360464710824506901698641
11247011659584063460819404791677311718597534510070618064903232080536322088318749
 for its shared key 
2 is using 162674881037209112318119317929093456737908880546545101084305753586887
80413969210484870490440170617184583400181301385191257361884845873927654370470276
26745380182951511168234786963778578100323441878072599985360464710824506901698641
11247011659584063460819404791677311718597534510070618064903232080536322088318749
 for its sharedkey

Example 6.4. The KeyAgreementApp Program

package com.jaworski. security.handbook;
import java.math.*;
import java.security.*;
import java.security.spec.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import javax.crypto.interfaces.*;

public class KeyAgreementApp {
 // SKIP base and modulus values are represented by a
 // DHParameterSpec object
 static DHParameterSpec skipParameterSpec;
 public static void main(String[] args) throws Exception {
  // Create common base and modulus used with SKIP
  createBaseAndModulus();
  // Generate key pair for 1
  System.out.println("1 is generating a key pair");
  KeyPairGenerator kpg1 = KeyPairGenerator.getInstance("DH");
  kpg1.initialize(skipParameterSpec);
  KeyPair kp1 = kpg1.generateKeyPair();
  // Create a KeyAgreement object using the private key
  System.out.println("1 is creating a key agreement object");
  KeyAgreement ka1 = KeyAgreement.getInstance("DH");
  DHPrivateKey privateKey1 = (DHPrivateKey) kp1.getPrivate();
  DHPublicKey publicKey1 = (DHPublicKey) kp1.getPublic();
  ka1.init(privateKey1);
  System.out.println("1 is using "+publicKey1.getY()+
   " for its public key");
  // Generate key pair for 2
  System.out.println("2 is generating a key pair");
  KeyPairGenerator kpg2 = KeyPairGenerator.getInstance("DH");
  kpg2.initialize(skipParameterSpec);
  KeyPair kp2 = kpg2.generateKeyPair();
  // Create a KeyAgreement object using the private key
  System.out.println("2 is creating a key agreement object");
  KeyAgreement ka2 = KeyAgreement.getInstance("DH");
  DHPrivateKey privateKey2 = (DHPrivateKey) kp2.getPrivate();
  DHPublicKey publicKey2 = (DHPublicKey) kp2.getPublic();
  ka2.init(privateKey2);
  System.out.println("2 is using "+publicKey2.getY()+
   " for its public key");
  // Use the KeyAgreement object of 1 to generate its shared key
  ka1.doPhase(publicKey2,true);
  byte[] sharedKey1 = ka1.generateSecret();
  System.out.println("1 is using "+new BigInteger(1,sharedKey1)+
   " for its shared key");
  // Use the KeyAgreement object of 2 to generate its shared key
  ka2.doPhase(publicKey1,true);
  byte[] sharedKey2 = ka2.generateSecret();
  System.out.println("2 is using "+new BigInteger(1,sharedKey2)+
   " for its shared key");
 }
 static void createBaseAndModulus() {
  // The SKIP modulus value
  String s = "F488FD584E49DBCD20B49DE49107366B336C380D451D0F7C88" +
   "B31C7C5B2D8EF6F3C923C043F0A55B188D8EBB558CB85D38D3" +
   "34FD7C175743A31D186CDE33212CB52AFF3CE1B1294018118D" +
   "7C84A70A72D686C40319C807297ACA950CD9969FABD00A509B" +
   "0246D3083D66A45D419F9C7CBD894B221926BAABA25EC355E9" +
   "2F78C7";
  BigInteger base = BigInteger.valueOf(2);
  BigInteger modulus = new BigInteger(s,16);
  skipParameterSpec = new DHParameterSpec (modulus,base);
 }
}

The DESKeyAgreementApp program of Listing 6.5 has a slight modification to the KeyAgreementApp program of Listing 6.4. It uses a different form of the generateSecret() method to generate a DES key as the shared session key. This program's output is as follows:

java com.jaworski.security.handbook.DESKeyAgreementApp
1 is generating a key pair
1 is creating a key agreement object
1 is using 151778792533013269473159537624810896254214836890710805250666497645582
19156766136419763269038803442774959068334146732644830388793623567596535720108547
58016429683623779338313560032209392390089814009651226251859153680355716745778315
10684825513252913055338348923468204255204654964687110235232317977288416701977147
 for its public key
2 is generating a key pair
2 is creating a key agreement object
2 is using 110227302952840276242342059222041554089835032612425081720522859174399
88118592839229310813875070996945502781100827234298131460837615945200930537562852
54342265436743670728945713131922912299830939034190132244884273453222094699673937
56347137395429424848849909685210638675428212663284630031689573476744053300277447
 for its public key
1 is using e48095e2ad7e22c2 as its DES session key
2 is using e48095e2ad7e22c2 as its DES session key

Example 6.5. The DESKeyAgreementApp Program

 package com.jaworski.security. handbook;
import java.math.*;
import java.security.*;
import java.security.spec.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import javax.crypto.interfaces.*;

public class DESKeyAgreementApp {
 // SKIP base and modulus values are represented by a
 // DHParameterSpec object
 static DHParameterSpec skipParameterSpec;
 public static void main(String[] args) throws Exception {
  // Create common base and modulus used with SKIP
  createBaseAndModulus();
  // Generate key pair for 1
  System.out.println("1 is generating a key pair");
  KeyPairGenerator kpg1 = KeyPairGenerator.getInstance("DH");
  kpg1.initialize(skipParameterSpec);
  KeyPair kp1 = kpg1.generateKeyPair();
  // Create a KeyAgreement object using the private key
  System.out.println("1 is creating a key agreement object");
  KeyAgreement ka1 = KeyAgreement.getInstance("DH");
  DHPrivateKey privateKey1 = (DHPrivateKey) kp1.getPrivate();
  DHPublicKey publicKey1 = (DHPublicKey) kp1.getPublic();
  ka1.init(privateKey1);
  System.out.println("1 is using "+publicKey1.getY()+
   " for its public key");
  // Generate key pair for 2
  System.out.println("2 is generating a key pair");
  KeyPairGenerator kpg2 = KeyPairGenerator.getInstance("DH");
  kpg2.initialize(skipParameterSpec);
  KeyPair kp2 = kpg2.generateKeyPair();
  // Create a KeyAgreement object using the private key
  System.out.println("2 is creating a key agreement object");
  KeyAgreement ka2 = KeyAgreement.getInstance("DH");
  DHPrivateKey privateKey2 = (DHPrivateKey) kp2.getPrivate();
  DHPublicKey publicKey2 = (DHPublicKey) kp2.getPublic();
  ka2.init(privateKey2);
  System.out.println("2 is using "+publicKey2.getY()+
   " for its public key");
  // Use the KeyAgreement object of 1 to generate its shared key
  ka1.doPhase(publicKey2,true);
  SecretKey sharedKey1 = ka1.generateSecret("DES");
  System.out.println("1 is using "+
   Conversion.byteArrayToHexString(sharedKey1.getEncoded())+
   " as its DES session key");
  // Use the KeyAgreement object of 2 to generate its shared key
  ka2.doPhase(publicKey1,true);
  SecretKey sharedKey2 = ka2.generateSecret("DES");
  System.out.println("2 is using "+
   Conversion.byteArrayToHexString(sharedKey2.getEncoded())+
   " as its DES session key");
 }
 static void createBaseAndModulus() {
  // The SKIP modulous value
  String s = "F488FD584E49DBCD20B49DE49107366B336C380D451D0F7C88" +
   "B31C7C5B2D8EF6F3C923C043F0A55B188D8EBB558CB85D38D3" +
   "34FD7C175743A31D186CDE33212CB52AFF3CE1B1294018118D" +
   "7C84A70A72D686C40319C807297ACA950CD9969FABD00A509B" +
   "0246D3083D66A45D419F9C7CBD894B221926BAABA25EC355E9" +
   "2F78C7";
  BigInteger base = BigInteger.valueOf(2);
  BigInteger modulous = new BigInteger(s,16);
  skipParameterSpec = new DHParameterSpec(modulous,base);
 }
}

Key Storage and Password-Based Encryption

Key storage is another critical aspect of key management. After you generate a secret key (or a private key of a public-private key pair), you must either commit the key to memory or store it in some fashion. Because most of us don't have the capability to memorize a single key (never mind a whole set of keys), we tend to store the keys.

Keys can be stored in a number of ways: You can write them down or print them, you can store them in a file, you can store them in a smart card, or you can store them in a tamperproof electronic keying device. Although most of you currently don't use smart cards or electronic keying devices to store your keys, it is likely that you'll encounter them in the not-too-distant future. Right now, your choices are to store them in hardcopy form or store them on your hard disk.

Hardcopy key storage is cumbersome and insecure. Although you might be able to print a key, you generally have to type it back in. For sizeable keys, that's usually a major inconvenience. You can use a scanner to scan in a previously printed key. However, you won't always have a scanner available when you need it. In any case, you don't want to carry around a wad of key printouts with you. Finally, hardcopy keys can be easily copied or stolen.

In most cases, keys are stored on disk. This makes them vulnerable to being disclosed to anyone who can obtain access to the disk or, in the case of a hard disk, to the computer in which the disk is used. If the computer is connected to the Internet, there's the potential for hackers to obtain access to your computer and then to keys.

The obvious countermeasure to storing your keys on disk is to encrypt them. But this solution tends to be a circular one. What do you do with the key that you used to encrypt the keys that are stored on your computer? The answer to this dilemma is to use password-based encryption (PBE) as shown in Figure 6.4.

Using password-based encryption to protect your keys.

Figure 6.4. Using password-based encryption to protect your keys.

PBE uses a password or passphrase to generate an encryption key. In many applications, PBE also uses a random value, referred to as a salt, to increase the effort required to decrypt a password-encrypted file. The salt is combined with the password to encrypt the file as shown in Figure 6.5.

A salt value is used to increase the strength of password-based encryption.

Figure 6.5. A salt value is used to increase the strength of password-based encryption.

The JCE supports PBE by providing an implementation of the RSA, Inc. PKCS #5: Password-Based Encryption Standard. Its algorithm name is PBEWithMD5AndDES when specified with the Cipher class. The algorithm operates in CBC mode and supports PKCS5Padding (covered in Chapter 5).

PBE is supported via the PBEKeySpec and PBEParameterSpec classes of javax.crypto.spec. A PBEKeySpec object is passed as an argument to the generateSecret() method of SecretKeyFactory to create a SecretKey object. The PBEKeySpec() constructor takes a char array as an argument. The getPassword() method returns the password that is used to create the PBEKeySpec object.

Caution

Storing Passwords as String Objects

String objects are immutable and cannot be overwritten once they are created. For this reason, it is not a good idea to store passwords as String objects.

The PBEParameterSpec class is used to specify the salt and iteration count values that are used to initialize a PBEWithMD5AndDES Cipher object. The salt is passed as a byte array, and the iteration count is passed as an int value to the PBEParameterSpec() constructor. The getSalt() and getIterationCount() methods return these values.

Going from password to a SecretKey object involves the following steps:

  1. Store the password in a char array.

  2. Pass the char array as an argument to the PBEKeySpec constructor to create a PBEKeySpec object.

  3. Create a SecretKeyFactory object by passing "PBEWithMD5AndDES" as an argument to the SecretKeyFactory getInstance() method.

  4. Generate a SecretKey object by invoking the generateSecret() method of the SecretKeyFactory object, passing the PBEKeySpec object as an argument.

The PBEApp program of Listing 6.6 illustrates each of the preceding steps. When you run the program, it displays the window shown in Figure 6.6. Type in a password phrase in the text field and click the Generate Key button. A SecretKey object is created and displayed in the text area (using Base64 notation), as in Figure 6.7.

The opening window of the PBEApp program.

Figure 6.6. The opening window of the PBEApp program.

The SecretKey object corresponding to the pass phrase is displayed in the text area.

Figure 6.7. The SecretKey object corresponding to the pass phrase is displayed in the text area.

After you've used PBE to create a SecretKey object, you need to go through a few more steps before you can create a PBEWithMD5AndDES Cipher object and begin encrypting. These steps are summarized as follows:

  1. Generate a salt value.

  2. Pass the salt value and an iteration count as arguments to the PBEParameterSpec constructor to create a PBEParameterSpec object. An interation count of 20 is generally sufficient.

  3. Create a Cipher object by passing "PBEWithMD5AndDES" as an argument to the Cipher getInstance() method.

  4. Initialize the Cipher object by passing the encryption mode, PBEKeySpec object, and PBEParameterSpec object as arguments to the Cipher object's init() method.

The PBEFileEncryptApp program of Listing 6.7 shows how the preceding steps are accomplished. When you run the program, it displays the window shown in Figure 6.8. Type in a password phrase in the text field (its contents are replaced by asterisks) and then select Encrypt from the File pulldown menu. A Select File to Encrypt dialog box appears, as shown in Figure 6.9. Use it to select a file to be encrypted.

The opening window of the PBEFileEncryptApp program.

Figure 6.8. The opening window of the PBEFileEncryptApp program.

The Select File to Encrypt dialog box.

Figure 6.9. The Select File to Encrypt dialog box.

After you've selected a file to encrypt, a Save Encrypted File as dialog box appears, as shown in Figure 6.10. Use it to identify the name and location of the ciphertext file. After closing the dialog box, the selected file is encrypted and stored as ciphertext in the specified location. You can open the ciphertext file to verify that it is indeed ciphertext.

The Save Encrypted File As dialog box.

Figure 6.10. The Save Encrypted File As dialog box.

To decrypt the ciphertext file, select Decrypt from the File menu. (Make sure that the same password used to encrypt the file is entered into the text field.) A Select File to Decrypt dialog box appears. Use it to open the ciphertext file. A Save Decypted File As dialog box follows. Use it to identify the name and location of the decrypted plaintext file. You can then check the decrypted file to verify that it matches the file that was initially encrypted.

Example 6.6. The PBEApp Program

package com.jaworski.security.handbook;
import java.awt.*;
import java.awt.event.*;
import java.security.*;
import javax.crypto.*;
import javax.crypto.spec.*;

public class PBEApp extends Frame {
 private int WIDTH = 600;
 private int HEIGHT = 500;
 private TextField inputTF;
 private TextArea outputTA;
 public static void main(String[] args) {
  new PBEApp();
 }
 public PBEApp() {
  super("PBEApp");
  setup();
  pack();
  setSize(WIDTH,HEIGHT);
  addWindowListener(new WindowHandler());
  setVisible(true);
 }
 void setup() {
  // Create panels for the input and output text fields
  Panel inputPanel = new Panel();
  Panel outputPanel = new Panel();
  Panel buttonPanel = new Panel();
  inputPanel.setLayout(new GridLayout(3,1));
  outputPanel.setLayout(new GridLayout(1,1));
  Label inputLabel = new Label("Enter pass phrase:");
  Button generateButton = new Button("Generate Key");
  generateButton.addActionListener(new ButtonHandler());
  inputTF = new TextField(70);
  outputTA = new TextArea(10,70);
  inputTF.setFont(new Font("Monospaced",Font.PLAIN,14));
  outputTA.setFont(new Font("Monospaced",Font.PLAIN,14));
  // Add the components to the panels
  inputPanel.add(inputLabel);
  inputPanel.add(inputTF);
  buttonPanel.add(generateButton);
  inputPanel.add(buttonPanel);
  outputPanel.add(outputTA);
  // Add to the frame
  add("North",inputPanel);
  add("Center",outputPanel);
 }
 private String generatePBEKey(char[] charArray) {
  String s = "";
  try {
   // Use the char array to create a PBEKeySpec
   PBEKeySpec keySpec = new PBEKeySpec(charArray);
   // Create a SecretKeyFactory for the PBE key
   SecretKeyFactory keyFactory =
    SecretKeyFactory.getInstance("PBEWithMD5AndDES");
   // Generate the key from the key spec
   SecretKey key = keyFactory.generateSecret(keySpec);
   // Convert the key to a byte buffer
   byte[] keyBuffer = key.getEncoded();
   // Convert the byte buffer to a Base64 string
   s = Conversion.byteArrayToBase64String(keyBuffer);
  }catch(Exception e) {
   s = e.toString();
  }
  // Return the key as a string or
  // any exception that was generated.
  return s;
 }
 class ButtonHandler implements ActionListener {
  public void actionPerformed(ActionEvent e) {
   // Generate the PBE key and put it in the text area
   outputTA.setText(generatePBEKey(inputTF.getText().toCharArray()));
  }
 }
 class WindowHandler extends WindowAdapter {
  public void windowClosing(WindowEvent e) {
   setVisible(false);
   dispose();
   System. exit(0);
  }
 }
}

Example 6.7. The PBEFileEncryptApp Program

package com.jaworski. security.handbook;

import java.io.*;
import java.awt.*;
import java.awt.event.*;
import java.security.*;
import javax.crypto.*;
import javax.crypto.spec.*;

public class PBEFileEncryptApp extends Frame {
 private int WIDTH = 500;
 private int HEIGHT = 400;
 private int BUFFER_SIZE = 8;
 private TextField inputTF;
 public static void main(String[] args) {
  new PBEFileEncryptApp();
 }
 public PBEFileEncryptApp() {
  super("PBEFileEncryptApp");
  setup();
  pack();
  setSize(WIDTH,HEIGHT);
  addWindowListener(new WindowHandler());
  setVisible(true);
 }
 void setup() {
  // Create panel for password input
  Panel inputPanel = new Panel();
  inputPanel.setLayout(new GridLayout(2,1));
  Label inputLabel = new Label("Enter pass phrase:");
  inputTF = new TextField(70);
  inputTF.setFont(new Font("Monospaced",Font.PLAIN,14));
  inputTF.setEchoChar('*'),
  inputPanel.add(inputLabel);
  inputPanel.add(inputTF);
  add("North",inputPanel);
   // Setup menu bar
  MenuBar menuBar = new MenuBar();
  Menu fileMenu = new Menu("File");
  MenuItem fileEncrypt = new MenuItem("Encrypt");
  MenuItem fileDecrypt = new MenuItem("Decrypt");
  MenuItem fileExit = new MenuItem("Exit");
  fileMenu.add(fileEncrypt);
  fileMenu.add(fileDecrypt);
  fileMenu.add(fileExit);
  fileEncrypt.addActionListener(new MenuItemHandler());
  fileDecrypt.addActionListener(new MenuItemHandler());
  fileExit.addActionListener(new MenuItemHandler());
  menuBar.add(fileMenu);
  setMenuBar(menuBar);
 }
 Cipher createCipher(int mode) throws Exception {
  // Get the pass phrase out of the text field
  char[] charArray = inputTF.getText().toCharArray();
  // Create a PBEKeySpec from the pass phrase
  PBEKeySpec keySpec = new PBEKeySpec(charArray);
  // Create a SecretKeyFactory
  SecretKeyFactory keyFactory =
   SecretKeyFactory.getInstance("PBEWithMD5AndDES");
  // Generate a secret key using the keyspec
  SecretKey key = keyFactory.generateSecret(keySpec);
  // Create the salt using the MD5 digest of the pass phrase
  MessageDigest md = MessageDigest.getInstance("MD5");
  md.update(inputTF.getText().getBytes());
  byte[] digest = md.digest();
  byte[] salt = new byte[8];
  for(int i=0;i<8;++i) salt[i] = digest[i];
  // Create a PBEParameterSpec using the salt
  PBEParameterSpec paramSpec = new PBEParameterSpec(salt,20);
  // Create an instance of the cipher
  Cipher cipher = Cipher.getInstance("PBEWithMD5AndDES");
  // Initialize it for encryption/decryption with the key and salt
  cipher.init(mode,key,paramSpec);
  return cipher;
 }
 void encryptFile() {
  try {  
   // Create and initialize a cipher
   Cipher cipher = createCipher(Cipher.ENCRYPT_MODE);
   // Select input file
   String inPathString =
    getPath("Select file to encrypt",FileDialog.LOAD);
   if(inPathString == null) return;
   // Select output file
   String outPathString =
    getPath("Save encrypted file as",FileDialog.SAVE);
   if(outPathString == null) return;
   // Open files and encrypt/decrypt
   applyCipher(inPathString,outPathString, cipher,"Encrypting ..");
  }catch(Exception ex) {
   ex.printStackTrace();
  }  
 }
 void decryptFile() {
  try {
   // Create and initialize a cipher
   Cipher cipher = createCipher(Cipher.DECRYPT_MODE);
   // Select input file
   String inPathString =
    getPath("Select file to decrypt",FileDialog.LOAD);
   if(inPathString == null) return;
   // Select output file
   String outPathString =
    getPath("Save decrypted file as",FileDialog.SAVE);
   if(outPathString == null) return;
   // Open files and encrypt/decrypt
   applyCipher(inPathString,outPathString, cipher,"Decrypting ...");
  }catch(Exception ex) {
   ex.printStackTrace();
  }
 }
 void applyCipher(String inFile,String outFile,Cipher cipher,
   String message) throws Exception {
  // Open input file using a CiperInputStream
  CipherInputStream in = new CipherInputStream(
   new BufferedInputStream(new FileInputStream(inFile)),cipher);
  // Open output file
  BufferedOutputStream out = new BufferedOutputStream(
   new FileOutputStream(outFile));
  // Notify user that encryption/decryption is in progress
  WaitDialog dialog = new WaitDialog(message);
  dialog.pack();
  dialog.setBounds(100,100,150,100);
  dialog.show();
  // Encrypt/decrypt file
  byte[] buffer = new byte[BUFFER_SIZE];
  int numRead = 0;
  do {
   numRead = in.read(buffer);
   if(numRead > 0) out.write(buffer,0,numRead);
  } while(numRead == 8);
  in.close();
  out.close();
  // Remove dialog box after a second
  Thread.sleep(1000);
  dialog.hide();
  dialog.dispose();
 }
 String getPath(String title,int mode) {
  FileDialog fd = new FileDialog(this,title,mode);
  fd.show();
  // If file a file has not been selected then return null
  String inDirectoryString = fd.getDirectory();
  String inFileString = fd.getFile();
  if(inDirectoryString == null || inFileString == null)
   return null;
  return inDirectoryString + inFileString;
 }
 class MenuItemHandler implements ActionListener {
  public void actionPerformed(ActionEvent e) {
   String s = e.getActionCommand();
   if(s.equals("Encrypt")) encryptFile();
   else if(s.equals("Decrypt")) decryptFile();
   else{
    setVisible(false);
    dispose();
    System.exit(0);  
   }
  }
 }
 class WindowHandler extends WindowAdapter {
  public void windowClosing(WindowEvent e) {
   setVisible(false);
   dispose();
   System.exit(0);
  }
 }
 class WaitDialog extends Dialog {
  public WaitDialog(String s) {
   super(PBEFileEncryptApp.this,"Please wait");
   setLayout(new BorderLayout());
   add("Center",new Label(s,Label.CENTER));
  }
 }
}

Key Management Differences Between JDK 1.1 and the Java 2 Platform (version JDK 1.2)

The key management approach used in the Java Security API has focused on user-oriented key generation and management. The Java security API and the JCE provide the user with the capability to securely generate public-private key pairs and secret keys. Keys can be securely stored by encrypting them using password-based encryption. Public keys are distributed and validated using digital certificates.

Although, numerous capabilities (such as support for X.509 version 3 certificates) have been added in JDK 1.2, the basic approaches to these key management activities have remained the same between JDK 1.1 and the Java 2 platform (version JDK 1.2). The major difference between JDK 1.1 to JDK 1.2 is the way in which the public keys of other users are managed. I'll cover these differences in the following sections.

JDK 1.1 Key Management

JDK 1.1 uses an approach, referred to as identity-based key management, that associates public keys with identities. Identities are entities, such as individuals, organizations, or software programs, that are capable of possessing and using a public key. Identities are implemented by the java.security.Identity class and are organized in a hierarchical fashion into identity scopes. An identity scope is a collection of identities and is implemented by the java.security.IdentityScope class. Because IdentityScope is a subclass of Identity, an IdentityScope can contain both Identity and IdentityScope objects. Another subclass of Identity, java.security.Signer, represents an identity that possesses a private key that is used for signing. Figure 6.11 illustrates the relationships between the Identity, IdentityScope, and Signer classes.

The JDK 1.1 approach to public key management classes.

Figure 6.11. The JDK 1.1 approach to public key management classes.

I'll cover the Identity, IdentityScope, and Signer classes in more detail in the following subsections. But, before doing so, I must warn you that these classes are deprecated in JDK 1.2 and have been replaced by a new public-key management mechanism. If you are only interested in JDK 1.2 key management, you can safely skip the following subsections. However, if you are interested in how key management changed from JDK 1.1 to JDK 1.2 then read on.

The Identity Class

The Identity class implements the java.security.Principal interface, which provides the capability to access the name of an Identity and obtain a String representation of an Identity. The Principal interface is used by the interfaces of the java.security.acl package to define access control policies. You can think of an Identity object as a Principal object that has a public key and is organized into a hierarchy of IdentityScope objects. The constructors of the Identity class allow the Principal to be named and (optionally) the IdentityScope to be specified. The getPublicKey() and setPublicKey() methods enable the Identity's PublicKey object to be accessed. The addCertificate(), getCertificates(), and removeCertificate() methods support the management of certificates that authenticate the Identity's public key.

Note

JDK 1.1 Certificates

The java.security.Certificate interface defines methods that encapsulate the notion of a JDK 1.1 certificate. This interface has been deprecated in JDK 1.2 and replaced by the Certificate class of the java.security.cert package.

The IdentityScope Class

The IdentityScope class extends Identity to provide a context for organizing identities. One of the Identity class's constructors allows an Identity's scope to be specified. A similar constructor is provided in the IdentityScope class, allowing an IdentityScope object to be created within the scope of another IdentityScope object. If an Identity is created without an IdentityScope, it is created in the default no scope scope.

Names and public keys are unique within an IdentityScope. No two identities within the same scope can have the same name or public key. The JDK 1.1 javakey tool is used to manage an IdentityScope object, referred to as the system identity scope. This identity scope is specified by the system.scope property of the libsecurityjava.security file of the base JDK 1.1 installation directory. This IdentityScope object is stored in serialized form by the javakey tool.

Note

Javakey is No Longer Supported

The JDK 1.1 javakey tool has been replaced by the JDK 1.2 keytool and jarsigner tools and is no longer supported.

The Signer Class

The Signer subclass of Identity enables an Identity to possess a private key. The getPrivateKey() and setKeyPair() methods support the management of this key. Note that a Signer may be created within an IdentityScope in the same manner as an Identity or IdentityScope object.

JDK 1.2 Key Management

The identity-based key management scheme of JDK 1.1 has been replaced by a keystore-based approach in JDK 1.2. A keystore is a container for secret keys, public-private key pairs, and certificates that attest to the validity of a public key. Figure 6.12 illustrates this concept.

What's inside a keystore?

Figure 6.12. What's inside a keystore?

A keystore may contain two types of entries:

  • Key Entry—Holds either a secret key or a public-private key pair (and a chain of certificates that authenticates the public key). A key entry can be used to sign objects and to provide a certificate that authenticates your signature.

  • Trusted Certificate Entry—Holds a public key certificate of another party. By having the certificate in the keystore, you trust that the certificate belongs to the subject/owner identified in the certificate. You can use a trusted certificate entry to authenticate information received from a third party.

A keystore can have multiple key entries and multiple trusted certificate entries. For example, you can have multiple public-private key pairs (in the form of key entries) that you use for work, personal correspondence, shopping, and banking. You can have trusted certificate entries for co-workers, friends, family, and acquaintances.

Each of the entries in a keystore is associated with a unique alias. This alias is a String that identifies the use of a key entry (for example, "Personal email" or "Code signing") or the name of the entity with which a trusted certificate is associated ("Jason" or "Emily"). Figure 6.13 shows how aliases are used within a keystore.

Aliases are used to identify keystore entries associated with aliases.

Figure 6.13. Aliases are used to identify keystore entries associated with aliases.

The KeyStore Class

The java.security.KeyStore class encapsulates the notion of a KeyStore. It is an abstract class that is implemented by provider-specific implementations. The getInstance() method is a factory method that is used to create KeyStore objects by name, and optionally, by provider. The keystore type supported by the JDK 1.2 is JKS for Java keystore. The PKCS12 type is also defined (but not implemented) in JDK 1.2. This type is defined in PKCS#12.

The KeyStore class provides a number of methods that are used to manage a keystore and its entries.:

Keystore management methods are as follows:

  • getInstance(String type)—A static factory method that creates a KeyStore object of the specified type.

  • getInstance(String type, String provider)—A static factory method that creates a KeyStore object of the specified type from the specified provider.

  • getDefaultType()—Returns the default keystore type as specified in the Java security properties file or JKS if the property does not exist.

  • getType()—Returns the keystore type as a String object.

  • getProvider()—Returns the Provider object that created this KeyStore object.

  • load(InputStream stream, char[] password)—Loads the KeyStore object from the specified input stream. The password is used to verify the integrity of the keystore's data.

  • store(OutputStream stream, char[] password)—Writes the keystore to the specified output stream, protecting its integrity with the specified password.

  • size()—Returns the number of entries in the keystore.

  • deleteEntry(String alias)—Deletes the entry identified by the alias.

Alias and certificate management methods are as follows:

  • aliases()—Returns an Enumeration of all the alias names in the keystore.

  • containsAlias(String alias)—Returns a boolean value indicating whether the specified alias is in the keystore.

  • getCertificate(String alias)—Returns the Certificate (java.security.cert.Certificate) associated with the specified alias or null if the alias does not exist or does not have a certificate.

  • getCertificateAlias(Certificate cert)—Returns the alias of the first keystore entry whose certificate matches the specified certificate or null if no match occurs.

  • getCertificateChain(String alias)—Returns the certificate chain associated with the specified alias.

  • getCreationDate(String alias)—Returns the date of creation of the entry with the given alias or null if the alias does not exist.

  • getKey(String alias, char[] password)—Returns the Key object associated with the specified alias via the supplied key decryption password. Returns null if the alias does not reference a key entry.

Key entry management methods are as follows:

  • isKeyEntry(String alias)—Returns a boolean value indicating whether the alias references a key entry.

  • setKeyEntry(String alias, byte[] key, Certificate[] chain)—If the alias does not exist, creates a key entry for the alias. If the alias exists and is a key entry, the entry's key is replaced. Otherwise, a KeyStoreException is thrown. A certificate chain is only needed for a private key (that is, the certificate chain of the associated public key).

  • setKeyEntry(String alias, Key key, char[] password, Certificate[] chain)—If the alias does not exist, creates a key entry for the alias. If the alias exists and is a key entry, the entry's key is replaced. Otherwise, a KeyStoreException is thrown. A certificate chain is only needed for a private key (that is, the certificate chain of the associated public key). The password is used to protect the key.

Trusted certificate management methods are as follows:

  • isCertificateEntry(String alias)—Returns a boolean value indicating whether the alias references a trusted certificate entry.

  • setCertificateEntry(String alias, Certificate cert)—If the alias does not exist, creates a trusted certificate entry for the alias. If the alias exists and is a trusted certificate entry, the entry's certificate is replaced. Otherwise, a KeyStoreException is thrown.

A keystore might or might not be persistent. If the keystore is persistent, it is loaded from a stream using the load() method. If the keystore is not persistent, it is still loaded via the load() method, but a null value is used as the input stream. In either case, the keystore is loaded before it is used. A password can be supplied to the load() method to check the integrity of the data that is loaded. The implementation of the password is provider-specific. However, it is typically combined with the keystore data to calculate a message digest when the keystore data is stored via the store() method. When the keystore is loaded (via load()) the supplied password is used to recalculate the digest as shown in Figure 6.14.

Using a password to check the integrity of a keystore.

Figure 6.14. Using a password to check the integrity of a keystore.

In most cases, you won't need need to access a KeyStore object directly in your programs. The JDK 1.2 keytool program enables you to manage the system KeyStore object directly from the command line.

The Keytool

The keytool was introduced with the Java 2 SDK. It is used to management keys and certificates and provides an implementation of the java.security.KeyStore class. It supports the following capabilities:

  • Key pair generation—Generates a public and private key pair (that is, a key entry) and creates an X.509 v1 self-signed certificate for the public key. The certificate is stored as a single-element certificate chain.

  • Management of key entries—Allows key entries to be created, imported, exported, listed, and deleted.

  • Management of trusted certificate entries—Allows the certificates and certificate chains of others to be imported, exported, listed, and deleted.

  • JDK 1.1 identity database importing—Allows the identity database used by the JDK 1.1 to be imported.

  • Certificate Signing Request (CSR) generation—CSRs can be generated for submission to a certification authority.

  • Password management—Passwords can be used to protect private keys or the entire keystore. Passwords can be changed through keytool commands.

  • Help—Provides help to the user in terms of command summaries.

The keystore managed by the keytool contains two types of entries: key entries and trusted certificate entries. Key entries contain a secret or private key that is protected (weakly encrypted) using a password. A private key entry is accompanied by a certificate chain that corresponds to its associated public key.

A trusted certificate entry is a public key certificate of someone other than the owner of the keystore. The keystore owner trusts that the public key in the certificate is really that of the identify specified in the certificate. These certificates are typically self-signed by the identity referred to in the certificate.

Note

The Java Keystore

The Java Keystore (JKS) is the default keystore implementation provided by the Java 2 SDK. It is U.S. exportable and does not provide strong encryption for the private keys that it protects. You can use other keystore implementations provided by other package providers. To do so, replace the default keystore with another keystore by specifying the keystore in the keytool command line or by setting the keystore.type property in the java.security configuration file.

Note

The Keytool and the Jarsigner Tool

The jarsigner tool uses the keystore that is managed by keytool to verify the signatures of signed applets. You'll learn about the jarsigner tool in Chapter 4,"Applet Security." The keytool and the jarsigner tool replace the javakey tool of JDK 1.1.

Each keystore entry is associated with a unique alias. The alias is a short name for the identity to which the keystore entry applies. For example, I use the alias jamie for myself. I also have the aliases lisa, emily, and jason. Aliases are not case sensitive—for example, jamie, Jamie, and JAMIE are all equivalent.

Figure 6.15 provides an overview of the keytool's operation. The keytool command set and an extended example of the keytool's operation is provided in Appendix F,"Using the Keytool."

Using the keytool.

Figure 6.15. Using the keytool.

Summary

In this chapter, you've covered the details of the Java Security API's support of key management. You learned how keys are securely generated, stored, and distributed. You also learned how the key management of the Java Security API changed from JDK 1.1 to JDK 1.2. In the next chapter, you'll learn the details of the Java Security API's support for message digests, digital signatures, and digital certificates.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.214.155