Java Cipher初探

在编写项目的时候因为要使用SSL,所以我使用到了Cipher这个类,这个类在jdk文档的描述为:html

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

此类提供用于加密和解密的加密密码的功能。 它构成了Java Cryptographic Extension(JCE)框架的核心。算法

描述和用法相同,经过从密钥库或证书的加密类型来获取对应的加密解密的功能。数组

由于好奇,所以我决定看看它的源码来了解RSA具体的加密过程。安全

首先是该类的使用加密Demo:多线程

/** * 最大加密大小 */
    private static final MAX_ENCRYPT_BLOCK = 117;

    public byte[] encryptByPrivateKey(byte[] data) throws Exception {
        //根据密钥库相关信息获取私钥对象
        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();
    }
复制代码

从上述代码能够知道在这个Demo中Cipher的调用顺序为:并发

  1. 经过标准密钥名称初始化加密解密功能。
  2. 初始化密钥。设定加密仍是解密的状态以及对应的密钥。
  3. 按字节数组进行加密并返回加密结果。

所以这篇文章就会经过这个顺序来探讨Cipher对数据加密的流程以及实现。框架

初始化加密解密功能

首先调用的是getInstance(String transformation)方法,该方法在文档中的描述为:dom

Returns a Cipher object that implements the specified transformation.ide

返回实现指定转换的Cipher对象。

和程序使用的目的同样,而后进入该方法内部查看源码:

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;
            }
        }
    }
复制代码

方法很大,这里我根据代码格局看。

首先是

List var1 = getTransforms(var0);
    ArrayList var2 = new ArrayList(var1.size());
    Iterator var3 = var1.iterator();
复制代码

这三行实际是对传入的加密方式的操做,其中的var0就是咱们传入的加密类型。那么有难度的就第一行的getTransforms(var0)方法,这个方法是能够从它的返回值进行猜想,参数是String,返回值是List,一般这种方法的做用都是对字符串进行分割。在此以前先肯定咱们传入的参数:var0 = "RSA"。

因为该方法和文章主题无关,所以我这里就直接一步骤带过:该方法返回一个具体类型为SingletonList的存储着加密类型及相关信息对象的不可变的列表。

在该样例中只有一个存储着相关加密信息的类。

而后是

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

经过遍历var3获取对应的加密类Cipher.Transform并将它的加密信息保存在ServiceId中放入var2中。而该信息为:"RSA"。

接着是

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

又是传入参数并返回List,这个方法的做用以个人理解为:传入密钥类型获取对应的加密解密服务列表,其中保存的元素类型为Service,在源码中对该类的描述为:

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.

安全服务的描述。 它封装了服务的属性,并包含一个工厂方法来获取此服务的新实现实例。

可知var11包含着加密解密服务的属性。

而后是一个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);
复制代码

这是一个对服务以及加密方式的遍历过程,这一步就和该方法的描述同样:

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.

此方法遍历已注册的安全提供程序列表,从最首选的提供程序开始。 将返回一个新的Cipher对象,该对象封装来自第一个支持指定算法的Provider的CipherSpi实现。

在提供的服务列表中遍历并获取到参数对应的服务而后获取它的支持模式填充,具体的就是supportsModePadding方法。该方法会获取提供的Cipher.Transform对象的属性来获得对应的模式。在该例子中返回的模式代码为2。即:var8 = 2。

而后

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

当状态为2,由上可知咱们知足这个判断语句所以返回一个新的Cipher对象。其中的参数描述从上面的阅读中咱们能够知道分别是:未知、服务对象、服务列表、密钥名称、保存着密钥名称信息的Cipher.Transform类。

初始化密钥

有上面可知,getInstance方法返回的Cipher对象中只有密钥和密钥相关的服务。并不知道是加密仍是解密,所以初始化就十分重要。

从该方法点进去能够看到

public final void init(int var1, Key var2) throws InvalidKeyException {
        this.init(var1, var2, JceSecurity.RANDOM);
    }
复制代码

最终具体的方法体为

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 new InvalidKeyException(var5);
            }
        }

        this.initialized = true;
        this.opmode = var1;
        if (!skipDebug && pdebug != null) {
            pdebug.println("Cipher." + this.transformation + " " + getOpmodeString(var1) + " algorithm from: " + this.provider.getName());
        }

    }
复制代码

咱们先从最开始看起

this.init(var1, var2, JceSecurity.RANDOM);
复制代码

这里有新增长了一个JceSecurity.RANDOM参数,这个参数的具体类型为SecureRandom,从源码中查看该类的描述:

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

构造一个实现默认随机数算法的安全随机数生成器(RNG)。

原来是一个随机数生成器。那么继续看下面的代码

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 new InvalidKeyException(var5);
        }
    }
复制代码

判断传入的CipherSpi是否存在。这里我就直接按照这个Demo中的条件来阅读,即CipherSpi不存在。那么执行的代码就是

try {
        this.chooseProvider(1, var1, var2, (AlgorithmParameterSpec)null, (AlgorithmParameters)null, var3);
    } catch (InvalidAlgorithmParameterException var5) {
        throw new InvalidKeyException(var5);
    }
复制代码

其中的参数为:1,加密模式:1,私钥对象,null,null,安全的随机数生成器。 因为chooseProvider方法代码比较多所以先看后续步骤

this.initialized = true;
    this.opmode = var1;
复制代码

这里就是对相关属性的赋值,其中initialized表示初始化完成,opmode表示打开的模式为加密模式(1)。

而后咱们查看chooseProvider方法的方法体

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;
                        }
                    }
                }
            }
        }
    }
复制代码

代码仍是不少,所以我按照功能来阅读

首先是

Object var7 = this.lock;
复制代码

从参数名称就能够知道这是做为并发执行的时候的安全锁。可是在这个Demo中该参数为null。

而后是

synchronized(this.lock) {
    if (this.spi != null) {
        this.implInit(this.spi, var1, var2, var3, var4, var5, var6);
    } else {
        ...
    }
}
复制代码

这里表示其中的操做为原子操做,不容许多个线程执行,按照Demo的条件判断程序进入的是else的代码。

接着是

Exception var8 = null;
复制代码

这里定义了一个线程池。外部原子操做,内部有线程池,一个初始化操做为何要涉及到多线程呢?继续往下

while(true) {
    Service var9;
    CipherSpi var10;
    Cipher.Transform var11;
    
    ...
}
复制代码

这里又是定义参数。其中有服务Service,CipherSpi和密钥信息Cipher.Transform。按照Demo的条件这里我猜想三个参数的值分别是:RSA的服务,null和RSA的信息。

接下来的代码为多个do-while语句嵌套,这里我从里向外阅读,首先是

if (this.firstService == null && !this.serviceIterator.hasNext()) {
    ...
}
复制代码

这个条件的判断须要不存在对应的服务才能知足,在这个Demo中显然仍是存在服务的,因此直接跳过。

而后是

if (this.firstService != null) {
    var9 = this.firstService;
    var10 = this.firstSpi;
    this.firstService = null;
    this.firstSpi = null;
} else {
    var9 = (Service)this.serviceIterator.next();
    var10 = null;
}
复制代码

当存在服务的时候对前面的几个参数(var9, var10)进行赋值,并置空原参数。那么当前var9存在服务,var10为null。而后这个while的判断为

while(!var9.supportsParameter(var3));
复制代码

直接从方法名能够知道,当服务支持密钥的时候就退出。

而后第二个判断的函数名称为canUseProvider,可知当该服务可用的时候退出。而后

var11 = getTransform(var9, this.transforms);
复制代码

可知根据服务和密钥信息获取密钥列表中和该服务对应的密钥并赋值给var11,目前为止这三个参数(var9, var10, var11)的赋值和我以前的猜想吻合。 那么后续的while判断都是获取和服务对应的密钥信息。

而后查看try-catch语句

if (var10 == null) {
    var10 = (CipherSpi)var9.newInstance((Object)null);
}
复制代码

若是var19(CipherSpi)为null,则经过服务返回一个新的实例。该方法的描述为

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.

返回此服务描述的实现的新实例。 安全提供程序框架使用此方法构造实现。 应用程序一般不须要调用它。

而CipherSpi这个类在文档中的描述为

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.

此类定义Cipher类的服务提供者接口(SPI)。 此类中的全部抽象方法必须由但愿提供特定密码算法实现的每一个加密服务提供者实现。

对此后面还有更加具体的描述

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.

转换是一个字符串,它描述要对给定输入执行的操做(或操做集),以产生一些输出。 变换老是包括加密算法的名称(例如,AES),而且能够跟随反馈模式和填充方案。

即这个类才是真正对数据进行操做的类。它提供对应服务的相关加密解密接口。

最后

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;
复制代码

将接口装入服务中,初始化加密权限,判断服务提供者接口和密钥库是否配对并将加密模式(1),密钥和安全的随机数生成器装入SPI中。并设置相关的参数。以及置空原参数。

按字节数组进行加密

首先查看方法体

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");
    }
}
复制代码

首先是

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");
    }
复制代码

这里有个chooseFirstProvider选择首个服务提供商的方法,因为当前已经存在着服务提供商因此能够跳过。

最终他是返回SPI进行加密后的数据。这里我最终肯定了数据是在SPI中处理的。

那么咱们进入到engineDoFinal方法中一探究竟

SPI-engineDoFinal

进入方法,经过查看对该接口的实现咱们能够发现多种算法的实现,这里咱们选择当前Demo中的实现,即RSACipher

进入以后查看方法体

protected byte[] engineDoFinal(byte[] var1, int var2, int var3) throws BadPaddingException, IllegalBlockSizeException {
    this.update(var1, var2, var3);
    return this.doFinal();
}
复制代码

这里有两个操做,分别是update和doFinal,这里我猜想一个是更新数据,另外一个才是进行计算。

首先查看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;
        }
    }
}
复制代码

在这以前有一个init方法会初始化密钥长度,而RSA的密钥长度最低为12bytes,具体能够查看关于RSA算法密钥长度/密文长度/明文长度。因此加密长度为117。 而后看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;
            }
        }
    }
复制代码

有init可知,当密钥为私钥的时候mode为3,因此执行的以下语句

var1 = this.padding.pad(this.buffer, 0, this.bufOfs);
var2 = RSACore.rsa(var1, this.privateKey, true);
return var2;
复制代码

先填充var1获取要要加密的数据,而后在RSACore.rsa中进行加密。

最终加密函数为

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 {
            return toByteArray(var18, getByteLength(var3));
        }
    }
复制代码

这些涉及RSA对于java的实现以及必定的理论知识为基础,我太菜了如今有点绕晕了,因此暂时就到这里了。

相关文章
相关标签/搜索