Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

JVM Languages

The Java Provider Architecture


Mar99: The Java Provider Architecture

Paul is a member of the R&D staff at Beechwood. He can be reached at [email protected]


The Java Cryptography Extension, an extension of the Java Development Kit, promises plug-in cryptographic libraries and seamless addition of new algorithms. It accomplishes this by using Java's "provider" architecture.

Providers are Java packages (or sets of packages) that deliver concrete implementations of facilities such as message digests, digital signatures, keys, certificates, algorithms, and random-number generators -- all important components of cryptography. In this article, I'll implement a cipher algorithm, which simulates the Enigma machine made famous by Germany in World War II. The encryption technique used by Enigma is sufficiently strong so as not to be as boring as a Caesar cipher or a Vigenere cipher; on the other hand, since the technique has been so well described and since so much as been written about how to crack Enigma, it is weak enough to pose no great threat. The cipher is symmetric and the key I use is derived from a simple pass phrase. It's worth mentioning that, as a citizen of the United States, I have access to the Java Cryptography Extension that is not available to readers outside the U.S. and Canada. These readers can obtain one of the functional equivalents of JCE that are not subject to U.S. export laws.

Constructing the Machine

The object paradigm lends itself to constructing the software Enigma. You simply create classes corresponding to the components of the machine and write methods that simulate the behavior of data passing through these components.

The main component of the original Enigma machine was the rotor, a disk with 26 contacts on each side and a set of wires that randomly connected the contacts on one side to the contacts on the other. Current flowing in through the contact located in the first position on one side might emerge on the other side at the contact located in the eighteenth position. A movable outer rim on the disc permitted the full alphabet to be shifted relative to the contacts. Before inserting a disc into the machine, a "starting letter" was chosen and the rim was rotated so that this letter was visible when the disc was inserted. The Enigma machine was equipped with five such rotors, three of which were chosen for any given usage of the machine. A message encrypted with a given set of three rotors set to selected start positions could only be decrypted using the same three rotors inserted into the machine in the same order using the same start positions.

Each time a letter was typed on the keyboard, the first disc was rotated one position. When the rotation caused a notch on the disk to pass a certain position, the second disc was rotated one position in the same manner as the tenth's digit on your car's odometer is advanced one digit each time the units digit hits zero. The tripping of a notch on the second rotor likewise caused rotation of the third rotor.

EnigmaRotor.java is the code for the software version of a rotor; see Listing One. It contains 256 contacts because it must accommodate all possible values of a byte. The internal cross wiring, instead of being done at the factory where the rotor was manufactured, is performed by the constructor when an instance of EnigmaRotor is created. The pairs of contacts are implemented using two arrays -- one for each direction. Using this approach, you can avoid lookups and use direct indexing to obtain the mapping of any contact in either the forward or backward direction. The instance variable currentPosition is incremented each time the rotor is advanced one position (that is, each time the advance() method is invoked). When it attains a value of 256, it is reset to zero; this simulates rotation. Each time currentPosition reaches notchPosition, an exception is thrown. (Listing Two is EnigmaRotorTrippedExcepion.java -- code that catches this exception advances the next rotor.)

EnigmaRotor contains three methods:

  • setStartPosition() achieves the same result as rotating the outer ring on the hardware version.
  • advance() does exactly what its name implies.
  • processByte() simulates the behavior of passing input through the contact on one side of a rotor, through the internal cross wiring, and out through the contact on the other side.

Another important component was the reflector, a nonrotating disc with contacts on one side only. Randomly selected pairs of contacts were wired together. The circuitry of the Enigma machine was such that when an electrical current exited the last rotor, it passed through a contact on the reflector and out through the contact's partner to begin its journey back through rotors three, two, and one, and on to a lamp board where it illuminated a lamp corresponding to some letter of the alphabet. The illuminated letter was the result of the encryption.

Listing Three is EnigmaReflector.java. Just as the wiring of EnigmaRotor was performed by the constructor, such is the case with EnigmaReflector. The wired pairs of contacts are stored in an array. The single method reflect() takes the integer value of a contact and returns the integer value of the contact to which the input contact is wired.

The original Enigma machine was equipped with one additional optional component -- the Steckerboard, a plugboard that caused the swapping of pairs of letters before they were sent to the scrambling unit. I didn't implement this component, but doing so would be trivial.

Listing Four is EnigmaMachine.java, which represents an Enigma machine constructed from the components just described. The construction of the machine from the components is performed by the constructor. The single method processByte() takes as input a single byte, passes it through the three rotors, through the reflector, back through the three rotors in reverse order, and returns the resultant value. Listing Four includes the code that catches EnigmaRotorTrippedException and advances the next rotor.

The Java Cryptography Provider Architecture

The Java provider package contains one or more engine classes (classes that provide the functionality of a specific algorithm). MessageDigest, Signature, KeyPairGenerator, CertificateFactory, KeyStore, AlgorithmParameters, AlgorithmParameterGenerator, and SecureRandom are engine classes. I'll limit my discussion to the Cipher class. As an application programmer, you create an instance of Cipher and invoke its methods when you need to use a Cipher. Other than specifying a cryptographic algorithm when you create the instance, you don't need to know what goes on under the covers; you work with concepts and trust someone else to handle the implementation. As the first step in creating your own provider package, I'll examine how you build the bridge between a concrete implementation and conceptual functionality.

In the same package where you find Cipher (javax.crypto.Cipher), you will also find the CipherSpi class, which defines the Service Provider Interface (SPI) for the Cipher class. Any service provider who wishes to supply an implementation of a particular cipher algorithm must do so by implementing all of the abstract methods in the CipherSpi class. There are 11 such methods. Enigma.java (available electronically; see "Resource Center," page 5) is a subclass of CipherSpi. The methods beginning with "engine" are those from the SPI. The overloaded method engineInit() creates instances of EnigmaRotor and EnigmaReflector and uses them to create an instance of EnigmaMachine. The difference between the two engineInit() methods is that one is algorithm independent and the other -- the one that takes an argument of type AlgorithmParameterSpec -- is algorithm specific. AlgorithmParameterSpec is an interface that has no methods or constants but can be used in whatever manner the implementer deems necessary. You can see that EnigmaParameterSpec.java (available electronically) implements this interface as well as the EnigmaParams interface (also available electronically) and is used to pass arrays of notch positions and start positions to the algorithm-specific engineInit() method. The overloaded methods engineDoFinal() and engineUpdate() perform the actual encryption. You now have a provider (based on my earlier definition of a provider as a package that delivers a concrete implementation of a cryptographic facility).

Now, from a functional point of view, let's look at using a Cipher in a program. You can do so by examining EnigmaTest.java (available electronically), which is the small program I used to test my implementation of the Enigma machine. The line cipher = Cipher.getInstance("Enigma", "Corbett"); creates an instance of Cipher. As you can see by the absence of the new keyword, a constructor is not used; instead, the static method getInstance() is invoked. A static method that returns an instance of a class is known as a "factory method." The instance of Cipher created and returned by the factory method encapsulates an instance of a subclass of CipherSpi. Table 1 compares the methods in CipherSpi to the those in Cipher. For any method invoked on an instance of Cipher, the instance invokes the corresponding method on the instance of CipherSpi, which it encapsulates. Therein lies the secret of the bridge between a concrete implementation and conceptual functionality.

Installing and Registering the Provider

Every Java Virtual Machine contains one or more providers and each is represented by an instance of the Provider class. The final class Security keeps track of all the providers in the JVM. Compile and run ListProviders.java (available electronically). When executed on my system, it produces the output in Example 1. The first provider listed (SUN) is the default provider that is delivered with the JDK. The second (SunJCE) is the one delivered with the Java Cryptography Extension. Pick any Java program and run it specifying the option -verbose:class; you will see numerous messages displayed. Among these are messages that show the providers being loaded as the Java Virtual Machine is started. On my system, I see the following two messages displayed:

[Loaded sun.security.provider.Sun from c:\jdk1.2beta4\jre\lib\rt.jar]

[Loaded com.sun.crypto.provider.SunJCE]

It would seem, then, that the next order of business is to provide a mechanism whereby your provider is loaded. This mechanism can be found by examining the file java.security, which is located in the directory jre/lib/security relative to the directory in which the JDK was installed. In this file, you'll find the statement: security.provider.1=sun.security.provider.Sun, which follows the format security.provider.<n> = <className> (<n> is the preference order and <className> is a subclass of the Provider class whose constructor sets the values of various properties that are required for the Java Security API to look up the algorithms or other facilities implemented by the provider).

In Corbett.java (available electronically), the constructor first calls super() and since the class is a subclass of Provider, an examination of the javadoc documentation for Provider reveals that the parameters are a unique provider name, version, and long name. The effect is to register version 1.0 of a provider whose name is "Corbett" and whose long name is "Provider for Dr. Dobb's Article." Following the call to super, the constructor invokes the put() method to map the key "Cipher.Enigma" to a package name. If you look back at Enigma.java (available electronically), you will find the package name in the first line of code.

To make the package available, create a JAR file (enigma.jar), containing all the classes. To verify that you have not missed anything, run the command jar -tv enigma.jar. It produces Example 2. To cause the Provider class in this package to be loaded when the Java Virtual Machine starts up, add the line security.provider.3=com.beechwood.crypto .provider.Corbett to the java.security file.

You can verify that things are okay up to this point by running the ListProviders program again. This time it should produce Example 3.

Putting it all Together

If you run the program EnigmaTest.java (Listing Five), the following activities will take place:

  • 1. As the Java Virtual Machine starts, it reads the statement security.provider.3=com.beechwood.crypto.provider.Corbett, which you added to the java.security file, and the provider is loaded.
  • 2. The method main() executes the statement: cipher =Cipher.getInstance ("Enigma", "Corbett"). The parameters are an algorithm ("Enigma") and a provider ("Corbett"). The getInstance() factory method determines whether the named provider has been registered. It finds the one that was registered by Corbett.java (available electronically). If the specified provider was not found, a NoSuchProvider exception would be thrown. Next, the factory method determines whether the provider has mapped the named algorithm using a key of the form "Cipher.Enigma." It finds the mapping created by Corbett.java and, based upon the mapping, it encapsulates an instance of Enigma and returns the Cipher object that contains this encapsulation.
  • 3. The program invokes methods on the Cipher object to initialize it, to create ciphertext by encrypting a message, to reinitialize the object for decryption and to decrypt the ciphertext to yield the original message.

Since this test program simply passes the encrypted message to itself for decryption, I did not address the issue of how to transmit the parameters required for decryption (that is, the passphrase, notch position, and start positions). This might be accomplished using authenticated Diffie-Hellman or a mechanism of your choice.

Because encrypted messages almost always contain unprintable characters that can cause strange side effects when included in e-mail, they are usually passed through a base64 encoder and decoded prior to use. A base64 encoder and decoder are available electronically.

Conclusion

The Java Provider Architecture facilitates seamless addition of algorithms, replacement of one version of an algorithm with another by simply changing the provider name, and provides a cafeteria approach to components for the applications programmer. Even though my discussion was limited to one cryptographic facility, you can apply the same technique to other facilities.

DDJ

Listing One

package com.beechwood.crypto.cipher;

</p>
import java.security.SecureRandom;
public class EnigmaRotor {
  
  private int notchIndex;
  private int startPosition = 0;
  private int currentIndex = 0;


</p>
  private int[] b = new int[256];
  private int[] f = new int[256];


</p>
  protected EnigmaRotor(long seed, int notchIndex) {
    this.notchIndex = notchIndex;
    int fx = 0;
    int bx;
    for (int i = 0; i < 256; ++i)
      f[i] = b[i] = -1;
    SecureRandom r = new SecureRandom();
    r.setSeed(seed);
    byte[] rb = new byte[1];
    for (int i = 0; i < 256; ++i) {
      r.nextBytes(rb);
      bx = rb[0] & 0xff;
      if (b[bx] < 0) {
        b[bx] = fx;
      }
      else {
        bx = (bx + 128) % 256;
        while (true) {
          if (bx > 255)
            bx = 0; 
          if (b[bx] < 0)
            break;
          bx++;
        }
        b[bx] = fx;
      }
      f[fx] = bx;
      fx++;
    }
  }


</p>
  protected void setStartingPosition(int startPosition) {
    this.startPosition = currentIndex = startPosition;
  }


</p>
  protected void advance() throws EnigmaRotorTrippedException {
    currentIndex++;
    if (currentIndex > 255) {
      currentIndex = 0;
    }
    if (currentIndex == notchIndex) {
      throw new EnigmaRotorTrippedException("notch at " + 
        notchIndex + " tripped");
    }
  }


</p>
  protected int processByte(int i, boolean forward) {
    int ri;
    int ix;
    if (forward) {
      ix = (i + currentIndex) % 256;
      ri = b[ix];
    }
    else {
      ix = i;
      ri = (f[ix] - currentIndex + 256) % 256;
    }
    return ri;
  }
}

Back to Article

Listing Two

package com.beechwood.crypto.cipher;

</p>
public class EnigmaRotorTrippedException extends Exception {


</p>
  protected EnigmaRotorTrippedException() {
    super();
  }


</p>
  protected EnigmaRotorTrippedException(String msg) {
    super(msg);
  }
}

Back to Article

Listing Three

package com.beechwood.crypto.cipher;

</p>
import java.security.SecureRandom;


</p>
public class EnigmaReflector {


</p>
  private int[] contacts = new int[256];


</p>
  protected EnigmaReflector(long seed) {
    byte[] rb = new byte[1];
    int[] mi = new int[256];
    for (int i = 0; i < 256; ++i)
      mi[i] = -1;
    SecureRandom r = new SecureRandom();
    r.setSeed(seed);
    int[] f = new int[2];
    for (int i = 0; i < 128; ++i) {
      for (int j = 0; j < 2; ++j) {
        r.nextBytes(rb);
        int ix  = rb[0] &0x3f;
        while (true) {
          if (mi[ix] < 0) {
            mi[ix] = 1;
            f[j] = ix;
            break;
          }
          ++ix;
          if (ix > 255) {
            ix = 0;
          }
        }
      }
      contacts[f[0]] = f[1];
      contacts[f[1]] = f[0];
    }
  }


</p>
  protected int reflect(int i) {
    return contacts[i];
  }
}

Back to Article

Listing Four

package com.beechwood.crypto.cipher;

</p>
public class EnigmaMachine {


</p>
  private EnigmaRotor[] rotors;
  private int rotorCount;
  private EnigmaReflector reflector;


</p>
  protected EnigmaMachine(EnigmaRotor[] rotors, EnigmaReflector ref) {
    this.rotors = rotors;
    rotorCount = rotors.length;
    reflector = ref;
  }


</p>
  protected void processMessage(byte[] in, int inOffset, 
      byte[] out, int outOffset, int len) {
    int ox = 0;
    for (int i = inOffset; i < len; ++i) {
      for (int rotorIndex = 0; rotorIndex < rotorCount; ++rotorIndex) {
        try {
          rotors[rotorIndex].advance();
          break;
        }
        catch (EnigmaRotorTrippedException erte) {
        }
      }
      int ic = ((int)in[i]) & 0xff;
      for (int k = 0; k < rotorCount; ++k) {
        ic = rotors[k].processByte(ic, true);
      }
      ic = reflector.reflect(ic);
      for (int k = rotorCount - 1; k >= 0; --k) {
        ic = rotors[k].processByte(ic, false);
      }
      out[ox++] = (byte)ic;
    }
  }
}

Back to Article

Listing Five

package com.beechwood.crypto.interfaces;

</p>
public abstract interface EnigmaParams {


</p>
  public int[] getNotchPositions();


</p>
  public int[] getStartPositions();


</p>
  public int getRotorCount();
}

Back to Article


Copyright © 1999, Dr. Dobb's Journal

Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.