Cryptography in Java. Class cipher
Hello, Habr! I present to you the translation of the second article "Java Cipher" by Jakob Jenkov from a series of articles for beginners who want to learn the basics of cryptography in Java.
Table of contents:
- Cryptography
- Cipher
- Messagedigest
- Mac
- Signature
- Keypair
- Keygenerator
- KeyPairGenerator
- Keystore
- Keytool
- Certificate
- CertificateFactory
- CertPath
Java Cipher (Cipher)
The Java Cipher class ( javax.crypto.Cipher ) is an encryption algorithm. The term "Cipher" is a standard term for an encryption algorithm in the world of cryptography. This is why the Java class is called the Cipher , and not the Encryptor / Decryptor or something else. You can use a Cipher instance to encrypt and decrypt data in Java. This chapter explains how the Cipher class works.
Cipher Creation
Before using a cipher, you must create an instance of the Cipher class by calling its getInstance () method with a parameter indicating which 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 an instance of Cipher using the AES encryption algorithm.
Encryption modes
Some encryption algorithms may work in different modes. The encryption mode determines the details of how the data will be encrypted. Thus, the encryption mode partially affects the encryption algorithm. Encryption modes can sometimes be used in several different encryption algorithms - as a method that is added to the main encryption algorithm. That is why modes are considered separately from the encryption algorithms themselves, but rather as “additions” to encryption algorithms. Here are some of the best known encryption modes:
- EBC - Electronic Codebook (Electronic Codebook Mode )
- CBC - Cipher Block Chaining ( clutch mode ciphertext blocks )
- CFB - Cipher Feedback (Cipher Feedback Mode )
- OFB - Output Feedback ( feedback mode of exit )
- CTR - Counter (Counter Mode )
When creating an instance of a cipher, you can add its mode to the name of the encryption algorithm. To create an instance of AES Cipher using the block coupling mode - Cipher Block Chaining (CBC), you can do this:
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
Since the coupling mode of the encryption blocks also requires a “padding scheme”, the padding scheme ( PKCS5Padding ) is added to the end of the string of the encryption algorithm name.
It is important to know that not all encryption algorithms and modes are supported by default by the Java SDK encryption provider. To create the cipher instance you need with the required mode and fill pattern, you may need to install a third-party provider, such as Bouncy Castle.
Cipher Initialization
Before using a Cipher instance, it must be initialized. The cipher is initialized by calling its init () method . The init () method takes two parameters:
- Mode
- Key
An example of initializing a Cipher instance in encryption mode:
Key key = ... // получить/создать симметричный ключ шифрования
cipher.init(Cipher.ENCRYPT_MODE, key);
And here is an example of initializing a Cipher instance already in decryption mode:
Key key = ... //получить/создать симметричный ключ шифрования
cipher.init(Cipher.DECRYPT_MODE, key);
Data Encryption and Decryption
To encrypt or decrypt data using a Cipher instance, one of these two methods is called:
update()
doFinal()
There are several overridden versions of the update () and doFinal () methods that take different parameters. Consider the most commonly used here. If you need to encrypt or decrypt one block of data, simply call doFinal () with the data to encrypt or decrypt. Example:
byte[] plainText = "abcdefghijklmnopqrstuvwxyz".getBytes("UTF-8");
byte[] cipherText = cipher.doFinal(plainText);
In fact, the code looks approximately the same in the case of data decryption. Just remember that the Cipher instance must be initialized in decryption mode. Here's what the decryption of one block of ciphertext looks like:
byte[] plainText = cipher.doFinal(cipherText);
If you need to encrypt or decrypt a large file divided into several blocks, update () is called once for each data block and ends with a call to the doFinal () method with the last data block. Here is an example of encrypting multiple data blocks:
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 that the doFinal () call is needed for the last block of data is because some encryption algorithms need to complement the data to fit a specific cipher block size (for example, an 8-byte boundary). It is not necessary to supplement intermediate encrypted data. Therefore, the update () method is called for the intermediate data blocks and the doFinal () call for the last data block is called.
When decrypting multiple data blocks, you also call the update () method for intermediate data blocks and the doFinal () method for the last block. Example of decryption of several data blocks:
byte[] plainText1 = cipher.update(cipherText1);
byte[] plainText2 = cipher.update(cipherText2);
byte[] plainText3 = cipher.doFinal(cipherText3);
Again, the cipher instance must be initialized in decryption mode for this example to work.
Encryption / Decryption of a part of a byte array
Encryption and decryption methods of the Cipher class can encrypt or decrypt some of the data stored in a byte array. The update () and / or doFinal () method needs to pass the offset and length.
int offset = 10;
int length = 24;
byte[] cipherText = cipher.doFinal(data, offset, length);
In this example, bytes from index 10 and forward 24 bytes will be encrypted (or decrypted, depending on the initialization of the cipher).
Encryption / Decryption to existing byte array
All encryption and decryption examples in this chapter return 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 reduce the number of byte arrays created. To do this, pass the target byte array as a parameter to the update () and / or doFinal () method .
int offset = 10;
int length = 24;
byte[] dest = new byte[1024];
cipher.doFinal(data, offset, length, dest);
В этом примере данные шифруются с 10 индекса на 24 байта вперед в байтовый массив dest со смещением 0. Если вы хотите установить другое смещение для байтового массива dest, существуют версии update() и doFinal(), которые принимают дополнительный параметр смещения. Пример вызова метода doFinal() со смещением в массиве dest:
int offset = 10;
int length = 24;
byte[] dest = new byte[1024];
int destOffset = 12
cipher.doFinal(data, offset, length, dest, destOffset);
Повторное использование экземпляра шифра
Инициализация экземпляра Cipher — дорогостоящая операция и хорошей идеей будет повторное использование экземпляров Cipher. К счастью, класс Cipher был разработан с учетом возможности повторного использования. Когда вы вызываете метод doFinal() для экземпляра Cipher, он возвращается в состояние, в котором находился сразу после инициализации. Экземпляр Cipher может затем использоваться для шифрования или дешифрования большего количества данных.
Пример повторного использования экземпляра Java Cipher:
Cipher cipher = Cipher.getInstance("AES");
Key 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, a Cipher instance is created and initialized, and then used to encrypt two blocks of consistent data. Notice the call to update () and then doFinal () for these two data blocks. After that, the Cipher instance can be used again to encrypt the data. This is done by calling doFinal () with a third data block. After this call to doFinal (), you can encrypt another block of data with the same Java Cipher instance.