Farewell to Code Copy of Encryption and Decryption Module - JCE Core Cpiher Details

premise

javax.crypto.Cipher, translated as a password, is actually better known as a crypto. Cipher is the core of JCA(Java Cryptographic Extension), which provides encryption and decryption functions based on a variety of encryption and decryption algorithms. Before we know Cipher, we always need to copy code everywhere when we complete some modules that need encryption and decryption. Even some wrong usage is copied countless times. After stepping on the pit, we need to copy the code to repair the pit. Why not try to understand Cipher and use it reasonably?

Some knowledge supplements for Cipher initialization transformation

Conversion mode transformation generally consists of three parts: algorithm/mode/padding. For example: DES/CBC/PKCS5 Padding.

algorithm

Algorithms refer to the names of specific encryption and decryption algorithms in English string, such as "SHA-256", "RSA" and so on. Here we do not specifically expand the implementation principle of specific algorithms.

Working mode

The working mode is mainly for block ciphers. Block cipher is a sequence of digits (plaintext digits) encoded by plaintext messages, which is divided into groups with growth degree n (see the vector with growth degree n). Each group is converted into equal length output digits (ciphertext digits) sequence under the control of the key. The emergence of working mode is mainly based on the following reasons:

  • When the plaintext length needed to be encrypted is very large (such as file content), block encryption is required for hardware or performance reasons.
  • Using the same key multiple times to encrypt multiple packets can cause many security problems.

Essentially, working mode is a technology that enhances cryptographic algorithms or adapts them to specific applications, such as applying block ciphers to sequences or streams of data blocks. At present, it mainly includes the following five working modes defined by NIST:

Pattern Name describe Typical application
Electronic Codebook (ECB) Electronic CodeBook Independent Encryption of Clear Text Groups with the Same Key Secure transmission of individual data (e.g. an encryption key)
Password Block Link (CBC) Cipher Block Chaining The input of the encryption algorithm is XOR of the previous ciphertext combination and the next plaintext group. Packet-oriented Universal Transport or Authentication
Ciphertext Feedback (CFB) Cipher FeedBack The last ciphertext is used as the input of encryption algorithm, and the output of pseudo-random number is different from that of plaintext or ciphertext as the next unit. Packet-oriented Universal Transport or Authentication
Output Feedback (OFB) Output FeedBack Similar to CFB, only the input of the encryption algorithm is the output of the last encryption, and the whole block is used. Data stream transmission over noise channels (e.g. satellite communications)
Counter (CTR) Counter Each plaintext packet is different or different from an encrypted counter. Incremental for each subsequent grouping counter Packet-oriented general-purpose transmission or for high-speed requirements

The above five working modes can be used for any block cipher including 3DES and AES, and the choice of which one needs to be analyzed in combination with the actual situation.

Fill mode

Padding refers to: block encryption algorithm requires that the original data length is an integer multiple of the fixed block size. If the original data length is larger than the fixed block size, it needs to fill the data in the fixed block until the whole block is complete. For example, we agree that the length of the block is 128, but the length of the text to be encrypted is 129. Then we need to divide it into two blocks. The second block needs to be filled with 127-length data, and the filling mode decides how to fill the data.

Filling data in encryption and removing filling data in decryption are the key factors to be considered by both sides of communication. Filling in the original text is mainly based on the following reasons:

  • First, consider security. Because of the filling of the original data, the original text can "disguise" in the filled data, making it difficult for the attacker to find the real location of the original text.
  • Secondly, since block encryption algorithm requires that the length of the original data be an integer multiple of the fixed block size, if the original encrypted text does not satisfy this condition, it needs to fill the original data to an integer multiple of the fixed block size before encrypting.
  • In addition, padding also provides a standard form for senders and receivers to constrain the size of the encrypted source text. Only when both sides of encryption and decryption know how to fill, can they know how to remove the filled data accurately and decrypt it.

There are at least five common filling methods. Most of the filling methods used in encryption and decryption in different programming languages come from these methods or their variants. The following five filling modes are excerpted from the reference papers:

1. Filling data is the length of the sequence of filling bytes

In this filling method, the filling string consists of a sequence of bytes, each byte fills the length of the sequence of bytes. Assuming that the block length is 8 and the original data length is 9, the number of filled bytes is equal to 0x07; if the plaintext data length is an integer multiple of 8, the number of filled bytes is 0x08. Fill in the string as follows:

  • Original Data 1: FF FF FF FF FF FF
  • Filled data 1:FF FF FF FF FF FF FF FF 07 07 07 07 07 07 07 07 07 07 07 07 07 07 07 07 07 07
  • ==========================================================
  • Original Data 2: FF FF FF FF FF FF
  • Filled data 2: FF FF FF FF FF FF FF 08 08 08 08 08 08 08 08 08 08 08 08 08 08 08 08 08 08 08

2. Add 0x00 after filling the data with 0x80

In this filling method, the first byte of the filling string is 0x80, and each byte after it is 0x00. Assuming that the block length is 8 and the original data length is 9 or an integer multiple of 8, the filled string is as follows:

  • Original Data 1: FF FF FF FF FF FF
  • Filled data 1:FF FF FF FF FF FF 80 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  • ==========================================================
  • Original Data 2: FF FF FF FF FF FF
  • Filled data 2: FF FF FF FF FF FF 80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

3. The last byte of filling data is the length of the sequence of filling bytes.

In this filling method, the last byte of the filling string is the length of the sequence, while the previous byte can be 0x00 or a random sequence of bytes. Assuming that the block length is 8 and the original data length is 9 or an integer multiple of 8, the filled string is as follows:

  • Original Data 1: FF FF FF FF FF FF
  • Filled data 1: FF FF FF FF FF FF FF 00 00 00 000 07 or FF FF FF FF FF FF FF FF FF FF 0 A B0 0C 08 05 09 07
  • ===============================================================================
  • Original Data 2: FF FF FF FF FF FF
  • Filled data 2: FF FF FF FF FF FF 00 00 00 00 00 00 00 08 or FF FF FF FF FF FF FF 80 06 AB EA 03 02 01 08

4. Fill in blanks

In this filling method, each byte of the filling string is 0 x 20 corresponding to the number of bytes in the space. Assuming that the block length is 8 and the original data length is 9 or an integer multiple of 8, the filled string is as follows:

  • Original Data 1: FF FF FF FF FF FF
  • Filled data 1: FF FF FF FF FF FF FF 20 20 20 20 20 20 20 20 20 20
  • ===============================================================================
  • Original Data 2: FF FF FF FF FF FF
  • Filled data 2: FF FF FF FF FF FF 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20

5. Fill-in data is 0x00

In this padding method, each byte of the padding string is 0x00. Assuming that the block length is 8 and the original data length is 9 or 8 integers, the filled string is as follows:

  • Original Data 1: FF FF FF FF FF FF
  • Filled data 1: FF FF FF FF FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00 00
  • ===============================================================================
  • Original Data 2: FF FF FF FF FF FF
  • Filled data 2: FF FF FF FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

Summary of transformation

The details of Cipher supported by SunJCE Provider are as follows:

Algorithm Mode (working mode) Padding
AES EBC, CBC, PCBC, CTR, CTS, CFB, CFB8-CFB128, etc. NoPadding,ISO10126Padding,PKCS5Padding
AESWrap EBC NoPadding
ARCFOUR EBC NoPadding
Blowfish,DES,DESede,RC2 EBC, CBC, PCBC, CTR, CTS, CFB, CFB8-CFB128, etc. NoPadding,ISO10126Padding,PKCS5Padding
DESedeWrap CBC NoPadding
PBEWithMD5AndDES,PBEWithMD5AndTripleDES,PBEWithSHA1AndDESede,PBEWithSHA1AndRC2_40 CBC PKCS5Padding
RSA ECB,NONE NoPadding, PKCS1 Padding, etc.

Padding(Cipher) native Java support is summarized as follows:

Fill mode describe
NoPadding No Filling Mode
ISO10126Padding XML Encryption Syntax and Processing Documents are described in detail
OAEPPadding, OAEPWith<digest>And<mgf>Padding The optimal asymmetric encryption filling scheme defined in PKCS1 is digest for message digest type and mgf for mask generation function, such as OAEPWithMD5 AndMGF1 Padding or OAEPWithSHA-512AndMGF1 Padding.
PKCS1Padding PKCS1, RSA algorithm use
PKCS5Padding PKCS5, RSA algorithm use
SSL3Padding See the definition of SSL Protocol Version 3.0

Other Padding s need to be provided by third-party providers.

All public methods of Cipher

Seven Major Public Attributes of Cipher

  • 1. ENCRYPT_MODE, integer value 1, encryption mode for Cipher initialization.
  • 2. DECRYPT_MODE, integer value 2, decryption mode for Cipher initialization.
  • 3. WRAP_MODE, integer value 3, wrapper key mode for Cipher initialization.
  • 4. UNWRAP_MODE, integer value 4, unpackaged key mode for Cipher initialization.
  • 5. PUBLIC_KEY, integer value 1, specified key type as public key in unpackaged key mode.
  • 6. PRIVATE_KEY, integer value 2, the specified key type in the unpackaged key mode is the private key.
  • 7. SECRET_KEY, integer value 3, the key type specified in the unpackaged key mode is the key, which is mainly used for keys that are not asymmetrically encrypted (only one key, not including private key and public key).

The Main Method of Cipher

getInstance method

Cipher provides three static factory methods, getInstance, to build its example. The three methods are as follows:

public static final Cipher getInstance(String transformation)
                                throws NoSuchAlgorithmException,
                                       NoSuchPaddingException

public static final Cipher getInstance(String transformation,
                                       String provider)
                                throws NoSuchAlgorithmException,
                                       NoSuchProviderException,
                                       NoSuchPaddingException

public static final Cipher getInstance(String transformation,
                                       Provider provider)
                                throws NoSuchAlgorithmException,
                                       NoSuchPaddingException

Transform, which is called transformation (pattern) here, is the core parameter. See the analysis in the previous section. In addition, two factory methods require that the full name or instance of the java.security.Provider must be passed in, because Cipher needs to obtain the implementation of the specified transformation pattern from the corresponding provider. The first factory method only has a single parameter transformation, which matches the first service that satisfies the transformation from all existing java. security. Providers and instantiates CipherS from it. Pi (Understand that Cipher delegates specific encryption and decryption functions to an internally held CipherSpi instance). In fact, the initialization of Cipher instances must depend on transformation patterns and providers.

init method

There are eight variants of init method, which is mainly used to initialize Cipher.

//The additional parameter is Key (key)
public final void init(int opmode,
                       Key key)
                throws InvalidKeyException

//The additional parameters are Key (key) and SecureRandom (random source)
public final void init(int opmode,
                       Key key,
                       SecureRandom random)
                throws InvalidKeyException

//The additional parameters are Key (key) and AlgorithmParameterSpec (transparent definition of algorithm parameters)
public final void init(int opmode,
                       Key key,
                       AlgorithmParameterSpec params)
                throws InvalidKeyException,
                       InvalidAlgorithmParameterException 

//The additional parameters are Key, AlgorithmParameterSpec and SecureRandom.
public final void init(int opmode,
                       Key key,
                       AlgorithmParameterSpec params,
                       SecureRandom random)
                throws InvalidKeyException,
                       InvalidAlgorithmParameterException

//The additional parameters are Key and AlgorithmParameters.
public final void init(int opmode,
                       Key key,
                       AlgorithmParameters params)
                throws InvalidKeyException,
                       InvalidAlgorithmParameterException

//The additional parameters are Key (key), AlgorithmParameters (algorithm parameter), SecureRandom (random source).
public final void init(int opmode,
                       Key key,
                       AlgorithmParameters params,
                       SecureRandom random)
                    throws InvalidKeyException,
                       InvalidAlgorithmParameterException

//The additional parameter is Certificate (Certificate)
public final void init(int opmode,
                       Certificate certificate)
                throws InvalidKeyException

//The additional parameters are Certificate (Certificate), SecureRandom (Random Source)
public final void init(int opmode,
                       Certificate certificate,
                       SecureRandom random)
                throws InvalidKeyException

Opmode (operation mode) is a necessary parameter, and the optional values are ENCRYPT_MODE, DECRYPT_MODE, WRAP_MODE and UNWRAP_MODE. If the Key type parameter is not asymmetric encryption, the corresponding type is SecretKey. If it is asymmetric encryption, it can be PublicKey or PrivateKey. SecureRandom is a random source, because some algorithms need different encryption results each time. At this time, they need to depend on the system or the random source passed in. Some algorithms that require the same encryption and decryption results each time, such as AES, can not use this parameter. Certificate is a certificate implementation with a key. The algorithm parameters mainly include IV(initialization vector, initialization vector) and so on.

wrap method and unwrap method

The wrap method is used to wrap a key.

public final byte[] wrap(Key key)
                  throws IllegalBlockSizeException,
                         InvalidKeyException

When using the wrap method, it is important to note that Cipher's opmode is initialized to WRAP_MODE.

unwrap method is used to unpack a key.

public final Key unwrap(byte[] wrappedKey,
                        String wrappedKeyAlgorithm,
                        int wrappedKeyType)
                 throws InvalidKeyException,
                        NoSuchAlgorithmException

When using unwrap method, it is important to note that Cipher's opmode is initialized to UNWRAP_MODE. When calling unwrap method, you need to specify the algorithm and the type of Key that wrapped the key before.

In fact, wrap and unwrap are reciprocal operations:

  • The function of wrap method is to wrap the original key into the encrypted key through some encryption algorithm, so that the plaintext of the key can be avoided when the key is transferred.
  • The function of unwrap method is to decompose the encrypted key into the original key and get the plaintext of the key.
public enum EncryptUtils {

    /**
     * Single case
     */
    SINGLETON;

    private static final String SECRECT = "passwrod";

    public String wrap(String keyString) throws Exception {
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        //Initialize the key generator, specifying that the key length is 128, and that the seed of the random source is the specified key (here is "passward")
        keyGenerator.init(128, new SecureRandom(SECRECT.getBytes()));
        SecretKey secretKey = keyGenerator.generateKey();
        SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.WRAP_MODE, secretKeySpec);
        SecretKeySpec key = new SecretKeySpec(keyString.getBytes(), "AES");
        byte[] bytes = cipher.wrap(key);
        return Hex.encodeHexString(bytes);
    }

    public String unwrap(String keyString) throws Exception {
        byte[] rawKey = Hex.decodeHex(keyString);
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        //Initialize the key generator, specifying that the key length is 128, and that the seed of the random source is the specified key (here is "passward")
        keyGenerator.init(128, new SecureRandom(SECRECT.getBytes()));
        SecretKey secretKey = keyGenerator.generateKey();
        SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.UNWRAP_MODE, secretKeySpec);
        SecretKey key = (SecretKey) cipher.unwrap(rawKey, "AES", Cipher.SECRET_KEY);
        return new String(key.getEncoded());
    }

    public static void main(String[] args) throws Exception {
        String wrapKey = EncryptUtils.SINGLETON.wrap("doge");
        System.out.println(wrapKey);
        System.out.println(EncryptUtils.SINGLETON.unwrap(wrapKey));
    }
}

The example above is to wrap and unwrap keys through AES, call main method, and output:

77050742188d4b97a1d401db902b864d
doge

update method

There are many variants of update method, but their meanings are almost the same.

public final byte[] update(byte[] input)

public final byte[] update(byte[] input,
                           int inputOffset,
                           int inputLen)

public final int update(byte[] input,
                        int inputOffset,
                        int inputLen,
                        byte[] output)
                 throws ShortBufferException    

public final int update(ByteBuffer input,
                        ByteBuffer output)
                 throws ShortBufferException

The update method is mainly used for partial encryption or partial decryption. As for encryption or decryption, it depends on the opmode of Cipher initialization. Even though it has several variants, the routine is the same: depending on an input buffer (with data that needs to be encrypted or decrypted), the return value or parameter is an output buffer, and some additional parameters can control the data segment of the encryption or decryption operation by offset and length. After some encryption or decryption operations are completed, the Cipher#doFinal() method must be invoked to terminate the encryption or decryption operations.

doFinal method

There are also several variants of the doFinal method:

/**
 * End multi-part encryption or decryption operations.
 * This method needs to be called after the execution of the update call chain, and the result returned is part of the result of encryption or decryption.
 * Cipher is reset to the initialization state after the normal call to this method is completed.
 */
public final byte[] doFinal()
                     throws IllegalBlockSizeException,
                            BadPaddingException

/**
 * End multi-part encryption or decryption operations.
 * This method needs to be called after the update call chain has been executed, and the incoming output is used as part of the buffer to receive the encryption or decryption results.
 * Cipher is reset to the initialization state after the normal call to this method is completed.
 */
public final int doFinal(byte[] output,
                         int outputOffset)
                  throws IllegalBlockSizeException,
                         ShortBufferException,
                         BadPaddingException                         

/**
 * End a single-part encryption or decryption operation.
 * This method receives the complete message that needs to be encrypted or decrypted, and returns the processing result.
 * Cipher is reset to the initialization state after the normal call to this method is completed.
 */
public final byte[] doFinal(byte[] input)
                     throws IllegalBlockSizeException,
                            BadPaddingException

/**
 * End single-part or multi-part encryption or decryption operations.
 * The parameter inputOffset is the starting position of the byte array of the message to be encrypted and decrypted, and inputLen is the byte length to be encrypted or decrypted.
 * Cipher is reset to the initialization state after the normal call to this method is completed.
 */
public final byte[] doFinal(byte[] input,
                            int inputOffset,
                            int inputLen)
                     throws IllegalBlockSizeException,
                            BadPaddingException      

/**
 * End single-part or multi-part encryption or decryption operations.
 * The parameter inputOffset is the starting position of byte array of messages to be encrypted and decrypted, and inputLen is the byte length to be encrypted or decrypted, and output is used to receive the results of encryption and decryption.
 * Cipher is reset to the initialization state after the normal call to this method is completed.
 */
public final int doFinal(byte[] input,
                         int inputOffset,
                         int inputLen,
                         byte[] output)
                  throws ShortBufferException,
                         IllegalBlockSizeException,
                         BadPaddingException                                                         

/**
 * End single-part or multi-part encryption or decryption operations.
 * The parameter inputOffset is the starting position of byte array of messages to be encrypted and decrypted, and inputLen is the byte length to be encrypted or decrypted.
 * output To receive the results of encryption and decryption, output Offset is used to set the starting position of output.
 * Cipher is reset to the initialization state after the normal call to this method is completed.
 */
public final int doFinal(byte[] input,
                         int inputOffset,
                         int inputLen,
                         byte[] output,
                         int outputOffset)
                  throws ShortBufferException,
                         IllegalBlockSizeException,
                         BadPaddingException 
/**
 * End single-part or multi-part encryption or decryption operations.
 * The parameter input is the input buffer and output is the output buffer.
 * Cipher is reset to the initialization state after the normal call to this method is completed.
 */
public final int doFinal(ByteBuffer input,
                         ByteBuffer output)
                  throws ShortBufferException,
                         IllegalBlockSizeException,
                         BadPaddingException                                              

The main function of doFinal is to end single or multi-part encryption or decryption operations. Single-part encryption or decryption is suitable for the case that the length of the message to be processed is short and no block is needed. At this time, the byte[] doFinal(byte[] input) method can be used directly. Multi-part encryption or decryption is suitable for the case that the length of the message to be processed is long and needs to be partitioned. At this time, we need to call several update method variants to encrypt and decrypt part of the block, and finally call doFinal method variants to complete the partial encryption and decryption operation. For example, if the size of the processing block is 8 and the length of the message that needs to be encrypted is 23, then it needs to be encrypted in three blocks. The first two 8-length messages need to be partially encrypted by calling update. The results of partially encrypted messages can be obtained from the return value of update. The last 7-length message (which is usually filled in to block length 8) is encrypted by calling doFinal. End the whole part of the encryption operation. In addition, it is worth noting that as long as Cipher normally calls any doFinal variant method (no exception is thrown during the process), Cipher will be reset to the initialization state and can continue to use. This reusable feature can reduce the performance loss of creating Cipher instances.

Update ADD Method

First, ADD means Additional Authentication Data. Update ADD also has three variations:

public final void updateAAD(byte[] src)

public final void updateAAD(byte[] src,
                            int offset,
                            int len)

public final void updateAAD(ByteBuffer src)

Its method variants rely on only one input buffer with additional authentication data, which is commonly used in GCM or CCM encryption and decryption algorithms. If this method is used, its calls must precede Cipher's update and doFinal variant methods, which are easy to understand, and authentication must precede the actual encryption and decryption operations. At present, the data of updateADD is relatively small, the author has not yet practiced in the production environment to find that, so do not start the analysis.

Other methods

Other methods are mainly the Getter method, which is used to get information about Cipher.

  • public final Provider getProvider(): Get the provider of Cipher.
  • Public final String getAlgorithms (): Gets the name of the algorithm used by Cipher.
  • public final int getBlockSize(): In block encryption, each group has a fixed length, also known as a block. This method returns the size of the block in bytes.
  • public final int getOutputSize(int inputLen): Returns the output buffer length (in bytes) required to save the result of the next update or doFinal operation, based on the given input length inputLen (in bytes).
  • public final byte[] getIV(): Returns the byte array of the initialization vector in Cipher.
  • public final AlgorithmParameters getParameters(): Returns the algorithm parameters used by Cipher.
  • Public final Exemption Mechanism getExemption Mechanism (): Returns the exemption mechanism object used by Cipher.
  • public static final int getMaxAllowedKeyLength(String transformation): Returns the maximum key length of the specified conversion based on the JCE policy file installed.
  • public static final AlgorithmParameterSpec getMaxAllowedParameterSpec(String transformation): Returns the largest AlgorithmParameterSpec object under Cipher specified transformation according to the JCE policy file.

Cipher workflow

Here's a diagram to analyze Cipher's workflow in detail:

Of course, the above figure only analyses the use of Cipher, in fact, there is an important step is the key processing, but the key processing and the use of specific algorithms are related, so the figure does not reflect. Put another official description of the Cipher loading process:

The main processes include:

  • 1. Create a Cipher instance, which matches the first available CipherSpi instance from all providers in the platform according to transformation. The "algorithm/working mode/filling mode" must match perfectly to be selected.

    The default load Provider can be seen in the java.security file in ${JAVA_HONE}/jre/lib/security. If additional or self-implementing providers need to be added, they can be added through the static method addProvider of java.security.Security.

  • 2. Cipher is initialized by init method of Cipher instance. The main parameters are opmode and key.
  • 3. According to the way of initialization and whether grouping processing is needed, the appropriate method is selected for invocation.

Use of Cipher

In order to facilitate the use of Cipher, it is better to introduce apache-codec dependency first, which can simplify the operation of Hex, Base64 and so on.

<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.11</version>
</dependency>

In most cases, the values of elements in the encrypted byte array are not in the range of Unicode code code points. What they appear is random code. In fact, they are meaningful. Therefore, it is necessary to consider converting this byte array into non-random string for transmission. The common ways are Hex (binary to hexadecimal), Base64 and so on. In the following example, there is no uniform throw for exception types, so don't imitate it. Moreover, all strings converted into byte arrays have no specified character encoding, so they can only be processed in non-Chinese plaintext.

Encryption mode

In encryption mode, Cipher can only be used for encryption, mainly determined by opmode in init method. For instance:

public String encryptByAes(String content, String password) throws Exception {
    //The algorithm is AES_128, the working mode is EBC, and the filling mode is NoPadding.
    Cipher cipher = Cipher.getInstance("AES_128/ECB/NoPadding");
    KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
    //Because AES requires the length of the key to be 128, we need a fixed password, so the seeds of the random source need to be set to our cipher array.
    keyGenerator.init(128, new SecureRandom(password.getBytes()));
    SecretKey secretKey = keyGenerator.generateKey();
    SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), "AES");
    //Cipher initialization based on encryption mode and key
    cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
    //End of Single Section Encryption, Reset Cipher
    byte[] bytes = cipher.doFinal(content.getBytes());
    //Encrypted ciphertext is converted from binary sequence to hexadecimal sequence, which depends on apache-codec package
    return Hex.encodeHexString(bytes);
}

In fact, the whole process of Cipher use is very simple, but more complex is the process of key generation. The above example needs to be noted because the filling mode is NoPadding, and the length of the message to be encrypted must be a multiple of 16(128bit).

Decryption mode

The use of decryption mode is roughly the same as that of encryption mode.

public String decryptByAes(String content, String password) throws Exception {
    //Here, the hexadecimal sequence is converted back to the binary sequence, depending on the apache-codec package.
    byte[] bytes = Hex.decodeHex(content);
    //The algorithm is AES_128, the working mode is EBC, and the filling mode is NoPadding.
    Cipher cipher = Cipher.getInstance("AES_128/ECB/NoPadding");
    KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
    //Because AES requires the length of the key to be 128, we need a fixed password, so the seeds of the random source need to be set to our cipher array.
    keyGenerator.init(128, new SecureRandom(password.getBytes()));
    SecretKey secretKey = keyGenerator.generateKey();
    SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), "AES");
    //Cipher initialization based on decryption mode and key
    cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
    //End of Single Section Encryption, Reset Cipher
    byte[] result = cipher.doFinal(bytes);
    return new String(result);
}

The above example needs to be noted because the filling mode is NoPadding, and the length of the message to be encrypted must be a multiple of 16(128bit).

Packaging key mode and unpacking key mode

The key packaging and unpacking mode is a pair of reciprocal operations. Its main function is to encrypt and decrypt the key through the algorithm, so as to improve the difficulty of key leakage.

public enum EncryptUtils {

    /**
     * Single case
     */
    SINGLETON;

    private static final String SECRECT = "passwrod";

    public String wrap(String keyString) throws Exception {
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        //Initialize the key generator, specifying that the key length is 128, and that the seed of the random source is the specified key (here is "passward")
        keyGenerator.init(128, new SecureRandom(SECRECT.getBytes()));
        SecretKey secretKey = keyGenerator.generateKey();
        SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.WRAP_MODE, secretKeySpec);
        SecretKeySpec key = new SecretKeySpec(keyString.getBytes(), "AES");
        byte[] bytes = cipher.wrap(key);
        return Hex.encodeHexString(bytes);
    }

    public String unwrap(String keyString) throws Exception {
        byte[] rawKey = Hex.decodeHex(keyString);
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        //Initialize the key generator, specifying that the key length is 128, and that the seed of the random source is the specified key (here is "passward")
        keyGenerator.init(128, new SecureRandom(SECRECT.getBytes()));
        SecretKey secretKey = keyGenerator.generateKey();
        SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.UNWRAP_MODE, secretKeySpec);
        SecretKey key = (SecretKey) cipher.unwrap(rawKey, "AES", Cipher.SECRET_KEY);
        return new String(key.getEncoded());
    }

    public static void main(String[] args) throws Exception {
        String wrapKey = EncryptUtils.SINGLETON.wrap("doge");
        System.out.println(wrapKey);
        System.out.println(EncryptUtils.SINGLETON.unwrap(wrapKey));
    }
}

Packet (Part) Encryption and Packet Decryption

When a message needs to be encrypted for a very long time, we can consider cutting the message into several segments, and then encrypting each segment, which is called block encryption. The process of packet decryption is similar, which can be regarded as the reverse process of block encryption. The following is an example of AES algorithm:

import org.apache.commons.codec.binary.Hex;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;

/**
 * @author throwable
 * @version v1.0
 * @description
 * @since 2018/8/15 1:06
 */
public enum Part {

    /**
     * SINGLETON
     */
    SINGLETON;

    private static final String PASSWORD = "throwable";

    private Cipher createCipher() throws Exception {
        return Cipher.getInstance("AES");
    }

    public String encrypt(String content) throws Exception {
        Cipher cipher = createCipher();
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        //Because AES requires the length of the key to be 128, we need a fixed password, so the seeds of the random source need to be set to our cipher array.
        keyGenerator.init(128, new SecureRandom(PASSWORD.getBytes()));
        SecretKey secretKey = keyGenerator.generateKey();
        SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), "AES");
        //Cipher initialization based on encryption mode and key
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
        byte[] raw = content.getBytes();
        StringBuilder builder = new StringBuilder();
        //[0,9]
        byte[] first = cipher.update(raw, 0, 10);
        builder.append(Hex.encodeHexString(first));
        //[10,19]
        byte[] second = cipher.update(raw, 10, 10);
        builder.append(Hex.encodeHexString(second));
        //[20,25]
        byte[] third = cipher.update(raw, 20, 6);
        builder.append(Hex.encodeHexString(third));
        //When the multi-part encryption ends, the result of the last encryption is obtained, and Cipher is reset.
        byte[] bytes = cipher.doFinal();
        String last = Hex.encodeHexString(bytes);
        builder.append(last);
        return builder.toString();
    }

    public String decrypt(String content) throws Exception {
        byte[] raw = Hex.decodeHex(content);
        Cipher cipher = createCipher();
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        //Because AES requires the length of the key to be 128, we need a fixed password, so the seeds of the random source need to be set to our cipher array.
        keyGenerator.init(128, new SecureRandom(PASSWORD.getBytes()));
        SecretKey secretKey = keyGenerator.generateKey();
        SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), "AES");
        //Cipher initialization based on decryption mode and key
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
        StringBuilder builder = new StringBuilder();
        //[0,14]
        byte[] first = cipher.update(raw, 0, 15);
        builder.append(new String(first));
        //[15,29]
        byte[] second = cipher.update(raw, 15, 15);
        builder.append(new String(second));
        //[30,31]
        byte[] third = cipher.update(raw, 30, 2);
        builder.append(new String(third));
        //The multi-part decryption ends, the result of the last decryption is obtained, and the Cipher is reset.
        byte[] bytes = cipher.doFinal();
        builder.append(new String(bytes));
        return builder.toString();
    }

    public static void main(String[] args) throws Exception{
        String raw = "abcdefghijklmnopqrstyuwxyz";
        String e = Part.SINGLETON.encrypt(raw);
        System.out.println(e);
        System.out.println(Part.SINGLETON.decrypt(e));
    }
}

The above sub-paragraph subscript has been given in the annotation. The rule of sub-section is considered by the actual situation. Generally, when the AES encryption and decryption message is small, it can be directly encrypted and decrypted in a single part. This is just for demonstration.

View all Cipher providers in the current JDK

We can look directly at all the Cipher providers and supporting encryption and decryption services in the JDK currently in use and simply write a main function:

import java.security.Provider;
import java.security.Security;
import java.util.Set;

public class Main {

    public static void main(String[] args) throws Exception {
        Provider[] providers = Security.getProviders();
        if (null != providers) {
            for (Provider provider : providers) {
                Set<Provider.Service> services = provider.getServices();
                for (Provider.Service service : services) {
                    if ("Cipher".equals(service.getType())) {
                        System.out.println(String.format("provider:%s,type:%s,algorithm:%s", service.getProvider(), service.getType(), service.getAlgorithm()));
                    }
                }
            }
        }
    }
}

The JDK used by the author is the last updated version of JDK 8, 8u181(1.8.0_181). The output of main function is as follows:

provider:SunJCE version 1.8,type:Cipher,algorithm:RSA
provider:SunJCE version 1.8,type:Cipher,algorithm:DES
provider:SunJCE version 1.8,type:Cipher,algorithm:DESede
provider:SunJCE version 1.8,type:Cipher,algorithm:DESedeWrap
provider:SunJCE version 1.8,type:Cipher,algorithm:PBEWithMD5AndDES
provider:SunJCE version 1.8,type:Cipher,algorithm:PBEWithMD5AndTripleDES
provider:SunJCE version 1.8,type:Cipher,algorithm:PBEWithSHA1AndDESede
provider:SunJCE version 1.8,type:Cipher,algorithm:PBEWithSHA1AndRC2_40
provider:SunJCE version 1.8,type:Cipher,algorithm:PBEWithSHA1AndRC2_128
.....Too much output ignores the rest

extend

Because Java's native support for transformation s is limited, sometimes we need to use some algorithms that other working modes or fill modes can't be supported by Java's native support. At this time, we need to introduce third-party Provider or even implement Provider by ourselves. The most common third-party provider is bouncycastle(BC), which currently relies on:

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.60</version>
</dependency>

For example, Java native does not support AESWRAP algorithm, so BC dependency can be introduced and conversion mode AESWRAP can be used.

import org.apache.commons.codec.binary.Hex;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.security.Security;

public enum EncryptUtils {

    /**
     * SINGLETON
     */
    SINGLETON;

    private static final String SECRET = "throwable";
    private static final String CHARSET = "UTF-8";

    //Loading BC Provider
    static {
        Security.addProvider(new BouncyCastleProvider());
    }


    private Cipher createAesCipher() throws Exception {
        return Cipher.getInstance("AESWRAP");
    }

    public String encryptByAes(String raw) throws Exception {
        Cipher aesCipher = createAesCipher();
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AESWRAP");
        keyGenerator.init(128, new SecureRandom(SECRET.getBytes(CHARSET)));
        SecretKey secretKey = keyGenerator.generateKey();
        SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), "AESWRAP");
        aesCipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
        byte[] bytes = aesCipher.doFinal(raw.getBytes(CHARSET));
        return Hex.encodeHexString(bytes);
    }

    public String decryptByAes(String raw) throws Exception {
        byte[] bytes = Hex.decodeHex(raw);
        Cipher aesCipher = createAesCipher();
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AESWRAP");
        keyGenerator.init(128, new SecureRandom(SECRET.getBytes(CHARSET)));
        SecretKey secretKey = keyGenerator.generateKey();
        SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), "AESWRAP");
        aesCipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
        return new String(aesCipher.doFinal(bytes), CHARSET);
    }

    public static void main(String[] args) throws Exception {
        String raw = "throwable-a-doge";
        String en = EncryptUtils.SINGLETON.encryptByAes(raw);
        System.out.println(en);
        String de = EncryptUtils.SINGLETON.decryptByAes(en);
        System.out.println(de);
    }
}

The above example needs to be noted, because the AESWRAP algorithm is used, the length of the message to be encrypted must be 8 times.

Summary

After mastering the usage of Cipher and some knowledge of transformation of transformation mode, the main factor that affects our coding of encryption and decryption module is the principle or use of encryption and decryption algorithm, which requires us to learn the relevant knowledge of special encryption and decryption algorithm. In addition, sometimes we find that different platforms or different languages can not use the same encryption algorithm to decrypt and encrypt each other. In fact, the reason is very simple. Most of the reasons are due to the different selection of working mode or filling mode. Excluding these two points, the remaining possibility is that the implementation of the algorithm is different, according to these three factors (or transformation). The only factor is to judge and find solutions. We can refer to the following information about the principles of encryption and decryption algorithms, working modes and other related knowledge.

Reference material:

  • Cryptocoding and Network Security - Principles and Practice (6th Edition)
  • Principles and Practice of Information Security (2nd Edition)
  • Research on Filling Ways of Encrypted Data
  • JavaSE8 API documentation

In addition, some special methods, such as Ciper updateADD, have not encountered a usage scenario for the time being, so there is no Demo that has not been practiced here. The next article will introduce the basic principles of some mainstream encryption and decryption algorithms and their application through Cipher.

(c-7-d)

Keywords: Java codec Apache JDK

Added by saint4 on Fri, 17 May 2019 00:54:10 +0300