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的實現以及必定的理論知識爲基礎,我太菜瞭如今有點繞暈了,因此暫時就到這裏了。

相關文章
相關標籤/搜索