Chapter 8. The Java Cryptography Extension

You've been working with the JCE since Chapter 5,"An Introduction to Cryptography." However, you haven't focused specifically on the capabilities provided by the JCE. In this chapter, you'll take a high-level view of the JCE. I'll describe its organization and capabilities and introduce any special classes and interfaces that were not covered in earlier chapters. You'll also take a look at the Cryptix JCE, another JCE implementation that is internationally available via the Web. Finally, you'll look at how cryptographic providers are developed and develop a small provider of your own.

Inside the JCE

United States export control laws restrict the exporting of certain types of cryptographic software outside the United States and Canada. If the JCE 1.2 were a part of the JDK, the JDK, as a whole, would fall under these export control laws. This would severely limit Java's world-wide popularity. Although the JCE was not developed specifically to deal with U.S. export control laws, you can think of it as a set of packages that contains the implementations of security-sensitive cryptographic algorithms that would otherwise be restricted. These packages are as follows:

  • javax.crypto—Provides the 14 classes, one interface, and four exceptions that support basic cryptographic algorithms.

  • javax.crypto.interfaces—Provides three interfaces that support Diffie-Hellman keys.

  • javax.crypto.spec—Provides 12 classes that define key specifications and algorithm parameter specifications.

With only three packages, 26 classes, four interfaces, and four exceptions, the JCE is rather small.

Note

Downloading and Installing the JCE

Appendix E,"Downloading and Installing the JCE 1.2," covers all that you need to know to obtain the JCE 1.2 and get it working on your system.

The javax.crypto package is the heart of the JCE. It defines the SecretKey interface, which extends java.security.Key to provide support for symmetric, secret-key cryptography. The classes defined by javax.crypto also define fundamental cryptographic objects. They are summarized as follows:

  • Cipher—Encapsulates the notion of a cipher and provides support for cipher selection, encryption, and decryption.

  • CipherSpi—The service provider interface for the Cipher class.

  • NullCipher—A subclass of Cipher that maps plaintext to ciphertext without performing any transformation. It is referred to as an identity cipher and is typically used for testing purposes.

  • CipherInputStream—The combination of an InputStream and a Cipher that supports the decryption of data that is read from the InputStream using the Cipher.

  • CipherOutputStream—The combination of an OutputStream and a Cipher that supports the encryption of data that is written to the OutputStream using the Cipher.

  • SealedObject—Provides the capability to work with objects that have been serialized and encrypted.

  • SecretKeyFactory—A key factory for transforming secret keys into key specifications and vice versa.

  • SecretKeyFactorySpi—The service provider interface for the SecretKeyFactory class.

  • KeyGenerator—A key generator for symmetric (secret-key) ciphers.

  • KeyGeneratorSpi—Theservice provider interface for the KeyGenerator class.

  • KeyAgreement—Encapsulates a key agreement/exchange protocol.

  • KeyAgreementSpi—The service provider interface for the KeyAgreement class.

  • Mac—Encapsulates a message authentication code (MAC) algorithm.

  • MacSpi—The service provider interface for the Mac class.

The four exceptions defined by javax.crypto are

  • BadPaddingException

  • IllegalBlockSizeException

  • NoSuchPaddingException

  • ShortBufferException

They extend java.security.GeneralSecurityException to define exceptions with the size and padding of data processed by cryptographic algorithms.

The javax.crypto.interfaces package defines the DHKey, DHPrivateKey, and DHPublicKey interfaces. DHKey provides access to a Diffie-Helman key, and DHPrivateKey and DHPublicKey provide access to the private and public key components.

The javax.crypto.spec package defines the following key and algorithm parameter specifications:

  • DESedeKeySpec

  • DESKeySpec

  • DHGenParameterSpec

  • DHParameterSpec

  • DHPrivateKeySpec

  • DHPublicKeySpec

  • IvParameterSpec

  • PBEKeySpec

  • PBEParameterSpec

  • RC2ParameterSpec

  • RC5ParameterSpec

  • SecretKeySpec

The key specifications enable keys to be defined in a provider-independent manner. The algorithm parameter specifications are used to define various parameters used with cryptographic algorithms.

The Cryptix JCE

The Cryptix Foundation Limited has developed a clean room version of the JCE 1.2, which is available over the Web at http://www.cryptix.org. It is not subject to U.S. export restrictions.

The Cryptix JCE is aimed at being 100% compatible with Sun's JCE 1.2. However, it provides support for additional cryptographic algorithms not available in Sun's implementation. The complete list of algorithms supported by the Cryptix JCE include

  • Ciphers—Blowfish, CAST5, DES, IDEA, MARS, RC2, RC4, RC6, Rijndael, Serpent, SKIPJACK, Square, TripleDES, Twofish

  • KeyAgreement Protocols—Diffie-Hellman

  • Modes—CBC, ECB, OFB

  • Hashes—MD2, MD4, MD5, RIPEMD-128, RIPEMD-160, SHA-0, SHA-1, Tiger

  • MACs—HMAC-MD2, HMAC-MD4, HMAC-MD5, HMAC-RIPEMD-128, HMAC-RIPEMD-160, HMAC-SHA-0, HMAC-SHA-1, HMAC-Tiger

  • Signatures—RawDSA, RSA

  • Assymetric ciphers —ElGamal, RSA

Even if you currently have Sun's JCE installed, you might want to download and install the Cryptix JCE because of the additional capabilities that it provides.

Note

The Cryptix JCE

If you are outside the United States and Canada and cannot obtain a copy of Sun's JCE, you should download and install the Cryptix JCE, as described in Appendix E.

Security Providers and Algorithm Independence

One of the design features of the Java Security API and JCE is their support for cryptographic package providers. This support enables software developers to provide an independent implementation of specific cryptographic algorithms. A provider takes the form of a set of packages that implement a subset of the JCE or Java Security API.

The advantage of the Java Security API's cryptographic package provider support is that it provides a common framework for extending the API with additional algorithms, as well as with faster or more secure implementations of existing algorithms. For example, some packages might be implemented in hardware or native code to speed up algorithm response times, whereas others might be implemented in a platform-independent manner to ensure widespread availability. The end result is that the developers of cryptographic applications are able to select the algorithms and providers that best fit their needs.

Cryptographic applications can query the Java runtime environment to determine which providers have been statically installed and then dynamically install any new providers that are needed by the applications. In addition, providers can be installed in a preference order. This enables applications to choose one provider over another when a particular algorithm is supported by multiple providers.

The standard provider that is installed with the JCE 1.2 is SunJCE. SunJCE provides popular algorithms that implement the cryptographic classes of the JCE 1.2.

How a Security Provider Is Organized

The Java Security API and JCE support security providers through the use of engine, SPI, and provider classes.

Engine Classes

Engine classes define general types of cryptographic objects and the API for accessing those objects. Examples of engine classes are as follows:

  • javax.crypto

    • Cipher

    • KeyAgreement

    • KeyGenerator

    • Mac

    • SecretKeyFactory

  • java.security

    • AlgorithmParameterGenerator

    • AlgorithmParameters

    • KeyFactory

    • KeyPairGenerator

    • KeyStore

    • MessageDigest

    • SecureRandom

    • Signature

  • java.security.cert

    • CertificateFactory

To use an engine class, invoke its static getInstance() method with the name of a specific algorithm and (optionally) a provider name.

SPI Classes

The SPI classes are abstract classes that provide a mechanism by which service providers can define their own implementations of the objects represented by and algorithms used by the engine classes. Each SPI class is used with a unique engine class. The SPI class has the same name as the corresponding engine class with Spi appended to it.

For example, to create a custom cipher algorithm for use with the Cipher engine class, you extend CipherSpi and override its abstract methods with the implementation of your custom cipher algorithm.

Provider Classes

The provider classes extend the java.security.Provider class. These classes supply the name, version number, and general information about the service provider. They also set properties that map algorithm names to the classes that implement those algorithms.

You'll see examples of the relationship between engine classes, SPI classes, and provider classes in the next section. You'll also develop a custom provider that supports a variation of the rot13 cipher.

Creating a New Provider

To show you how providers are developed and organized, you'll develop a provider that supplies a custom implementation of the rot13 cipher. The rot13 cipher is a very simple, keyless cipher that has been around since the time of Caesar. In fact, it is the Caesar cipher with a rotation of 13. The section, "A Short History of Secret Writing," in Chapter 5 covers the Caesar cipher and rot13.

Instead of limiting rot13 to the letters of the alphabet, you'll apply it to all 256 possible byte values. That is the byte value of n (between 0 and 255 inclusive) will be mapped to

(n + 13) % 256

To decrypt an encrypted byte value m, map it to

(m + 243) % 256

You'll encrypt using ECB mode and without padding (refer to Chapter 5).

Because you're developing an encryption algorithm, you'll work with the Cipher (the engine) and CipherSpi (the corresponding SPI class) classes of javax.crypto. You'll also extend the java.security.Provider class with a custom provider class that supports rot13.

Extending the SPI Class

Listing 8.1 provides the Rot13Cipher class, which extends CipherSpi to provide a rot13 implementation. When extending an SPI class, it is important to provide a public, parameterless constructor. This constructor is invoked by the newInstance() method of the Class class when a instance of the class is created. Other than the public, parameterless constructor, all you do is provide an implementation of all abstract methods inherited from the SPI class.

In the case of CipherSpi, there are 13 methods that require implementation. The three forms of engineInit() are used to initialize the cryptographic algorithm. Our rot13 implementation only needs to set the mode (Ciper.ENCRYPT_MODE or Cipher.DECRYPT_MODE) in which the algorithm is to operate. All other arguments are ignored.

The two forms of engineUpdate() perform the actual encryption/decryption operations. These methods use the cipher() method to apply the cipher. The second form of engineUpdate() checks the size of the output buffer before applying the cipher.

The two forms of engineDoFinal() perform the same processing as engineUpdate(). The engineUpdate() method is intended for intermediate cryptographic processing, and the engineDoFinal() method is used to perform the final operation on a byte sequence. As such, doFinal() would incorporate operations such as padding. However, rot13 does not require any specific finalization processing.

The engineGetBlockSize() method returns a value of 1 because rot13 operates on one byte at a time. The engineGetOutputSize() method maps input length to output length because rot13 provides a byte-for-byte transformation.

The engineSetMode() and engineSetPadding() methods support the ECB and NoPadding options.

The engineGetParameters() and engineGetIV() methods return null because rot13 does not require any special parameters or an initialization vector.

Example 8.1. The Rot13Cipher Class

package com.jaworski.security.handbook;

import java.security.*;
import java.security.spec.*;
import javax.crypto.*;
public class Rot13Cipher extends CipherSpi {
 private int opmode = Cipher.ENCRYPT_MODE;
 private int encryptionKey = 13;
 private int decryptionKey = 243;
 // Default constructor.
 public Rot13Cipher() {
 }
 // Implement the cipher
 private byte cipher(byte b) {
  int key = encryptionKey;
  if(opmode == Cipher.DECRYPT_MODE)
   key = decryptionKey;
  return (byte) ((b + key) % 256);
 }
 // CipherSpi that must be implemented.
 protected void engineInit(int opmode, Key key,
   AlgorithmParameterSpec params, SecureRandom random) {
  this.opmode = opmode;
 }
 protected void engineInit(int opmode, Key key,
   AlgorithmParameters params, SecureRandom random) {
  this.opmode = opmode;
 }
 protected void engineInit(int opmode, Key key, SecureRandom random) {
  this.opmode = opmode;
 }
 protected byte[] engineUpdate(byte[] input, int inputOffset,
   int inputLen) {
  byte[] b = new byte[inputLen];
  for(int i=0; i<inputLen; ++i)
   b[i] = cipher(input[inputOffset + i]);
  return b;
 }
 protected int engineUpdate(byte[] input, int inputOffset,
   int inputLen, byte[] output, int outputOffset)
   throws ShortBufferException {
  String msg = "Not enough room in output buffer.";
  if((output.length - outputOffset) < inputLen)
   throw new ShortBufferException(msg);
  for(int i=0; i<inputLen; ++i)
   output[outputOffset + i] = cipher(input[inputOffset + i]);
  return inputLen;
 }
 protected byte[] engineDoFinal(byte[] input, int inputOffset,
   int inputLen) {
  byte[] b = new byte[inputLen];
  for(int i=0; i<inputLen; ++i)
   b[i] = cipher(input[inputOffset + i]);
  return b;
 }
 protected int engineDoFinal(byte[] input, int inputOffset,
   int inputLen, byte[] output, int outputOffset)
   throws ShortBufferException {
  String msg = "Not enough room in output buffer.";
  if((output.length - outputOffset) < inputLen)
   throw new ShortBufferException(msg);
  for(int i=0; i<inputLen; ++i)
   output[outputOffset + i] = cipher(input[inputOffset + i]);
  return inputLen;
 }
 protected int engineGetBlockSize() {
  return 1;
 }
 protected int engineGetOutputSize(int inputLen) {
  return inputLen;
 }
 protected void engineSetMode(String mode)
   throws NoSuchAlgorithmException {
  String msg = "ROT13 only supports ECB mode.";
  if(!mode.toUpperCase().equals("ECB"))
   throw new NoSuchAlgorithmException(msg);
 }
 protected void engineSetPadding(String padding)
   throws NoSuchPaddingException {
  String msg = "ROT13 only supports the NoPadding option.";
  if(!padding.toUpperCase().equals("NOPADDING"))
   throw new NoSuchPaddingException(msg);
 }
 protected AlgorithmParameters engineGetParameters() {
  return null;
 }
 protected byte[] engineGetIV() {
  return null;
 }
}

Extending the Provider Class

Having implemented Rot13Cipher as a concrete subclass of CipherSpi, you now extend java.security.Provider to define a provider class. Listing 8.2 contains the Rot13Provider extension of Provider.

The Rot13Provider() constructor invokes the superclass constructor to pass the provider name, version number, and descriptive information to the Provider constructor. This information is used to register the provider. It also uses the put() method (inherited from Provider) to associate the ROT13 cipher with the com.jaworski.security.handbook.Rot13Cipher class.

The put() method takes two arguments: One is in the form engine.algorithm, and the second is the name of the class that implements the algorithm for the specific provider. Because you are working with the Cipher engine (through CipherSpi), and you're implementing an algorithm named ROT13, the first argument is "Cipher.ROT13". The second argument is simply the name of the class resulting from Listing 8.1.

Cipher algorithm names can include the encryption mode and padding into the algorithm name. These are written in the form "algorithName/mode/padding". For example, you can use "ROT13/ECB/NoPadding". If you supported cipher block chaining with PKCS 5 padding, you can use "ROT13/CBC/PKCS5Padding". Multiple put() invocations can be used to associate multiple algorithm/mode/padding combinations with their implementation classes.

Example 8.2. The Rot13Provider Class

package com.jaworski.security.handbook;

import java.security.*;

public final class Rot13Provider extends Provider {
 public Rot13Provider() {
  // Pass info to superclass constructor
  super("Rot13Provider", 1.0,
   "Rot13Provider 1.0 provides a custom ROT13 implementation.");
  // Set provider properties
  put("Cipher.ROT13","com.jaworski.security.handbook. Rot13Cipher");
 }
}

Installing Provider Classes

Having developed the rot13 cipher's implementation and provider classes, all that's left is to install these classes. This amounts to putting them in your CLASSPATH and configuring them in the java.security file. Typically, provider classes are distributed as a JAR file, and the JAR file is placed in the CLASSPATH. Because the Rot13Cipher and Rot13Provider classes depend on the JCE, the JCE must be installed in order for them to work.

Editing the java.security file enables the provider class to be statically added to the set of known, approved providers. Edit C:jdk1.2.2jrelibsecurity java.security and look for the following:

#
# List of providers and their preference orders (see above):
#
security.provider.1=sun.security.provider.Sun
security.provider.2=com.sun.crypto.provider.SunJCE

Then add the following line:

security.provider.3=com.jaworski.security.handbook.Rot13Provider

You can also add a provider dynamically via the addProvider() and insertProviderAt() methods of the java.security.Security class.

Note

Install the Rot13Provider

Make sure that you install the Rot13Provider before going on to the next section.

Using the Provider

Having developed and installed the Rot13Provider, we'll write a test program that loads the provider, verifies its properties, and performs a sample encryption. The ProviderTest program of Listing 8.3 is this program. It displays the following results:

Provider name: Rot13Provider
Provider version: 1.0
Provider information: Rot13Provider 1.0 provides a custom ROT13 implementation.
Cipher: ROT13
Data to be encrypted: This is a test!
Encrypted data: 617576802d76802d6e2d817280812e
Decrypted data: 546869732069732061207465737421
Decrypted data as a String: This is a test!

The ProviderTest program invokes two methods, listProviderInfo() and testCipher(), that perform these tests. The listProviderInfo() method loads the Rot13Provider and displays the information returned by the getName(), getVersion(), and getInfo() methods inherited from Provider.

The testCipher() method creates a Cipher object that implements the rot13 algorithm as supplied by the Rot13Provider:

   Cipher cipher = Cipher.getInstance("ROT13", "Rot13Provider");

It then initializes this object and puts it in the encryption mode:

   cipher.init(Cipher.ENCRYPT_MODE,null,new SecureRandom());

A SecureRandom object is provided with init() so that the compiler can tell which form of init() to use. However, the SecureRandom object is not used by Rot13Cipher.

The test string is encrypted via the doFinal() method of Cipher:

   byte[] b1 = cipher.doFinal(testString.getBytes());

The cipher is then reinitialized in decryption mode, and the encrypted data is decrypted:

   cipher.init(Cipher.DECRYPT_MODE,null,new SecureRandom());
   byte[] b2 = cipher.doFinal(b1);

The results of these operations are then displayed to the console.

Example 8.3. The ProviderTest Program

package com.jaworski.security.handbook;

import java.security.*;
import javax.crypto.*;

public final class ProviderTest {
 String providerName = "Rot13Provider";
 String algorithmName ="ROT13";
 public static void main(String[] args) {
  listProviderInfo();
  testCipher();
 }
 static void listProviderInfo() {
  Provider p = Security.getProvider("Rot13Provider");
  System.out.println("Provider name: " + p.getName());
  System.out.println("Provider version: " + p.getVersion());
  System.out.println("Provider information: " + p.getInfo());
 }
 static void testCipher() {
  try {
   Cipher cipher = Cipher.getInstance("ROT13", "Rot13Provider");
   System.out.println("Cipher: " + cipher.getAlgorithm());
   String testString = "This is a test!";
   cipher.init(Cipher.ENCRYPT_MODE,null,new SecureRandom());
   byte[] b1 = cipher.doFinal(testString.getBytes());
   cipher.init(Cipher.DECRYPT_MODE,null,new SecureRandom());
   byte[] b2 = cipher.doFinal(b1);
   System.out.println("Data to be encrypted: " + testString);
   System.out.println("Encrypted data: " +
    Conversion.byteArrayToHexString(b1));
   System.out.println("Decrypted data: " +
    Conversion.byteArrayToHexString(b2));
   System.out.println("Decrypted data as a String: " + new String(b2));
  } catch(Exception e) {
   e.printStackTrace();
   System. exit(0);
  }
 }
}

Summary

In this chapter, you took a high-level view of the JCE and learned its organization and capabilities. You also looked at the Cryptix JCE. You learned how cryptographic providers are organized in terms of engine, SPI, and provider classes. Then you developed and tested a provider that implements the rot13 cipher. In the next chapter, you'll learn how the Java Authentication and Authorization Service (JAAS) is used to support strong authentication and user authorization.

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

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