Cryptography in Java

From JDK 1.1 onwards, Java provides general purpose APIs for cryptographic functions, collectively known as the Java Cryptography Architecture ( JCA) and Java Cryptography Extensions (JCE). Signed applets, which we will discuss in the next chapter, are one specialized use of the JCA capabilities.

In this chapter we describe the sort of problems for which cryptography can provide solutions and then look in more detail at JCA and JCE.

Security Questions, Cryptographic Answers

We want to create secure applications, but "secure" means different things depending on what the application does and the environment in which it operates. In each case we need to understand what the requirements are, based on the following categories:

Authentication How sure does the client need to be that the server really is who it claims to be? And does the server need to identify the client, or can he or she remain anonymous? Normally, authentication is based on either something you know (such as a password), or something you have (such as an encryption key or card). A developing form of authentication is based on something you are , including biometric measurements such as retinal scans or voice recognition.

Access control Having found out who is at the other end of the session, the next step is to decide whether they are allowed to do what they want to do.

Data integrity You want to be sure that data has not been altered between what was sent and what was received. This is especially true if the application crosses an insecure network, such as the Internet, where a man-in-the-middle attack may be easily mounted.

Confidentiality If any of the data that you are sending is sensitive, you do not want an attacker to be able to read it in transit. To prevent this it needs to be encrypted.

Non-repudiation In a business application you often have to be able to prove that a particular transaction took place.

If we measure applet sandbox security against these requirements we find that the only one it helps us with is access control. The control is very strict: the security manager says "I can't authenticate the server that delivered this applet, so I will allow it to only do safe things."

As we mentioned in See Cryptographic Tools in Brief , we have a trio of tools to answer the questions that these requirements pose, namely: symmetric key encryption, public key encryption and hashing/digital signatures.

Symmetric key, or bulk, encryption provides confidentiality, by making sure that a message can be read only if the recipient has the same key as the sender. But how to share the key in a secure manner? A common answer is to use public key encryption. This is too inefficient for general encryption of the whole data stream, but it is ideal for encrypting a small item, such as a bulk encryption key. The sender uses the receiver's public key to encrypt it, knowing that only the owner of the private half of the key pair, that is to say the receiver, will be able to decrypt it. Having secretly shared the bulk encryption key in this way, they can then use it to encrypt the real data that they want to keep private.

Digital signatures also use public key encryption, but the other way around. See Creating a Digital Signature illustrates how they work.

 

Creating a Digital Signature

The sender generates a digest from the data and then encrypts it with its private key. It then sends the result, together with the public key, along with the data. The receiver uses the public key to decrypt the signature and then performs the same hashing function on the data. If the digest obtained matches the result of the decryption, the receiver knows:

That the data has not been changed in transit (data integrity)
That it really was sent by the owner of the key pair (authentication)
Public Key Certificates

Whenever public key encryption is used, the owner of the key pair has to make the public key available to the session partner. But how can the session partner be sure of where the key really came from? The answer lies in public key certificates . Instead of sending a naked key, the owner sends a certificate, which is a message containing:

The whole message is digitally signed by a trusted third party , that is, an organization that is trusted by both sender and receiver (usually known as a Certificate Authority, or CA). The resulting certificate electronically ties the real identity of the user to the public key.

The international standard for public key certificates is called X.509. This has evolved over time and the latest version is V3. The most significant enhancement in X.509 V3 is the ability to add other, arbitrary, data in addition to the basic identity fields of the distinguished name. This is useful when constructing certificates for specific purposes (for example, a certificate could include a bank account number, or credit card information).

Certificate Hierarchies

A public key certificate can also embody a chain of trust. Consider the situation shown in See Certificate Hierarchy . A system has received a request containing a chain of certificates, each of which is signed by the next higher CA in the chain. The system also has a collection of root certificates from CAs that it views as trusted. It can match the top of the chain in the request with one of these root certificates ("Ham"). If the chain of signatures is intact, the receiver can infer that Nimrod is trustworthy and that it inherits its trustworthiness from Ham.

Certificate Hierarchy

Note that one of the implications of a certificate chain is that the certificate at the top of the chain is self-signed .

Introducing JCA: the Provider Concept

From the brief discussion above you can see that to use cryptographic solutions you may require a whole collection of tools and functions, not only the encryption algorithms themselves, but functions for message digests, certificate management and key generation. And of course, life would be too simple if there were only one way to do each of the functions. So, for example, there are two different message digest algorithms in common use, the MD5 algorithm from RSA and the US Government SHA standard.

The provider architecture of JCA aims to allow algorithm independence, by representing all functions of a given type by a generic engine class . This masks the idiosyncrasies of the algorithm behind standardized Java class behavior. Vendor independence is supported in the same way, by allowing any number of vendors to register their own implementations of the algorithms. See Vendor and Algorithm Independence illustrates how the provider architecture works in practice.

Vendor and Algorithm Independence

The figure shows two providers of cryptographic algorithms, Bob and Alice. These are in fact subclasses of the java.security.provider class. The acceptable algorithms are defined in engine classes . In JCA the only engine classes are related to digital signatures: creating the keys and digests needed for signing and then performing the signature itself. Bob and Alice both implement a number of algorithms that fall into these classes.

Now, let's assume that in your Java code you want to generate a key pair. You invoke the getInstance() method of the KeyPairGenerator engine class, passing it the specific type of key pair as an argument. The engine class reads the provider registration information from the java.security configuration file. This identifies the provider package names and assigns each one a preference order. In this case, the "Bob" provider package comes before "Alice" in the preference order. The engine class then searches through the providers until it finds an implementation of the algorithm required.

JDK1.1 offers one built-in provider package as standard, named SUN . This includes:

  • An implementation of the Digital Signature Algorithm (NIST FIPS 186)
  • An implementation of the MD5 (RFC 1321) and SHA-1 (NIST FIPS 180-1) message digest algorithms

It is worth noting here what is not contained in this package. The main omission is a facility for managing user IDs (more properly called principals in crypto-speak) and public key certificates. This makes the practical uses of the 1.1 package rather limited, as we show in an example using the SUN provider functions in See The Security Classes in Practice . JDK1.1 does include a set of tools for manipulating signed applets and these do provide management of principals, keys and certificates. We explore them in See JavaSoft Signed JAR Example .

JCE and Export Considerations

As we discussed in See US Export Rules for Encryption , JCA only provides for the digital signature part of the cryptographic spectrum. This allows us to perform reliable authentication which, in turn, can be used as a basis for implementing access controls that relax the sandbox restrictions. However, it does not provide the general purpose encryption needed to send confidential data.

The Java Cryptography Extension (JCE) package uses the same structure as JCA, being composed of engine classes that expose the algorithms in a generic way. The exact specification of the API is not openly published. This is because it is not only the JCE package itself that falls under the US export restrictions, but also the documentation for it.

What can be said about JCE is that it provides engine classes for bulk (symmetric key) encryption algorithms and for generating and manipulating the secret keys that such algorithms require.

The Security Classes in Practice

In this section we describe an example of the kind of application that JCA could be used for. We will illustrate it using snippets of code that use the APIs. In this way we aim to show, not only the useful features of JCA, but also the areas in which, at the JDK 1.1 level, it is lacking.

The Scenario

Imagine a home banking application, in which the customer, sitting in front of a browser in the comfort of his or her home, wishes to make a payment. Two things, at least, are required here:

The server (the bank) wants to authenticate the user, to make sure that it is not an imposter.
The customer will want to be sure that the bank is really who it claims to be.

We assume that the user will be authenticated by normal means: a PIN number or pass-phrase. Both client and server side are written in Java.

Step 1: Generate Keys and Certificates

Before the transaction can start, the bank must have generated a key pair and requested a certificate for it. The first part is simple:

try {

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

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

// Now generate a key pair

keypair = kg. generateKeyPair();

}

catch (NoSuchAlgorithmException e) {

System.err.println("No implementation of DSA keypair generator");

System.exit(1) ;

}

This instantiates the provider class for a DSA key pair and then generates it. Now it gets tricky. The server needs to use the same key pair each time it restarts, which means that it has to somehow save it securely in a file. There is no built-in facility for this, so the programmer would need to create a method to do it. Secondly, the server needs to generate an X.509 certificate request. JCA 1.1 defines an interface named Certificate, but there is no implementation of it in the SUN provider package.

Step 2: Challenge the Server

The client applet starts off the transaction by establishing a socket connection to the server using the Socket class from java.net (alternatively, it could use RMI). There may be some firewall considerations here, as discussed in See Firewalls: In and Out of the Net , but we assume the connection can get through.

Next, the browser generates a random array of bytes and sends it to the server. There are two types of algorithm for generating random numbers, true and pseudo . Pseudo random number generators are based on a seed, which means that they become predictable if you can predict the seed value. The standard JDK Random class is seeded from the system clock, so it is theoretically predictable, but in our case the predictability of the random data does not matter, so we can use it.

When the server receives the data, it signs it using the private key from the key pair generated earlier:

try {

siggi = Signature.getInstance("SHA/DSA");

siggi.initSign( keypair.getPrivate() );

// Pipe the string into a stream and sign it

StringReader sr = new StringReader(line) ;

byte b ;

while (( b = (byte) sr.read()) != -1) {

try {

siggi.update(b);

}

catch (SignatureException e) {

failmsg((Exception) e, "Problem performing the signature") ;

}

}

It then sends the signature, plus the certificate, to the client. It also generates and sends another piece of random data, this time challenging the client.

Client Accepts the Challenge

The client receives the data from the server and verifies the signature. The verification uses a standard method of the Signature class, but, as before, there is no way to handle the certificate using JDK 1.1 functions. Even if there was a way to handle a certificate, the browser sandbox would pose some problems, because the applet would need to check the signature against a trusted root CA, which implies reading the CA certificate from disk.

Finally, the client needs to prove his or her identity. The way to do this is to take the random data provided by the server, combine it with the PIN or pass-phrase, encrypt it using the public key from the server certificate and send it to the server. This, too, is not possible with JDK 1.1, because JCE has no general purpose public key encryption function.

What Do We Learn from This?

The scenario described above has shown that the facilities provided by JCA and JCE in JDK 1.1 are very limited. Future versions of the development kit will fill in the gaps.

The scenario also prompts another, more fundamental, question: challenge-based authentication is a common requirement; should there not be a common solution that implements it ? In other words, an application developer should be able to plug in code that performs the whole process, instead of designing the protocol from scratch and building it from basic components. This becomes more obvious when you start to consider the legal, contractual and practical implications of writing cryptographic code, for example:

Buying a package that implements a complete protocol does not remove these obligations, of course, but it does mean that they have already been considered and resolved.

IBM Packages for Cryptographic Protocols

IBM Research in Zurich has developed a complete cryptographic framework in Java, which handles most application requirements. For example, it includes classes for bulk-key and public-key encryption and for X.509v3 certificate management. This is compatible with JDK 1.1, but it uses its own provider framework (because it was built before JDK 1.1 became available).

IBM Zurich has built implementations of Secure Sockets Layer (SSL) as Java classes, based on this framework. SSL is a protocol that provides bulk data encryption with server and client authentication. We discuss it further in See Java and SSL . The Java crypto-framework has also been used by IBM Development in Hursley, UK, to create a package that is optimized for consumer transactions such as home banking, insurance and financial services.

The Consumer Transaction Framework (CTF) is a set of Java classes which are used by the sample programs. CTF provides a number of services such as menuing, user validation and a secure interface to the server so that the application developer need not be concerned with the infrastructure, but may concentrate on the end-user function. Furthermore the CTF package uses cryptography for specific, well-defined purposes, which means that IBM has been able to obtain an export license for the use of full strength (128-bit) encryption.