- Example Programs and crypttool
- Cryptographic Services and Providers
- Cryptographic Keys
- Encryption and Decryption
- Message Digest
- Message Authentication Code
- Digital Signature
- Key Agreement
- Summary of Cryptographic Operations
- Cryptography with crypttool
- Limited versus Unlimited Cryptography
- Performance of Cryptographic Operations
- Practical Applications
- Legal Issues with Cryptography
- Summary
- Further Reading
Cryptographic Keys
Secret keys, a stream of randomly generated bits appropriate for the chosen algorithm and purpose, are central to a number of cryptographic operations. In fact, much of the security offered by cryptography depends on appropriate handling of keys, for the algorithms themselves are publicly published. What it means is that a key that can be easily compromised, computed, guessed, or found by trial and error with reasonable effort offers little or no security, no matter how secure the algorithm. Strength of security, or the degree of difficulty in determining the right key by a brute force exhaustive search, depends on the size and randomness of the key. For all these reasons, it is imperative that due diligence is exercised in selecting the right keys, using them properly and protecting them adequately.
However, not all cryptographic operations require secret keys. Certain operations work with a pair of keysa private key that must be kept secret and a corresponding public key that can be shared freely.
The Java platform offers a rich set of abstractions, services and tools for generation, storage, exchange and use of cryptographic keys, simplifying the problem to careful use of these APIs and tools.
Java Representation of Keys
Java interface java.security.Key provides an opaque, algorithm and type independent representation of keys with the following methods:
public String getAlgorithm()
Returns the standard name of the algorithm associated with the key. Examples include "DES", "DSA" and "RSA", among many others.
public byte[] getEncoded()
Returns the encoded value of the key as a byte array or null if encoding is not supported. The type of encoding is obtained by method getFormat(). For "RAW" encoding format, the exact bytes comprising the key are returned. For "X.509" and "PKCS#8" format, the bytes representing the encoded key are returned.
public String getFormat()
Returns the encoding format for this key or null if encoding is not supported. Examples: "RAW", "X.509" and "PKCS#8".
As we know, there are two kinds of encryption algorithms: symmetric or secret key algorithms and asymmetric or public key algorithms. Symmetric algorithms use the same key for both encryption and decryption and it must be kept secret, whereas asymmetric algorithms use a pair of keys, one for encryption and another for decryption. These keys are represented by various subinterfaces of Key with self-explanatory namesSecretKey, PrivateKey and PublicKey. These are marker interfaces, meaning they do not have any methods and are used only for indicating the purpose and type-safety of the specific Key objects. Java Security API has many more Key subinterfaces that allow access of algorithm specific parameters, but they are rarely used directly in application programs and hence are not covered.
Generating Keys
A Key object is instantiated by either internal generation within the program or getting the underlying bit stream in some way from an external source such as secondary storage or another program. Let us look at how keys are generated programmatically.
A SecretKey for a specific algorithm is generated by invoking method generateKey() on javax.crypto.KeyGenerator object. KeyGenerator is an engine class implying that a concrete object is created by invoking the static factory method getInstance(), passing the algorithm name and optionally, the provider name as arguments. After creation, the KeyGenerator object must be initialized in one of two waysalgorithm independent or algorithm specific. Algorithm independent initialization requires only the key size in number of bits and an optional source of randomness. Here is example program GenerateSecretKey.java that generates a secret key for DES algorithm.
Listing 3-3. Generating a secret key
// File: src\jsbook\ch3\GenerateSecretKey.java import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import java.security.Key; public class GenerateSecretKey { private static String formatKey(Key key){ StringBuffer sb = new StringBuffer(); String algo = key.getAlgorithm(); String fmt = key.getFormat(); byte[] encoded = key.getEncoded(); sb.append("Key[algorithm=" + algo + ", format=" + fmt + ", bytes=" + encoded.length + "]\n"); if (fmt.equalsIgnoreCase("RAW")){ sb.append("Key Material (in hex):: "); sb.append(Util.byteArray2Hex(key.getEncoded())); } return sb.toString(); } public static void main(String[] unused) throws Exception { KeyGenerator kg = KeyGenerator.getInstance("DES"); kg.init(56); // 56 is the keysize. Fixed for DES SecretKey key = kg.generateKey(); System.out.println("Generated Key:: " + formatKey(key)); } }
Running this program produces the following output:
C:\ch3\ex1>java GenerateSecretKey Generated Key:: Key[algorithm=DES, format=RAW, bytes=8] Key Material (in hex):: 10 46 8f 83 4c 8a 58 57
Run the same program again. Do you get the same key material? No, you get a different value. How is this explained? The KeyGenerator uses the default implementation of SecureRandom as a source of randomness and this generates a different number for every execution.
Generation of public and private key pair follows a similar pattern with class KeyGenerator replaced by java.security.KeyPairGenerator and method SecretKey generateKey() replaced by KeyPair generateKeyPair(). Example program GenerateKeyPair.java illustrates this.
Listing 3-4. Generating a public-private key pair
import java.security.KeyPairGenerator; import java.security.KeyPair; import java.security.PublicKey; import java.security.PrivateKey; import java.security.Key; public class GenerateKeyPair { private static String formatKey(Key key){ // Same as in GenerateSecretKey.java. hence omitted. } public static void main(String[] unused) throws Exception { KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA"); kpg.initialize(512); // 512 is the keysize. KeyPair kp = kpg.generateKeyPair(); PublicKey pubk = kp.getPublic(); PrivateKey prvk = kp.getPrivate(); System.out.println("Generated Public Key:: " + formatKey(pubk)); System.out.println("Generated Private Key:: " + formatKey(prvk)); } }
Running this program produces:
C:\ch3\ex1>java GenerateKeyPair Generated Public Key:: Key[algorithm=DSA, format=X.509, bytes=244] Generated Private Key:: Key[algorithm=DSA, format=PKCS#8, bytes=201]
Note that the format of public and private keys is not RAW. The public key is in X.509 format and the private key is in PKCS#8 format.
Utility crypttool has commands genk and genkp to generate secret keys and pairs of public-private keys, allowing the user to specify the algorithm, keysize and a way to save the generated keys. Refer to the section Cryptography with crypttoolcrypttool for more details.
Storing Keys
Keys need to be stored on secondary storage so that programs can access them conveniently and securely for subsequent use. This is accomplished through the engine class java.security.KeyStore. A KeyStore object maintains an in-memory table of key and certificate entries, indexed by alias strings, allowing retrieval, insertion and deletion of entries. This object can be initialized from a file and saved to a file. Such files are known as keystore files. For security reasons, keystore files and, optionally, individual entries, are password protected.
The following code fragment illustrates initializing a KeyStore object from a JCEKS keystore file test.ks protected with password "changeit".
FileInputStream fis = new FileInputStream("test.ks"); KeyStore ks = KeyStore.getInstance("JCEKS"); ks.load(fis, "changeit".toCharArray());
Different providers or even the same provider supporting different keystore types can store keys in different types of persistent store: a flat file, a relational database, an LDAP (Light-weight Data Access Protocol) server or even MS-Windows Registry.
J2SE v1.4 bundled providers support flat file formats JKS and JCEKS. JKS keystore can hold only private key and certificate entries whereas JCEKS keystore can also hold secret key entries. There is also read-only support for keystore type PKCS12, allowing import of Netscape and MSIE browser certificates into a Java keystore
Java keystore types JKS and JCEK work okay for development and simple applications with small number of entries, but may not be suitable in the production environment that is required to support a large number of entries. Consider investing in a commercial provider for such uses.
Java platform includes a simple command line utility keytool to manage keystores. The primary purpose of this tool is to generate public and private key pairs and manage certificates for PKI based applications. We talk more about this tool in Chapter 4, PKI with Java.