An Introduction to Cryptography and the Java Cryptography Extension

An Introduction to Cryptography and the Java Cryptography Extension

By Brian R. Gilstrap, OCI Principal Software Engineer 

December 2003


The Java Cryptography Extension (JCE) provides APIs for performing cryptographic operations in Java code. To understand what this means, it is useful to define what we mean by cryptography.

cryp·tog·ra·phy n.
    1) The process or skill of communicating in or deciphering secret writings or ciphers.
    2) Secret writing.

    The American Heritage® Dictionary of the English Language, Fourth Edition 

Essentially, the JCE lets us:

We'll discuss these in more detail later, but first we need to introduce some basic terminology used when doing cryptography, understand how the JCE relates to other Java security APIs, and get an overview of the JCE's architecture.

Introduction to Cryptographic Terms

In order to understand the JCE, it is necessary to understand the basics of cryptography. This leads to an obvious question:

What does cryptography encompass?

Perhaps the most important areas that cryptography encompasses are:

There are other aspects to cryptography beyond the scope of this article, but these are some of the most central.

There is also some terminology that is common in the cryptographic community that makes it easier to discuss. The most important terms are:

Encryption and Decryption

Encryption and decryption are what most people associate with cryptography. This is about scrambling something you don't want unauthorized people to read and then later recovering the scrambled information in its original form.

Encryption and decryption are done using a particular algorithm. There are many encryption and decryption algorithms and they have different strengths and weaknesses.

One of the challenges of cryptography for the non-cryptographer is understanding which algorithm is best suited to the situation at hand. With so many options to choose from, and high level math required to truly understand those options, it can be hard to know which algorithm is most appropriate to use.

Symmetric versus Asymmetric algorithms

The process of encrypting and decrypting data almost always uses a single symmetric key or a pair of asymmetric keys.

A symmetric key can be used to both encrypt or decrypt information. Examples of symmetric key algorithms include DES, Rijndael, AES, triple-DES, and Blowfish.

Unlike symmetric keys, asymmetric keys come in pairs. Probably the best known asymmetric key algorithm is RSA. Its two keys are called the public key and the private key. Either key in the pair can be used either to encrypt or decrypt a given cleartext. If information is encrypted with one of the keys, the other key is required to decrypt it.

An advantage of RSA is that one key can be kept private and the other made available to the general public (hence the names of the keys). This eliminates a common problem with symmetric keys: if two people want to encrypt and decrypt information they send to each other, how do they exchange the secret key without anyone else getting it? In addition, symmetric keys suffer from the problem that anyone who has the key can encrypt and decrypt information. This makes it hard to know who has done the encrypting or decrypting when more than one person has the key.

One interesting characteristic of RSA is that it is limited in how much data it can encrypt or decrypt. Essentially, it cannot encrypt or decrypt more information than the size of its keys. Because of this, RSA is usually combined with a symmetric algorithm to support the encryption and decryption of larger amounts of data (since RSA key sizes are relatively small compared to documents or messages exchanged). By having the public/private keys encrypt/decrypt the relatively small secret key and by using the secret key to encrypt and decrypt the actual cleartext, RSA becomes practical for use with larger cleartexts. Digital certificates, which build upon public/private keys, were created to facilitate the reliable exchange of public keys between correspondents.

Figure 1
The Complexities of encryption and decryption

In addition to the difference between symmetric and asymmetric algorithms, many algorithms can be configured in different ways (for example, AES and RSA support keys of varying lengths). There are also various 'modes' in which algorithm can operate and various means of padding cleartexts which are not a multiple of the bit size required by the algorithm. Finally, because some algorithms need to get 'jumpstarted' they may require some initialization. This initialization involves parameters specifying exactly how the encryption/decryption is to be done and/or may require initialization information (called an initialization vector).

Message Authentication Codes (MACs)

A Message Authentication Code (MAC) is an algorithm applied to some cleartext which produces a large number. This large number is of a fixed size and is usually represented as an array of bytes. The number is referred to as the hash result or the message digest.

In some ways, a MAC is similar to a checksum, except it doesn't simply result in the same number for the same document input. Because they are based upon one-way hashing, MACs have some useful properties beyond checksums:

A true MAC is cryptographically secure, meaning that you can know the MAC used in every detail and still find it very hard or impossible to break one of the 'rules' listed above. Sometimes a MAC is called a digital fingerprint because it produces a small, essentially unique number representing the original cleartext.

Digital Certificates and Signatures

A digital certificate is essentially the public key of an asymmetric algorithm (like RSA) combined with some identifying information to specify the owner of the private key. As long as the owner of the certificate keeps the private key to him or herself, he or she can 'sign' data with the private key, and anyone who possesses the digital certificate can verify that the data was signed by that person/program.

Digital certificates still have issues that must be resolved to use them effectively, like how to know that a particular certificate from your friend Mary is really from her. There are essentially two ways to verify this:

  1. Contact Mary and make sure the certificate you have is hers
  2. Have someone else that both you and Mary trust (called a certifying authority (CA)) sign her certificate

If the second approach is used, Mary arranges with the CA to validate her identity and have her certificate signed. Then, she can send that certificate to anyone who trusts the CA, and those people can verify her certificate by validating the CA's signature.

The area of digital certificates is vast, including many issues relating to the administration of certificates, such as:

These issues are beyond the scope of this article.

How JCE relates to other Java Security APIs

The JCE is the standard mechanism for performing cryptographic activities within the Java environment. This includes activities such as encrypting and decrypting data, signing documents or data with a digital certificate, and verifying someone else's digital signature. To better understand the JCE, it is useful to place it within the context of other security-related APIs in Java.

Much of the first security work in Java focused on securing the Java Virtual Machine (JVM) itself. This work is focused on preventing malicious or poorly-designed code from harming a user's computer or data. It includes things like security managers, the API for privileged blocks, policies for controlling what Java code is allowed to do, etc. At its heart, these APIs are about managing the security of the JVM as it relates to the code which is executing.

As time went by and Java was used in the broader business environment, there was a need to authenticate and authorize multiple users within the Java environment (in an EJB container, for example). Multi-user environments like EJB containers need to verify the identity of users interacting with them in order to make authorization decisions based upon the user's identity. To replace vendor-specific solutions to this problem, the Java Authentication and Authorization Service (JAAS) APIs were created.

In parallel with the development of JAAS, The Java Secure Sockets Extension (JSSE) and the Java GSS-API (Generic Security Services Application Programming Interface) were also created. These APIs are focused primarily upon providing secure communication to Java programs. Both provide the means to create secure communication channels, though the GSS-API adds some additional features such as management of single-sign-on tokens.

The JCE complements these other security APIs by providing the means to perform general cryptographic activities in Java. While these other technologies may or may not directly use the JCE, they are conceptually built upon the same cryptographic techniques that the JCE makes available to developers.

JCE Architecture

The JCE was designed to provide a generic, vendor-independent set of APIs. Different vendors can then write an implementation of those APIs called a provider. Different vendors offer providers with different feature sets, performance characteristics, and costs.

There are two providers that come with the JCE, which offer a number of useful features.

Figure 2

Developers can plug in different providers (even plugging in more than one at the same time) and use features of each.

One down side of this provider-based approach is that the JCE APIs are made somewhat more complex by their use of the factory pattern (we'll see the effects in an example later). In addition, there are differences between different kinds of cryptographic algorithms that introduce some complexities (such as algorithm parameters and initialization vectors, which we'll discuss later). However, because of this plug-in architecture, developers who are careful to avoid vendor-specific code can replace one provider with another quite easily.

In addition, the architecture makes it easier to separate the specialized work of creating cryptographic implementations (performed by the implementors of the provider) from the more general work of using cryptography (performed by developers using the JCE).

A note about cryptographic restrictions

Cryptographic software is controlled in the United States by the Federal government. Because of this, applications exported to certain countries are limited in the strength of the cryptography they can include. For most developers, this is not an issue, but there are mechanisms to make applications exempt from the restrictions if you find your application needs to be shipped to a restricted country.

API Organization

The generic APIs that the JCE provides are defined in three packages:

In addition to these packages, the javax.crypto classes and interfaces build upon existing classes and interfaces in the packages that start with

Core classes and interfaces

The JCE core classes and interfaces are:

There are many other classes and interfaces in the packages, but these represent the core classes. Of these, the most fundamental is probably the Cipher class, since most cryptography involves some form of encryption and decryption (even digital signatures require the signer of a cleartext to encrypt the MAC for the document with their private key, so those verifying the document can decrypt the MAC with the certificate’s public key and compare it against an independently generated MAC).

An Example

To give a sense for using the JCE, we will provide a pair of simple example programs, one of which encrypts some data and the other which decrypts the data. The two programs share some common information in the form of an interface with some constants, named Constants:

  1. import;
  2. public interface Constants {
  3. public static final byte[] DATA = "This is a test".getBytes();
  5. public static final File DATA_FILE = new File( "" );
  6. public static final File KEY_FILE = new File( "" );
  7. }

Encrypting the data is perhaps a bit more involved than we might expect. Part of the complexity is due to the JCE's provider architecture and the factory pattern it employs.

  1. /**
  2.  * A simple example of encrypting some data
  3.  */
  4. public class EncryptionExample implements Constants {
  6. public static void main(String[] args) throws Exception {
  8. // Create the secret/symmetric key
  9. KeyGenerator kgen = KeyGenerator.getInstance("Blowfish");
  10. SecretKey skey = kgen.generateKey();
  11. byte[] raw = skey.getEncoded();
  12. SecretKeySpec skeySpec = new SecretKeySpec(raw, "Blowfish");
  14. // Create the cipher for encrypting
  15. Cipher cipher = Cipher.getInstance("Blowfish");
  16. cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
  18. // Encrypt the data
  19. byte[] encrypted = cipher.doFinal( DATA );
  21. // Save the encrypted data
  22. FileOutputStream fos = new FileOutputStream( DATA_FILE );
  23. fos.write( encrypted );
  24. fos.close();
  26. // Save the cipher settings
  27. byte[] encodedKeySpec = skeySpec.getEncoded();
  28. FileOutputStream eksos = new FileOutputStream( KEY_FILE );
  29. eksos.write( encodedKeySpec );
  30. eksos.close();
  31. }
  32. }

Decrypting the data involves essentially reversing the process of encryption. It is important to note that successfully decrypting the data requires that we have the same key as was used to encrypt it (which the encrypting program saves to the KEY_FILE in this example).

  1. import javax.crypto.Cipher;
  2. import javax.crypto.spec.SecretKeySpec;
  3. import;
  5. /**
  6.  * A sample program which decrypts the data encrypted with the sample encryption
  7.  * program and makes sure it decrypted successfully.
  8.  */
  9. public class DecryptionExample implements Constants {
  10. public static void main(String[] args) throws Exception {
  12. // Read the encrypted data
  13. FileInputStream fis = new FileInputStream(DATA_FILE);
  14. byte[] temp = new byte[ DATA_FILE.length()];
  15. int bytesRead =;
  16. byte[] data = new byte[bytesRead];
  17. System.arraycopy(temp, 0, data, 0, bytesRead);
  20. // Read the cipher settings
  21. FileInputStream eksis = new FileInputStream( KEY_FILE );
  22. bytesRead =;
  23. byte[] encodedKeySpec = new byte[bytesRead];
  24. System.arraycopy(temp, 0, encodedKeySpec, 0, bytesRead);
  26. // Recreate the secret/symmetric key
  27. SecretKeySpec skeySpec = new SecretKeySpec( encodedKeySpec, "Blowfish");
  29. // Create the cipher for encrypting
  30. Cipher cipher = Cipher.getInstance("Blowfish");
  31. cipher.init(Cipher.DECRYPT_MODE, skeySpec);
  33. // Decrypt the data
  34. byte[] decrypted = cipher.doFinal(data);
  36. // Validate successful decryption
  37. for (int i = 0; i < decrypted.length; i++) {
  38. if ( decrypted[ i ] != DATA[ i ] ) {
  39. System.err.println( "Decrypted data wrong at byte " + i + "!" );
  40. System.exit( 1 );
  41. }
  42. }
  43. System.err.println( "Success!" );
  44. }
  45. }


It is probably obvious from this article that cryptography is a vast topic. As a result, the JCE is quite large and has many features. However, because of its organization, a developer can get started using the JCE fairly easily.

In this article we have just scratched the surface of cryptography and the JCE. There are many interesting problems cryptography can help solve, and many ways to use the JCE. It is also important to consider the situation at hand and the degree of security required. Depending upon the situation, one of the other security-related APIs may be more appropriate than diving straight into raw cryptography using the JCE, or perhaps JCE can be combined with other APIs to solve the problem.

Most importantly, we must realize that security only works when it is end-to-end, meaning all steps in a process must be secured. For example, no matter how good the algorithm used to encrypt data on disk, if that data is transmitted across an unsecured network the algorithm for encrypting on disk may not matter.


Software Engineering Tech Trends (SETT) is a regular publication featuring emerging trends in software engineering.