Java Cipher
Jakob Jenkov |
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.
Tweet | |
Jakob Jenkov |