Because I use SSL for my project, I use the Cipher class, which is described in the JDK documentation as:

This class provides the functionality of a cryptographic cipher for encryption and decryption. It forms the core of the Java Cryptographic Extension (JCE) framework.

This class provides the ability to encrypt passwords for encryption and decryption. It forms the core of the Java Cryptographic Extension (JCE) framework.

The description and usage are the same. The encryption and decryption function is obtained from the encryption type of the keystore or certificate.

Curious, I decided to take a look at the source code to learn more about RSA encryption.

The first is the encrypted Demo of this class:


    /** * Maximum encryption size */
    private static final MAX_ENCRYPT_BLOCK = 117;

    public byte[] encryptByPrivateKey(byte[] data) throws Exception {
        // Obtain the private key object based on the keystore information
        PrivateKey privateKey = getPrivateKey(keyStorePath, alias, password);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        int inputLen = data.length;
        int offSet = 0;
        int i = 0;
        byte[] cache;
        while (inputLen - offSet > 0) {
            if (intputLen - offSet > MAX_ENCRYPT_BLOCK) {
                cache = cipler.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
            } else {
                cache = cipler.doFinal(data, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * MAX_ENCRYPT_BLOCK;
        }
        return out.toByteArray();
    }
Copy the code

From the above code, it can be seen that in this Demo, Cipher is called in the following order:

  1. Initialize encryption and decryption by standard key name.
  2. Initialize the key. Set the state of encryption or decryption and the corresponding key.
  3. Encrypt by byte array and return encrypted result.

Therefore, this paper will discuss the process and implementation of data encryption by Cipher in this order.

Example Initialize the encryption and decryption function

The first call is the getInstance(String Transformation) method, which is described in the documentation as:

Returns a Cipher object that implements the specified transformation.

Returns a Cipher object that implements the specified transformation.

For the same purpose as the program, then go inside the method to view the source code:

    public static final Cipher getInstance(String var0) throws NoSuchAlgorithmException, NoSuchPaddingException {
        List var1 = getTransforms(var0);
        ArrayList var2 = new ArrayList(var1.size());
        Iterator var3 = var1.iterator();

        while(var3.hasNext()) {
            Cipher.Transform var4 = (Cipher.Transform)var3.next();
            var2.add(new ServiceId("Cipher", var4.transform));
        }

        List var11 = GetInstance.getServices(var2);
        Iterator var12 = var11.iterator();
        Exception var5 = null;

        while(true) {
            Service var6;
            Cipher.Transform var7;
            int var8;
            do {
                do {
                    do {
                        if(! var12.hasNext()) {throw new NoSuchAlgorithmException("Cannot find any provider supporting " + var0, var5);
                        }

                        var6 = (Service)var12.next();
                    } while(! JceSecurity.canUseProvider(var6.getProvider())); var7 = getTransform(var6, var1); }while(var7 == null);

                var8 = var7.supportsModePadding(var6);
            } while(var8 == 0);

            if (var8 == 2) {
                return new Cipher((CipherSpi)null, var6, var12, var0, var1);
            }

            try {
                CipherSpi var9 = (CipherSpi)var6.newInstance((Object)null);
                var7.setModePadding(var9);
                return new Cipher(var9, var6, var12, var0, var1);
            } catch(Exception var10) { var5 = var10; }}}Copy the code

It’s a big method, and I’m going to look at the code pattern here.

The first is

    List var1 = getTransforms(var0);
    ArrayList var2 = new ArrayList(var1.size());
    Iterator var3 = var1.iterator();
Copy the code

These three lines are actually operations on the encryption method passed in, where var0 is the encryption type we passed in. The tricky one is the first line’s getTransforms(var0) method, which can be guessable from its return value, which takes a String and returns a List. Typically, this method splits strings. Before doing so, make sure we pass in the argument var0 = “RSA”.

Since this method is irrelevant to the topic of this article, I will go through it directly: this method returns an immutable list of objects of type SingletonList that store encryption types and related information.

In this example there is only one class that stores the relevant encryption information.

And then the

    while(var3.hasNext()) {
        Cipher.Transform var4 = (Cipher.Transform)var3.next();
        var2.add(new ServiceId("Cipher", var4.transform));
    }
Copy the code

Run var3 to obtain the cipher. Transform encryption class, save its encryption information in ServiceId, and add it to var2. The message is “RSA”.

Then there is

    List var11 = GetInstance.getServices(var2);
    Iterator var12 = var11.iterator();
    Exception var5 = null;
Copy the code

In my understanding, the function of this method is: pass in the key type to get the corresponding encryption and decryption Service List, where the saved element type is Service. The class is described as follows in the source code:

The description of a security service. It encapsulates the properties of a service and contains a factory method to obtain new implementation instances of this service.

Description of the security service. It encapsulates the properties of the service and contains a factory method to get a new implementation instance of the service.

Var11 contains the attributes of the encryption and decryption service.

Then there is a while(true) loop, which reads by function because there is a lot of code:

The first is:

    Service var6;
    Cipher.Transform var7;
    int var8;
Copy the code

This is an initialization process, nothing to say.

And then the

    do {
        do {
            do {
                if(! var12.hasNext()) {throw new NoSuchAlgorithmException("Cannot find any provider supporting " + var0, var5);
                }

                var6 = (Service)var12.next();
            } while(! JceSecurity.canUseProvider(var6.getProvider())); var7 = getTransform(var6, var1); }while(var7 == null);

        var8 = var7.supportsModePadding(var6);
    } while(var8 == 0);
Copy the code

This is a traversal of the service and the encryption method, as described in the method:

This method traverses the list of registered security Providers, starting with the most preferred Provider. A new Cipher object encapsulating the CipherSpi implementation from the first Provider that supports the specified algorithm is returned.

This method iterates through the list of registered security providers, starting with the most preferred provider. A new Cipher object is returned that encapsulates the CipherSpi implementation from the first Provider that supports the specified algorithm.

Iterate through the list of provided services and get the service for the parameter and then get its support mode fill, which is the supportsModePadding method. This method retrieves the properties of the provided cipher. Transform object to obtain the corresponding schema. The pattern code returned in this example is 2. Var8 = 2.

then

    if (var8 == 2) {
        return new Cipher((CipherSpi)null, var6, var12, var0, var1);
    }
Copy the code

When the state is 2, we can see from the above that we satisfy this statement and therefore return a new Cipher object. According to the above information, the parameters are unknown, service object, service list, key name, cipher. Transform class that stores key name information.

Initialize key

As you can see from the above, the Cipher object returned by getInstance has only keys and key-related services. It is not known whether to encrypt or decrypt, so initialization is important.

You can see that by clicking on this method

    public final void init(int var1, Key var2) throws InvalidKeyException {
        this.init(var1, var2, JceSecurity.RANDOM);
    }
Copy the code

The final concrete method body is


    public final void init(int var1, Key var2, SecureRandom var3) throws InvalidKeyException {
        this.initialized = false;
        checkOpmode(var1);
        if (this.spi ! =null) {
            this.checkCryptoPerm(this.spi, var2);
            this.spi.engineInit(var1, var2, var3);
        } else {
            try {
                this.chooseProvider(1, var1, var2, (AlgorithmParameterSpec)null, (AlgorithmParameters)null, var3);
            } catch (InvalidAlgorithmParameterException var5) {
                throw newInvalidKeyException(var5); }}this.initialized = true;
        this.opmode = var1;
        if(! skipDebug && pdebug ! =null) {
            pdebug.println("Cipher." + this.transformation + "" + getOpmodeString(var1) + " algorithm from: " + this.provider.getName()); }}Copy the code

Let’s start at the very beginning

    this.init(var1, var2, JceSecurity.RANDOM);
Copy the code

RANDOM = jcesecurity. RANDOM = jcesecurity. RANDOM = jcesecurity. RANDOM = jcesecurity. RANDOM = jcesecurity. RANDOM = jcesecurity. RANDOM = jcesecurity. RANDOM = jcesecurity. RANDOM

Constructs a secure random number generator (RNG) implementing the default random number algorithm.

Construct a secure random number generator (RNG) that implements the default random number algorithm.

It turned out to be a random number generator. So look at the code below

    this.initialized = false;
    checkOpmode(var1);
Copy the code

These are the initialization conditions and determine whether the incoming pattern is correct.

And then the

    if (this.spi ! =null) {
        this.checkCryptoPerm(this.spi, var2);
        this.spi.engineInit(var1, var2, var3);
    } else {
        try {
            this.chooseProvider(1, var1, var2, (AlgorithmParameterSpec)null, (AlgorithmParameters)null, var3);
        } catch (InvalidAlgorithmParameterException var5) {
            throw newInvalidKeyException(var5); }}Copy the code

Determine if the incoming CipherSpi exists. I’m just going to read it in the conditions of this Demo, which is that CipherSpi doesn’t exist. So the code that executes is

    try {
        this.chooseProvider(1, var1, var2, (AlgorithmParameterSpec)null, (AlgorithmParameters)null, var3);
    } catch (InvalidAlgorithmParameterException var5) {
        throw new InvalidKeyException(var5);
    }
Copy the code

The parameters are: 1, encryption mode: 1, private key object, NULL, NULL, secure random number generator. The chooseProvider method has a lot of code so let’s look at the next steps

    this.initialized = true;
    this.opmode = var1;
Copy the code

Here is the assignment of related attributes, where Initialized means that initialization is complete, and opmode means that the enabled mode is encryption mode (1).

Then we look at the body of the chooseProvider method

    private void chooseProvider(int var1, int var2, Key var3, AlgorithmParameterSpec var4, AlgorithmParameters var5, SecureRandom var6) throws InvalidKeyException, InvalidAlgorithmParameterException {
        Object var7 = this.lock;
        synchronized(this.lock) {
            if (this.spi ! =null) {
                this.implInit(this.spi, var1, var2, var3, var4, var5, var6);
            } else {
                Exception var8 = null;

                while(true) {
                    Service var9;
                    CipherSpi var10;
                    Cipher.Transform var11;
                    do {
                        do {
                            do {
                                do {
                                    if (this.firstService == null&&!this.serviceIterator.hasNext()) {
                                        if (var8 instanceof InvalidKeyException) {
                                            throw (InvalidKeyException)var8;
                                        }

                                        if (var8 instanceof InvalidAlgorithmParameterException) {
                                            throw (InvalidAlgorithmParameterException)var8;
                                        }

                                        if (var8 instanceof RuntimeException) {
                                            throw(RuntimeException)var8; } String var16 = var3 ! =null ? var3.getClass().getName() : "(null)";
                                        throw new InvalidKeyException("No installed provider supports this key: " + var16, var8);
                                    }

                                    if (this.firstService ! =null) {
                                        var9 = this.firstService;
                                        var10 = this.firstSpi;
                                        this.firstService = null;
                                        this.firstSpi = null;
                                    } else {
                                        var9 = (Service)this.serviceIterator.next();
                                        var10 = null; }}while(! var9.supportsParameter(var3)); }while(! JceSecurity.canUseProvider(var9.getProvider())); var11 = getTransform(var9,this.transforms);
                        } while(var11 == null);
                    } while(var11.supportsModePadding(var9) == 0);

                    try {
                        if (var10 == null) {
                            var10 = (CipherSpi)var9.newInstance((Object)null);
                        }

                        var11.setModePadding(var10);
                        this.initCryptoPermission();
                        this.implInit(var10, var1, var2, var3, var4, var5, var6);
                        this.provider = var9.getProvider();
                        this.spi = var10;
                        this.firstService = null;
                        this.serviceIterator = null;
                        this.transforms = null;
                        return;
                    } catch (Exception var14) {
                        if (var8 == null) {
                            var8 = var14;
                        }
                    }
                }
            }
        }
    }
Copy the code

There’s still a lot of code, so I read it by function

The first is

Object var7 = this.lock;
Copy the code

You can tell from the parameter name that this is used as a security lock for concurrent execution. But in this Demo this parameter is null.

And then the

synchronized(this.lock) {
    if (this.spi ! =null) {
        this.implInit(this.spi, var1, var2, var3, var4, var5, var6);
    } else{... }}Copy the code

This indicates that the operation is atomic and cannot be executed by multiple threads. Else code is entered according to the conditions of Demo.

Then there is

Exception var8 = null;
Copy the code

A thread pool is defined here. External atomic operations, internal thread pools, why would an initialization operation involve multiple threads? To continue down

while(true) { Service var9; CipherSpi var10; Cipher.Transform var11; . }Copy the code

Again, this is defining parameters. There are Service, CipherSpi, and key information cipher.transform. The values of the three parameters are: RSA service, NULL and RSA information.

The next code is nested with multiple do-while statements, and here I read from the inside out, starting with

if (this.firstService == null&&!this.serviceIterator.hasNext()) {
    ...
}
Copy the code

This condition can only be satisfied if there is no corresponding service. In this Demo, there is obviously a service, so skip it.

And then the

if (this.firstService ! =null) {
    var9 = this.firstService;
    var10 = this.firstSpi;
    this.firstService = null;
    this.firstSpi = null;
} else {
    var9 = (Service)this.serviceIterator.next();
    var10 = null;
}
Copy the code

Assign the preceding parameters (var9, var10) when a service exists, and empty the original parameters. So var9 currently has a service and var10 is null. And then the judgment of this while is zero

while(! var9.supportsParameter(var3));Copy the code

As can be seen directly from the method name, the service exits when it supports the key.

The second function name is canUseProvider. Exit when the service is available. then

var11 = getTransform(var9, this.transforms);
Copy the code

Obtain the key corresponding to the service in the key list according to the service and key information and assign the key to VAR11. So far, the values of the three parameters (VAR9, VAR10, var11) are consistent with my previous guess. Then the subsequent while judgment is to obtain the key information corresponding to the service.

Then look at the try-catch statement

if (var10 == null) {
    var10 = (CipherSpi)var9.newInstance((Object)null);
}
Copy the code

If VAR19 (CipherSpi) is NULL, a new instance is returned through the service. The method is described as

Return a new instance of the implementation described by this service. The security provider framework uses this method to construct implementations. Applications will typically not need to call it.

Returns a new instance of the implementation of this service description. The security provider framework uses this method to construct the implementation. Applications usually do not need to invoke it.

The CipherSpi class is described in the documentation as

This class defines the Service Provider Interface (SPI) for the Cipher class. All the abstract methods in this class must be implemented by each cryptographic service provider who wishes to supply the implementation of a particular cipher algorithm.

This class defines the service provider interface (SPI) for the Cipher class. All abstract methods in this class must be implemented by each cryptographic service provider that wants to provide a specific cryptographic algorithm implementation.

There is a more specific description of this later

A transformation is a string that describes the operation (or set of operations) to be performed on the given input, to produce some output. A transformation always includes the name of a cryptographic algorithm (e.g., AES), and may be followed by a feedback mode and padding scheme.

A transformation is a string that describes the operations (or set of operations) to be performed on a given input to produce some output. Transformations always include the name of the encryption algorithm (for example, AES) and can follow feedback patterns and fill schemes.

That is, this class is the one that actually operates on the data. It provides the relevant encryption and decryption interface for the corresponding service.

The last

    var11.setModePadding(var10);
    this.initCryptoPermission();
    this.implInit(var10, var1, var2, var3, var4, var5, var6);
    this.provider = var9.getProvider();
    this.spi = var10;
    this.firstService = null;
    this.serviceIterator = null;
    this.transforms = null;
    return;
Copy the code

Load the interface into the service, initialize the encryption permission, determine whether the service provider interface is paired with the keystore and load the encryption mode (1), key and secure random number generator into the SPI. And set related parameters. And the null original parameter.

Encrypt by byte array

First look at the method body

public final byte[] doFinal(byte[] var1, int var2, int var3) throws IllegalBlockSizeException, BadPaddingException {
    this.checkCipherState();
    if(var1 ! =null && var2 >= 0 && var3 <= var1.length - var2 && var3 >= 0) {
        this.chooseFirstProvider();
        return this.spi.engineDoFinal(var1, var2, var3);
    } else {
        throw new IllegalArgumentException("Bad arguments"); }}Copy the code

The first is

this.checkCipherState();
Copy the code

Determines the status and throws an exception if it is not encrypted or decrypted

And then the

    if(var1 ! =null && var2 >= 0 && var3 <= var1.length - var2 && var3 >= 0) {
        this.chooseFirstProvider();
        return this.spi.engineDoFinal(var1, var2, var3);
    } else {
        throw new IllegalArgumentException("Bad arguments");
    }
Copy the code

Here’s a chooseFirstProvider method to select the first service provider, which can be skipped because it already exists.

Finally, it returns SPI’s encrypted data. Here I finally confirm that the data is being processed in SPI.

So let’s go to the engineDoFinal method and see what happens

SPI-engineDoFinal

Enter the method, by viewing the implementation of the interface we can find a variety of algorithm implementation, here we choose the current Demo implementation, namely RSACipher

View the method body after entering

protected byte[] engineDoFinal(byte[] var1, int var2, int var3) throws BadPaddingException, IllegalBlockSizeException {
    this.update(var1, var2, var3);
    return this.doFinal();
}
Copy the code

There are two operations, update and doFinal. I guess one is to update the data and the other is to perform the calculation.

First look at update

private void update(byte[] var1, int var2, int var3) {
    if(var3 ! =0&& var1 ! =null) {
        if (this.bufOfs + var3 > this.buffer.length) {
            this.bufOfs = this.buffer.length + 1;
        } else {
            System.arraycopy(var1, var2, this.buffer, this.bufOfs, var3);
            this.bufOfs += var3; }}}Copy the code

The RSA key length must be at least 12bytes. For details, see key length, ciphertext length, and plaintext length. So the encryption length is 117. Then watch doFinal

    private byte[] doFinal() throws BadPaddingException, IllegalBlockSizeException {
        if (this.bufOfs > this.buffer.length) {
            throw new IllegalBlockSizeException("Data must not be longer than " + this.buffer.length + " bytes");
        } else {
            try {
                byte[] var1;
                byte[] var2;
                byte[] var3;
                switch(this.mode) {
                case 1:
                    var1 = this.padding.pad(this.buffer, 0.this.bufOfs);
                    var3 = RSACore.rsa(var1, this.publicKey);
                    return var3;
                case 2:
                    var3 = RSACore.convert(this.buffer, 0.this.bufOfs);
                    var1 = RSACore.rsa(var3, this.privateKey, false);
                    byte[] var4 = this.padding.unpad(var1);
                    return var4;
                case 3:
                    var1 = this.padding.pad(this.buffer, 0.this.bufOfs);
                    var2 = RSACore.rsa(var1, this.privateKey, true);
                    return var2;
                case 4:
                    var2 = RSACore.convert(this.buffer, 0.this.bufOfs);
                    var1 = RSACore.rsa(var2, this.publicKey);
                    var3 = this.padding.unpad(var1);
                    return var3;
                default:
                    throw new AssertionError("Internal error"); }}finally {
                this.bufOfs = 0; }}}Copy the code

Init indicates that mode is 3 when the key is a private key, so the following statement is executed

var1 = this.padding.pad(this.buffer, 0.this.bufOfs);
var2 = RSACore.rsa(var1, this.privateKey, true);
return var2;
Copy the code

Fill var1 to get the data to be encrypted, and then encrypt it in rsacore.rsa.

The final encryption function is

    private static byte[] crtCrypt(byte[] var0, RSAPrivateCrtKey var1, boolean var2) throws BadPaddingException {
        BigInteger var3 = var1.getModulus();
        BigInteger var4 = parseMsg(var0, var3);
        BigInteger var6 = var1.getPrimeP();
        BigInteger var7 = var1.getPrimeQ();
        BigInteger var8 = var1.getPrimeExponentP();
        BigInteger var9 = var1.getPrimeExponentQ();
        BigInteger var10 = var1.getCrtCoefficient();
        BigInteger var11 = var1.getPublicExponent();
        BigInteger var12 = var1.getPrivateExponent();
        RSACore.BlindingRandomPair var13 = getBlindingRandomPair(var11, var12, var3);
        BigInteger var5 = var4.multiply(var13.u).mod(var3);
        BigInteger var14 = var5.modPow(var8, var6);
        BigInteger var15 = var5.modPow(var9, var7);
        BigInteger var16 = var14.subtract(var15);
        if (var16.signum() < 0) {
            var16 = var16.add(var6);
        }

        BigInteger var17 = var16.multiply(var10).mod(var6);
        BigInteger var18 = var17.multiply(var7).add(var15);
        var18 = var18.multiply(var13.v).mod(var3);
        if(var2 && ! var4.equals(var18.modPow(var11, var3))) {throw new BadPaddingException("RSA private key operation failed");
        } else {
            returntoByteArray(var18, getByteLength(var3)); }}Copy the code

These involve the implementation of RSA for Java and some theoretical knowledge based on, I am too much now a little dizzy, so I will stop here for the moment.