In Java we trust

Find out how the Java Security API can help you secure your applications

Trust everyone? Trust no one? Sounds a bit like the X-Files, but when it comes to confidential information, knowing who it is you're trusting is as important as knowing what you trust them with. This concept is as important for applications as it is for people. After all, we've made applications the custodians of our information and the stewards of our resources. It's true across the enterprise -- applications hold critical information about our business and our customers -- and it's true on the desktop. I can't tell you how many times I've been asked how to write an applet that scans a user's drive so that one user can commandeer another user's browser or capture private information.

Java, being the network development platform that it is, has had to tackle the problem of trust head on. The result is the Java Security API and the Java Cryptography Architecture.

A brief glance backward

Before I dive headlong into APIs, code, and commentary, I'd like to briefly revisit last month's discussion. If you're joining us for the first time, you might want to back up a month and read " Signed and delivered: An introduction to security and authentication ." This column provides a thorough introduction to all the terms and concepts I'll be using this month.

Security and authentication address two crucial concerns: that of proving a message was created by a particular entity, and that of proving a message wasn't tampered with after it was created. One way of meeting both of these goals is by the use of digital signatures.

Digital signatures depend heavily on a branch of cryptography known as public-key cryptography. Public-key algorithms are characterized by the fact that they rely on a matched pair of keys (one private and one public) rather than a single key. An entity keeps its private key secret, but makes its public key available.

A digital signature algorithm takes as input a message and an entity's private key, and generates a digital signature. The digital signature is created in such a way that anyone can take the entity's public key and use it to verify that the entity in fact signed the message in question. Furthermore, if the original message has been tampered with, the signature can no longer be verified. Digital signatures provide one additional benefit: once an entity has signed and distributed a message, it's impossible for its originator to deny having signed the message (without claiming his or her private key was stolen, anyway).

Of engines and providers

The Java Cryptography API defines the Java toolkit for security and authentication. The Java Cryptography Architecture (JCA) describes how to use the API. To ensure the highest degree of flexibility for both the developer and the end user, the JCA embraces two guiding principles:

  1. The architecture should support algorithm independence and extensibility. A developer must be able to write applications without tying them too closely to a particular algorithm. In addition, as new algorithms are developed, they must be easily integrated with existing algorithms.

  2. The architecture should support implementation independence and interoperability. A developer must be able to write applications without tying them to a particular vendor's implementation of an algorithm. In addition, implementations of an algorithm provided by different vendors must interoperate.

To satisfy these two requirements, the developers of the Java Cryptography API based their design on a system of engines and providers.

Engines produce instances of message-digest generators, digital-signature generators, and key-pair generators. Each instance is used to carry out its corresponding function.

The canonical engine in the JCA is a class that provides a static method (or methods) named getInstance(), which returns an instance of a class that implements a cryptographically significant algorithm. The getInstance() method comes in both a one-argument and a two-argument form. In both cases, the first argument is the name of the algorithm. The JCA provides a list of standard names, though not all will be provided in any particular release. The second argument selects a provider.

The SUN provider

Only one provider -- SUN -- is supplied in JDK 1.1. SUN provides both an implementation of the NIST Digital Signature Algorithm (DSA), and an implementation of the MD5 and NIST SHA-1 message digest algorithms.

Class MessageDigest

We'll begin by looking at code that generates a message digest from a message.

MessageDigest messagedigest = MessageDigest.getInstance("SHA");

MessageDigest messagedigest = MessageDigest.getInstance("SHA", "SUN");

As I mentioned just a moment ago, the getInstance() method comes in two flavors. The first requires only the algorithm to be specified. The second requires both the algorithm and the provider to be specified. Both return an instance of a class that implements the SHA algorithm.

Next, we pass the message through the message-digest generator.

int n = 0; byte [] rgb = new byte [1000]; while ((n = inputstreamMessage.read(rgb)) > -1) { messagedigest.update(rgb, 0, n); }

Here, we assume the message is available as an input stream. This code works well for large messages of unknown length. The update() method also accepts a single byte as an argument for messages of a few bytes in length, and a byte array for messages of a fixed or predictable size.

rgb = messagedigest.digest();

The final step involves generating the message digest itself. The resulting digest is encoded in an array of bytes.

As you can see, the JCA conveniently hides all the low-level implementation and algorithm-specific details, allowing you to work at a higher, more abstract level.

Of course, one of the risks of such an abstract approach is the increased likelihood that we won't recognize erroneous output resulting from bugs. Given the role of cryptography, this can be a significant problem.

Consider the "off-by-one" bug in the update line below:

int n = 0; byte [] rgb = new byte [1000]; while ((n = inputstreamMessage.read(rgb)) > -1) { messagedigest.update(rgb, 0, n - 1); }

C, C++, and Java programmers use the limit-minus-one idiom so frequently that typing it becomes almost automatic -- even when it's not appropriate. The code above will compile, and the executable will run without error or warning, but the resulting message digest will be wrong.

Luckily, the JCA is well thought out and well designed, making potential pitfalls like the one above relatively rare.

Before we move on to key-pair generators, take a look at

MessageDigestGenerator, the complete source code for a program that generates a message digest.

Class KeyPairGenerator

To generate a digital signature (and encrypt data), we need keys.

Key generation, in its algorithm-independent form, is not substantially more difficult than creating and using a message digest.

KeyPairGenerator keypairgenerator = KeyPairGenerator.getInstance("DSA");

As in the message digest example above, this code creates an instance of a class that generates DSA-compatible keys. A second (if necessary) argument specifies the provider.

After a key-pair generator instance is created, it must be initialized. We can initialize key-pair generators in one of two ways: algorithm-independent or algorithm-dependent. Which method you use depends on the amount of control you want over the final result.

keypairgenerator.initialize(1024, new SecureRandom());

Keys based on different algorithms differ in how they're generated, but they have one parameter in common -- the key's strength. Strength is a relative term that corresponds roughly to how hard the key will be to "break." If you use the algorithm-independent initializer, you can specify only the strength -- any algorithm-dependent values assume reasonable defaults.

DSAKeyPairGenerator dsakeypairgenerator = (DSAKeyPairGenerator) keypairgenerator; DSAParams dsaparams = new DSAParams() { private BigInteger p = BigInteger(...); private BigInteger q = BigInteger(...); private BigInteger g = BigInteger(...); public BigInteger getP() { return p; } public BigInteger getQ() { return q; } public BigInteger getG() { return g; } }; dsakeypairgenerator.initialize(dsaparams, new SecureRandom());

While the defaults are usually good enough, if you need more control, it is available. Let's assume you used the engine to create a generator of DSA-compatible keys, as in the code above. Behind the scenes, the engine loaded and instantiated an instance of a class that implements the DSAKeyPairGenerator interface. If we cast the generic key-pair generator we received to DSAKeyPairGenerator, we then gain access to the algorithm-dependent method of initialization.

To initialize a DSA key-pair generator, we need three values: the prime P, the subprime Q, and the base G. These values are captured in an inner class instance that is passed to the initialize() method.

The SecureRandom class provides a secure source of random numbers used in the key-pair generation.

return keypairgenerator.generateKeyPair();

The final step involves generating the key pair itself.

Before we move on to digital signatures, take a look at KeyTools, the complete source code for a program that generates a key pair.

Class Signature

The creation and use of an instance of the Signature class is not substantially different from either of the two previous examples. The differences lie in how the instance is used -- either to sign or to verify a message.

Signature signature = Signature.getInstance("DSA");

Just as before, we use the engine to get an instance of the appropriate type. What we do next depends on whether or not we are signing or verifying a message.

signature.initSign(privatekey);

In order to sign a message, we must first initialize the signature instance with the private key of the entity that is signing the message.

signature.initVerify(publickey);

In order to verify a message, we must initialize the signature instance with the public key of the entity that claims it signed the message.

int n = 0; byte [] rgb = new byte [1000]; while ((n = inputstreamMessage.read(rgb)) > -1) { signature.update(rgb, 0, n); }

Next, regardless of whether or not we are signing or verifying, we must pass the message through the signature generator. You'll notice how similar the process is to the earlier example of generating a message digest.

The final step consists of generating the signature or verifying a signature.

rgb = signature.sign();

If we are signing a message, the sign() method returns the signature.

signature.verify(rgbSignature);

If we are verifying the signature previously generated from a message, we must use the verify() method. It takes as a parameter the previously generated signature and determines whether or not it is still valid.

Before we wrap things up, take a look at Sign.java, the complete source code for a program that signs a message, and Verify.java, the complete source code for a program that verifies a message.

Conclusion

If you arm yourself with the tools and techniques I've presented this month, you'll be more than ready to secure your applications. The Java Cryptography API makes the process almost effortless. Release 1.2 of the Java Developers Kit promises even more. Stay tuned.

Next month I'll head back into middleware territory. I'm going to take a little RMI, some threading, and a heap of code, and show you how to build your own message-oriented middleware.

Todd Sundsted has been writing programs since computers became available in convenient desktop models. Though originally interested in building distributed object applications in C++, Todd moved on to the Java programming language when it became the obvious choice for that sort of thing. In addition to writing, Todd is president of Etcee which offers training, mentoring, consulting, and software development services.

Learn more about this topic

  • Download the complete source code http://www.javaworld.com/jw-01-1999/howto/jw-01-howto.zip
  • Java Security API Overview http://www.javasoft.com/products/jdk/1.1/docs/guide/security/JavaSecurityOverview.html
  • Java Cryptography Architecture http://www.javasoft.com/products/jdk/1.1/docs/guide/security/CryptoSpec.html
  • Sun's Java Security Page http://java.sun.com/security/index.html
  • RSA's FAQ on Cryptography http://www.rsa.com/rsalabs/faq/
  • Cryptographic Policy and Information http://www.crypto.com/
  • Read Todd's previous How-To Java columns http://www.javaworld.com/topicalindex/jw-ti-howto.html