Java Cipher

Jakob Jenkov
Last update: 2019-11-15

The Java Cipher (javax.crypto.Cipher) class represents an encryption algorithm. The term Cipher is standard term for an encryption algorithm in the world of cryptography. That is why the Java class is called Cipher and not e.g. Encrypter / Decrypter or something else.

You can use a Cipher instance to encrypt and decrypt data in Java. This Java Cipher tutorial will explain how the Cipher class of the Java Cryptography API works.

Creating a Cipher

Before you can use a Java Cipher you just create an instance of the Cipher class. You create a Cipher instance by calling its getInstance() method with a parameter telling what type of encryption algorithm you want to use. Here is an example of creating a Java Cipher instance:

Cipher cipher = Cipher.getInstance("AES");

This example creates a Cipher instance using the encryption algorithm called AES.

Cipher Modes

Some encryption algorithms can work in different modes. An encryption mode specifies details about how the algorithm should encrypt data. Thus, the encryption mode impacts part of the encryption algorithm.

The encryption modes can sometimes be used with multiple different encryption algorithms - like a technique that is appended to the core encryption algorithm. That is why the modes are thought of as separate from the encryption algorithms themselves, and rather "add-ons" to the encryption algorithms. Here are some of the most wellknown cipher modes:

  • ECB - Electronic Codebook
  • CBC - Cipher Block Chaining
  • CFB - Cipher Feedback
  • OFB - Output Feedback
  • CTR - Counter

When instantiating a cipher you can append its mode to the name of the encryption algorithm. For instance, to create an AES Cipher instance using Cipher Block Chaining (CBC) you use this code:

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

Since Cipher Block Chaining requires a "padding scheme" too, the padding scheme is appended in the end of the encryption algorithm name string.

Please keep in mind that not all encryption algorithms and modes are supported by the default Java SDK cryptography provider. You might need an external provider like Bouncy Castle installed to create your desired Cipher instance with the required mode and padding scheme.

Initializing a Cipher

Before you can use a Cipher instance you must initialize it. Initializing a Cipher is done by calling its init() method. The init() method takes two parameters:

  • Encryption / decryption cipher operation mode.
  • Encryption / decryption key.

Here is an example of initializing a Cipher instance in encryption mode:

Key key = ... // get / create symmetric encryption key
cipher.init(Cipher.ENCRYPT_MODE, key);

Here is an example of initializing a Cipher instance in decryption mode:

Key key = ... // get / create symmetric encryption key
cipher.init(Cipher.DECRYPT_MODE, key);

Encrypting and Decrypting Data

In order encrypt or decrypt data with a Cipher instance you call one of these two methods:

  • update()
  • doFinal()

There are several overridden versions of both update() and doFinal() which takes different parameters. I will cover the most commonly used versions here.

If you have to encrypt or decrypt a single block of data, just call the doFinal() with the data to encrypt or decrypt. Here is an encryption example:

byte[] plainText  = "abcdefghijklmnopqrstuvwxyz".getBytes("UTF-8");
byte[] cipherText = cipher.doFinal(plainText);

The code actually looks pretty much the same in case of decrypting data. Just keep in mind that the Cipher instance must be initialized into decryption mode. Here is how decrypting a single block of cipher text looks:

byte[] plainText = cipher.doFinal(cipherText);

If you have to encrypt or decrypt multiple blocks of data, e.g. multiple blocks from a large file, you call the update() once for each block of data, and finish with a call to doFinal() with the last data block. Here is an example of encrypting multiple blocks of data:

byte[] data1 = "abcdefghijklmnopqrstuvwxyz".getBytes("UTF-8");
byte[] data2 = "zyxwvutsrqponmlkjihgfedcba".getBytes("UTF-8");
byte[] data3 = "01234567890123456789012345".getBytes("UTF-8");

byte[] cipherText1 = cipher.update(data1);
byte[] cipherText2 = cipher.update(data2);
byte[] cipherText3 = cipher.doFinal(data3);

The reason a call to doFinal() is needed for the last block of data is, that some encryption algorithms need to pad the the data to fit a certain cipher block size (e.g. an 8 byte boundary). But - we do not want to pad the intermediate blocks of data encrypted. Hence the calls to update() for intermediate blocks of data, and the call to doFinal() for the last block of data.

When decrypting multiple blocks of data you also call the Cipher update() method for intermediate data blocks, and the doFinal() method for the last block. Here is an example of decrypting multiple blocks of data with a Java Cipher instance:

byte[] plainText1 = cipher.update(cipherText1);
byte[] plainText2 = cipher.update(cipherText2);
byte[] plainText3 = cipher.doFinal(cipherText3);

Again, the Cipher instance must be initialized into decryption mode for this example to work.

Encrypting / Decrypting Part of a Byte Array

The Java Cipher class encryption and decryption methods can encrypt or decrypt part of the data stored in a byte array. You simply pass an offset and length to the update() and / or doFinal() method. Here is an example:

int offset = 10;
int length = 24;
byte[] cipherText = cipher.doFinal(data, offset, length);

This example will encrypt (or decrypt, depending on the initialization of the Cipher) from byte with index 8 and 24 bytes forward.

Encrypting / Decrypting Into an Existing Byte Array

All the encryption and decryption examples shown in this tutorial so far have been returning the encrypted or decrypted data in a new byte array. However, it is also possible to encrypt or decrypt data into an existing byte array. This can be useful to keep the number of created byte arrays down.

You can encrypt or decrypt data into an existing byte array by passing the destination byte array as parameter to the update() and / or doFinal() method. Here is an example:

int offset = 10;
int length = 24;
byte[] dest = new byte[1024];
cipher.doFinal(data, offset, length, dest);

This example encrypts the data from the byte with index 10 and 24 bytes forward into the dest byte array from offset 0. If you want to set a different offset for the dest byte array there is a version of update() and doFinal() which takes an offset parameter extra. Here is an example of calling the doFinal() method with an offset into the dest array:

int offset = 10;
int length = 24;
byte[] dest = new byte[1024];
int destOffset = 12
cipher.doFinal(data, offset, length, dest, destOffset);

Reusing a Cipher Instance

Initializing a Java Cipher instance is an expensive operation. Therefore it is a good idea to reuse Cipher instances. Luckily, the Cipher class was designed with reuse in mind.

When you call the doFinal() method on a Cipher instance, the Cipher instance is returned to the state it had just after initialization. The Cipher instance can then be used to encrypt or decrypt more data again.

Here is an example of reusing a Java Cipher instance:

Cipher cipher = Cipher.getInstance("AES");

Key key = ... // get / create symmetric encryption key
cipher.init(Cipher.ENCRYPT_MODE, key);

byte[] data1 = "abcdefghijklmnopqrstuvwxyz".getBytes("UTF-8");
byte[] data2 = "zyxwvutsrqponmlkjihgfedcba".getBytes("UTF-8");

byte[] cipherText1 = cipher.update(data1);
byte[] cipherText2 = cipher.doFinal(data2);

byte[] data3 = "01234567890123456789012345".getBytes("UTF-8");
byte[] cipherText3 = cipher.doFinal(data3);

First the Cipher instance is created and initialized, and then used to encrypt two blocks of coherent data. Notice the call to update() and then doFinal() for these two blocks of data. Now the Cipher instance can be used again to encrypt more data. This is done with the doFinal() call with the third data block. After this doFinal() call you can encrypt yet another block of data with the same Java Cipher instance.

Jakob Jenkov

Featured Videos

Java ConcurrentMap + ConcurrentHashMap

Java Generics

Java ForkJoinPool

P2P Networks Introduction

















Close TOC
All Tutorial Trails
All Trails
Table of contents (TOC) for this tutorial trail
Trail TOC
Table of contents (TOC) for this tutorial
Page TOC
Previous tutorial in this tutorial trail
Previous
Next tutorial in this tutorial trail
Next