1. 程式人生 > >深入理解Android之Java Security第一部分

深入理解Android之Java Security第一部分

深入理解Android之Java Security(第一部分)

從事Android工作4年以來,只有前1年不到的時間是用C++在開發東西(主要是開發DLNA元件,目前我已將它們全部開源,參考http://blog.csdn.net/innost/article/details/40216763),後面的工作幾乎都在用Java。自以為Java相關的東西都見過了,可前段時間有個朋友給我花了1個多小時講解他們某套系統的安全體系結構,其中涉及到很多專業術語,比如Message Digest(訊息摘要)、Digital Signature(數字簽名)、KeyStore(恕我不知道翻譯成什麼好,還是用英文原稱吧)、CA(Certificate Authority)等。我當時腦袋就大了,尼瑪搞Java這麼久,從來沒接觸過啊。為此,我特意到AndroidFramework程式碼中查詢了下,Android平臺裡與之相關的東西還有一個KeyChain。

原來,上述內容都屬於Java世界中一個早已存在的知識模組,那就是JavaSecurity。Java Security包含很多知識點,常見的有MD5,DigitalSignature等,而Android在Java Seurity之外,拓展了一個android.security包,此包中就提供了KeyChain。

本文將介紹Java Security相關的基礎知識,然後介紹下Android平臺上與之相關的使用場景。

實際上,在一些金融,銀行,電子支付方面的應用程式中,JavaSecurity使用的地方非常多。

程式碼路徑:

  • Security.java:libcore/lunl/src/main/java/java/security/
  • TrustedCertificateStore.java:libcore /crypto/src/main/java/org/conscrypt/

本文例子:

一  Java Security背景知識

Java Security其實是Java平臺中一個比較獨立的模組。除了軟體實現上內容外,它實際上對應了一系列的規範。從Java2開始,Java Security包含主要三個重要的規範:

  • JavaCryptography Extension(簡寫為JCE),JCE所包含的內容有加解密,金鑰交換,訊息摘要(Message Digest,比如MD5等),金鑰管理等。本文所涉及的大部分內容都屬於JCE的範疇。
  • JavaSecure Socket Extension(簡寫為JSSE),JSSE所包含的內容就是Java層的SSL/TLS。簡單點說,使用JSSE就可以建立SSL/TLS socket了。
  • JavaAuthentication and Authorization Service(簡寫為JAAS),JSSA和認證/授權有關。這部分內容在客戶端接觸得會比較少一點,所以本文不擬討論它。

在上述三個子模組或規範中,JCE是JavaSecurity的大頭,其他兩個子模組JSSE和JAAS都依賴於它,比如SSL/TLS在工作過程中需要使用金鑰對資料進行加解密,那麼金鑰的建立和使用就依靠JCE子模組了。

另外,既然和安全相關,那麼對安全敏感的相關部門或政府肯定會有所幹涉。Java是在美國被髮明的,所以美國政府對於Java Security方面的出口(比如哪些模組,哪些功能能給其他國家使用)有相關的限制。例如,不允許出口的JCE(從軟體實現上看,可能就是從Java官網上下載到的幾個Jar包檔案)支援一些高階的加解密功能(比如在金鑰長度等方面有所限制)。

注意,Java Security包含的內容非常多,而本文將重點關注Java SecurityAPI與其使用方法方面的知識。對Java Security其他細節感興趣的同學可在閱讀完本文後,再閱讀參考文獻[1]。

1.1  JCE介紹

1.1.1  JCE設計架構介紹

介紹JCE之前,先來講解下JCE的設計架構。JCE在設計時重點關注兩個問題:

  • 其功能具體實現的獨立性和可互操作性。
  • 使用演算法的獨立性和可擴充套件性。

對於獨立性而言,一個最通用的做法就是把定義和實現通過抽象類或基類的方式進行解耦合。在JCE中:

  • 它首先把功能組合成一個一個的Service(也稱作Engine)。比如,對應“加解密”的API組合成CipherService,對應“訊息摘要”的API組合成MessageDigest Service,對應“簽名”的API組合成SignatureService。JCE為這些Service都定義了一個諸如XXXSpi的抽象類。其中,SPI是Service Provider Interface的縮寫。
  • 上述SPI的具體實現則由不同的Provider來提供。比如JDK會提供一組預設的實現(當然,這些實現以前是由Sun公司,現在由Oracle提供。在這個時候,前面提到的政府管制就能插一腳了)。注意,Provider可以提供一組Service的實現,也可以提供單個Service的實現。

注意,可互操作性是指Provider A實現的MD5值,能被Provider B識別。顯然,這個要求是合理的。

圖1所示為JCE中一些類的定義:


圖1  JCE中一些類的定義

圖1展示了JCE中的一些類定義。大家先關注左下角的Provider和Security類:

  • Security是java.security包的主要管理類。通過它可向JCE框架註冊不同的Provider。
  • Android平臺上,每一個應用程式在啟動的時候都會預設註冊一個型別為AndroidKeyStoreProvider的物件。

注意,由於歷史原因,JCE的Service分散定義在好些個Package中:

  • java.security:此Package包含最多的Service定義。
  • javax.crypto:此package包含加解密相關的Service定義。

上述這兩個package都屬於JCE的內容。從使用者的角度來看,其實不太需要關注它們到底定義在哪個Package中(程式碼中通過一句import xxx就可以把對應的包引入,後續編寫程式碼時候,直接使用相關類就好了)。

BTW,個人感覺Java類定義非常多,而且有些類和它們所在包的關係似乎有些混亂。

(1)  Android平臺中Provider的註冊

前面已經說過,JCE框架對使用者提供的是基礎類(抽象類或介面類),而具體實現需要有地方註冊到JCE框架中。所以,我們來看看Android平臺中,JCE框架都註冊了哪些Provider:

[-->Security.java]

static {

   boolean loaded = false;

   try {

       //從資原始檔裡搜尋是否有security.properties定義,Android平臺是沒有這個檔案的

      InputStreamconfigStream =

         Security.class.getResourceAsStream("security.properties");

       InputStream input = newBufferedInputStream(configStream);

       secprops.load(input);

       loaded = true;

       configStream.close();

        }......

     if (!loaded) {//註冊預設的Provider

        registerDefaultProviders();

     }

      ......

    }

   private static void registerDefaultProviders() {

     /*

    JCE對Provider的管理非常簡單,就是將Provider類名和對應的優先順序存在屬性列表裡

    比如下面的:OpenSSLProvider,它對應的優先順序是1.

     優先順序是什麼意思呢?前面說過,不同的Provider可以實現同一個功能集合,比如都實現

     MessageDigestSpi。那麼使用者在建立MessageDigest的例項時,如果沒有指明Provider名,

     JCE預設從第一個(按照優先順序,由1到n)Provider開始搜尋,直到找到第一個實現了

     MessageDigestSpi的Provider(比如Provider X)為止。然後,MessageDigest的例項

      就會由Provider X建立。圖2展示了一個例子

    */

     secprops.put("security.provider.1",

           "com.android.org.conscrypt.OpenSSLProvider");

     secprops.put("security.provider.2",

            "org.apache.harmony.security.provider.cert.DRLCertFactory");

    secprops.put("security.provider.3",

       "com.android.org.bouncycastle.jce.provider.BouncyCastleProvider");

    secprops.put("security.provider.4",

        "org.apache.harmony.security.provider.crypto.CryptoProvider");

      //JSSE有關

      secprops.put("security.provider.5",

        "com.android.org.conscrypt.JSSEProvider");

 }

圖2展示了Provider優先順序使用的例子:


圖2  Provider優先順序示例

圖2中:

  • 左邊的Application要使用MD5演算法來建立一個MessageDigest例項。而左下角有三個Provider(A、B、C)。其中B和C支援MD5演算法。所以JCE會使用第一個實現MD5演算法的Provider(即Provider B)來建立MessageDigest例項。
  • 右邊的Application也要使用MD5演算法來建立MD例項。但是它在引數中指明瞭Provider的名字(“ProviderC”),所以JCE就會直接使用ProviderC來建立這個MD例項。

注意,圖2中的SHA-1/256和MD5都是MessageDigest的一種演算法,本文後面會介紹它們。

(2) AndroidKeyStoreProvider

Android平臺中,每個應用程式啟動時都會註冊一個型別為“AndroidKeyStoreProvider”的物件。這是在ActivityThread中完成的,程式碼如下所示:

[-->ActivityThread.java::main]

public static void main(String[] args) {

   ......

  Security.addProvider(new AndroidKeyStoreProvider());

  ......

}

來看看AndroidKeyStoreProvider是什麼,程式碼如下所示:

[-->AndroidKeyStoreProvider::AndroidKeyStoreProvider]

public class AndroidKeyStoreProvider extends Provider {

  public static final StringPROVIDER_NAME = "AndroidKeyStore";

  publicAndroidKeyStoreProvider() {

    super(PROVIDER_NAME, 1.0,"Android KeyStore security provider");

    put("KeyStore." + AndroidKeyStore.NAME,

          AndroidKeyStore.class.getName());

    put("KeyPairGenerator.RSA",AndroidKeyPairGenerator.class.getName());

  }

}

AndroidKeyStoreProvider很簡單,但是看上去還是不明白它是幹什麼的?其實,這個問題的更深層次的問題是:Provider是幹什麼的?

  • Provider從Properties類派生。Properties本質上是一個屬性列表,也就是它儲存了一串的屬性名和對應的屬性值。
  • 從圖2的例子可知,應用程式只說要建立MD5的MessageDigest例項。在程式碼中,“MD5”是一個字串,而MessageDigest例項肯定是由某個類建立的(假設這個類名叫com.md5.test)。那麼,這個“MD5”字串就是一個Key,對應的屬性值就是這個類的類名(“com.md5.test”)。JCE根據屬性key,找到屬性value,然後建立這個類的例項就完成了工作!

當然,Provider的內容和功能比這要複雜,不過我們對Provider的實現沒什麼興趣,大家只要知道它儲存了一系列的屬性key和value就可以了。JCE會根據情況去查詢這些key和對應的value。

(3)  例項介紹

來個例子,看看Android系統上都有哪些Provider:

[-->DemoActivity.java::testProvider]

void testProvider(){

   e(TAG, "***Begin TestProviders***");

   //獲取系統所有註冊的Provider

   Provider[] providers = Security.getProviders();

   for(Providerprovider:providers){

      //輸出Provider名

      e(TAG,"Provider:" + provider+" info:");

//前面說了,provider其實就是包含了一組key/value值。下面將列印每個Provider的

      //這些資訊

      Set<Entry<Object,Object>>allKeyAndValues = provider.entrySet();

     Iterator<Entry<Object, Object>> iterator =allKeyAndValues.iterator();

      while(iterator.hasNext()){

        Entry<Object,Object> oneKeyAndValue =iterator.next();

        Object key = oneKeyAndValue.getKey();

        Object value =oneKeyAndValue.getValue();

        //列印Key的型別和值

       e(TAG,"===>" + "Keytype="+key.getClass().getSimpleName()+"

                           Key="+key.toString());

        //列印Value的型別和值

      e(TAG,"===>" + "Valuetype="+value.getClass().getSimpleName()+"

                           Value="+value.toString());

        }

     }

     e(TAG, "***End TestProviders***\n\n");

 }

在控制檯中,通過adb logcat | grep ASDemo就可以顯示testProvider的輸出資訊了,如圖3所示:


圖3  testProvider輸出示例

圖3打出了AndroidOpenSSLprovider的資訊:

  • 其中Key和Value的型別都是String。
  • Key的值其實都是JCE中一些Service或演算法的名稱或別名。此處先不討論這些細節,以後碰到再說。

瞭解完JCE框架後,我們分別來介紹JCE中的一些重要Service。

1.1.2  Key知識介紹

談到安全,大家第一想到的就是金鑰,即Key。那麼大家再仔細想想下面這兩個問題:

  • Key從何而來?即程式碼中怎麼建立Key。
  • Key怎麼傳遞給外部使用者?外部使用者可能是一個開發者,這時候就不能把程式碼中的一個物件通過email發給他了,而是要把Key的書面表達形式(參考資料[1]把它叫做externalrepresentations,即外部表達形式。我為了強調這種表達形式,把它稱為書面表達形式。即可有把Key寫到文件裡,發給其他人)發給人家。

圖4解釋了上述問題:


圖4  Key示意

圖4中:

  • Key怎麼建立?在JCE中是通過Generator類建立的,這時候在程式碼中得到的是一個Key例項物件。
  • Key怎麼傳遞?這就涉及到如何書面表達Key了,最最常用的方式就是把它表示成16進位制(比如圖4中下部Encoded Key Data“0AF34C4E56...”)。或者,因為Key產生是基於演算法的,這時候就可以把參與計算的關鍵變數的值搞出來。比如圖4右上角的“Param P=3,ParamQ=4”。所以,Key的書面表達形式有兩種,一種是16進位制金鑰資料,一種是基於演算法的關鍵變數(這種方式叫KeySpecification)。
  • 此後,我們可以把16進位制或者關鍵變數發給對方。對方拿到Key的書面表示式後,下一步要做的就是還原出程式碼中的key物件。這時候要用到的就是KeyFactory了。所以,KeyFactory的輸入是Key的二進位制資料或者KeySpecification,輸出就是Key物件。

在安全領域中,Key分為兩種:

  • 對稱Key:即加密和解密用得是同一個Key。JCE中,對稱key的建立由KeyGenerator類來完成。
  • 非對稱Key:即加密和解密用得是兩個Key。這兩個Key構成一個Key對(KeyPair)。其中一個Key叫公鑰(PublicKey),另外一個Key叫私鑰(PrivateKey)。公鑰加密的資料只能用私鑰解密,而私鑰加密的資料只能用公鑰解密。私鑰一般自己儲存,而公鑰是需要發給合作方的。(此處我們還不討論公鑰和私鑰的使用場景,僅限介紹公鑰和私鑰的概念)。JCE中,非對稱Key的建立由KeyPairGenerator類來完成。

圖5所示為JCE中Key相關的類和繼承關係。


圖5  JCE Key相關類

圖5中:

  • PublicKey,PrivateKey和SecretKey都派生自Key介面。所以,這三個類也是介面類,而且沒有定義新的介面。
  • DSAPublicKey和RSAPublicKey也派生自PublicKey介面。DSA和RSA是兩種不同的演算法。
(1)  Key例項介紹

先來看對稱key的建立和匯入(也就是把Key的書面表達匯入到程式中並生成Key物件)

[-->DemoActivity.java::testKey]

{//對稱key即SecretKey建立和匯入

     //假設雙方約定使用DES演算法來生成對稱金鑰

     e(TAG,"==>secret key: generated it using DES");

     KeyGeneratorkeyGenerator = KeyGenerator.getInstance("DES");

     //設定金鑰長度。注意,每種演算法所支援的金鑰長度都是不一樣的。DES只支援64位長度金鑰

     //(也許是演算法本身的限制,或者是不同Provider的限制,或者是政府管制的限制

     keyGenerator.init(64);

     //生成SecretKey物件,即建立一個對稱金鑰

     SecretKey secretKey = keyGenerator.generateKey();

     //獲取二進位制的書面表達

     byte[] keyData =secretKey.getEncoded();

     //日常使用時,一般會把上面的二進位制陣列通過Base64編碼轉換成字串,然後發給使用者

     String keyInBase64 =Base64.encodeToString(keyData,Base64.DEFAULT);

     e(TAG,"==>secret key: encrpted data ="+ bytesToHexString(keyData));

     e(TAG,"==>secrety key:base64code=" + keyInBase64);

     e(TAG,"==>secrety key:alg=" + secretKey.getAlgorithm());

    //假設對方收到了base64編碼後的金鑰,首先要得到其二進位制表示式

   byte[] receivedKeyData =Base64.decode(keyInBase64,Base64.DEFAULT);

   //用二進位制陣列構造KeySpec物件。對稱key使用SecretKeySpec類

   SecretKeySpec keySpec =new SecretKeySpec(receivedKeyData,”DES”);

   //建立對稱Key匯入用的SecretKeyFactory

   SecretKeyFactorysecretKeyFactory = SecretKeyFactory.getInstance(”DES”);

   //根據KeySpec還原Key物件,即把key的書面表示式轉換成了Key物件

   SecretKey receivedKeyObject = secretKeyFactory.generateSecret(keySpec);

   byte[]encodedReceivedKeyData = receivedKeyObject.getEncoded();

   e(TAG,"==>secret key: received key encoded data ="

                                +bytesToHexString(encodedReceivedKeyData));

如果一切正常的話,紅色程式碼和綠色程式碼打印出的二進位制表示應該完全一樣。此測試的結果如圖6所示:


圖6  SecretKey測試結果

此處有幾點說明:

  • 對稱key的建立有不同的演算法支援,根據參考資料[1],一般有Blowfish, DES, DESede,HmacMD5,HmacSHA1,PBEWithMD5AndDES, and PBEWithMD5AndTripleDES這些演算法。但是Android平臺,甚至不同手機是否都支援這些演算法,則需要在testProvider那個例子去查詢。
  • 另外,KeyGenerator如果支援上面的演算法,但是SecretKeyFactory則不一定支援。比如ScreteKeyFactory則不支援HmacSHA1。

注意,本文會討論太多演算法相關的內容。

再來看KeyPair的用例:

[-->DemoActivity.java::KeyPair測試]

{//public/private key test

  e(TAG, "==>keypair: generated it using RSA");

  //使用RSA演算法建立KeyPair

  KeyPairGeneratorkeyPairGenerator = KeyPairGenerator.getInstance("RSA");

  //設定金鑰長度

  keyPairGenerator.initialize(1024);

  //建立非對稱金鑰對,即KeyPair物件

  KeyPair keyPair =keyPairGenerator.generateKeyPair();

  //獲取金鑰對中的公鑰和私鑰物件

  PublicKey publicKey =keyPair.getPublic();

  PrivateKey privateKey =keyPair.getPrivate();

  //列印base64編碼後的公鑰和私鑰值

  e(TAG,"==>publickey:"+bytesToHexString(publicKey.getEncoded()));

  e(TAG, "==>privatekey:"+bytesToHexString(privateKey.getEncoded()));

  /*

   現在要考慮如何把公鑰傳遞給使用者。雖然可以和對稱金鑰一樣,把二進位制陣列取出來,但是

   對於非對稱金鑰來說,JCE不支援直接通過二進位制陣列來還原KeySpec(可能是演算法不支援)。

   那該怎麼辦呢?前面曾說了,除了直接還原二進位制陣列外,還可以通過具體演算法的引數來還原

   RSA非對稱金鑰就得使用這種方法:

   1 首先我們要獲取RSA公鑰的KeySpec。

  */

  //獲取RSAPublicKeySpec的class物件

  Class spec = Class.forName("java.security.spec.RSAPublicKeySpec");

   //建立KeyFactory,並獲取RSAPublicKeySpec

  KeyFactory keyFactory = KeyFactory.getInstance("RSA");

  RSAPublicKeySpecrsaPublicKeySpec =

            (RSAPublicKeySpec)keyFactory.getKeySpec(publicKey, spec);

  //對RSA演算法來說,只要獲取modulusexponent這兩個RSA演算法特定的引數就可以了

  BigInteger modulus =rsaPublicKeySpec.getModulus();

  BigInteger exponent =rsaPublicKeySpec.getPublicExponent();

  //把這兩個引數轉換成Base64編碼,然後傳送給對方

  e(TAG,"==>rsa pubkey spec:modulus="+

             bytesToHexString(modulus.toByteArray()));

  e(TAG,"==>rsa pubkey spec:exponent="+

             bytesToHexString(exponent.toByteArray()));

  //假設接收方收到了代表modulus和exponent的base64字串並得到了它們的二進位制表示式

  byte[] modulusByteArry =modulus.toByteArray();

  byte[] exponentByteArry =exponent.toByteArray();

  //由接收到的引數構造RSAPublicKeySpec物件

  RSAPublicKeySpecreceivedKeySpec = new RSAPublicKeySpec(

                    newBigInteger(modulusByteArry),

                    new BigInteger(exponentByteArry));

  //根據RSAPublicKeySpec物件獲取公鑰物件

  KeyFactoryreceivedKeyFactory = keyFactory.getInstance("RSA");

  PublicKey receivedPublicKey =

                 receivedKeyFactory.generatePublic(receivedKeySpec);

  e(TAG, "==>received pubkey:"+

                   bytesToHexString(receivedPublicKey.getEncoded()));

}

如果一切正常的話,上述程式碼中紅色和黑色程式碼段將輸出完全一樣的公鑰二進位制資料。如圖7所示:


圖7  KeyPair測試示意圖

在Android平臺的JCE中,非對稱Key的常用演算法有“RSA”、“DSA”、“Diffie−Hellman”、“Elliptic Curve (EC)”等。

(2)  Key知識小結

我自己在學習Key的時候,最迷惑的就是前面提到的兩個問題:

Key是什麼?雖然“金鑰”這個詞經常讓我聯想到實際生活中的鑰匙,但是在學習JavaSecurity之前,我一直不知道在程式碼中(或者程式設計時),它到底是個什麼玩意。並且,它到底怎麼建立。

建立Key以後,我怎麼把它傳遞給其他人。就好比鑰匙一樣,你總得給個實物給人家吧?

現在來看這兩個問題的總結性回答:):

  • JCE中,Key根據加密方法的不同,分為對稱Key和非對稱Key兩大類。其中對稱Key由類SecretKey表達,而非對稱Key往往是公鑰和私鑰構成一個金鑰對。在程式裡,他們的型別分別是PublicKey、PrivateKey和KeyPair。
  • SecretKey和KeyPair都有對應的Generator類來建立。其中,建立對稱Key的是KeyGenerator,建立KeyPair的是KeyPairGenerator。一方建立Key後,怎麼把金鑰資訊傳遞給其他人呢?這時就需要用到Key的外部表現形式了(我把它叫做書面表達形式)。
  • Key的書面表示式有兩種,一種是直接把它的二進位制陣列搞出來,然後編碼成base64發給對方。對方從這個base64編碼字串中得到二進位制陣列。這個二進位制陣列叫KeyEncoded Data。怎麼把它轉換成程式碼中的Key例項呢?對於對稱Key來說,先用這個二進位制陣列構造一個SecretKeySpec,然後再用SecretKeyFactory構造出最終的SecretKey物件。
  • Key的另外一種表示式就是利用建立Key的演算法。這個主要針對非對稱Key。Key建立時,總是需要依賴一些特定的演算法,而這些演算法也會有一些引數。類似於學數學時,演算法對應於一個公式,我們只要把引數值帶入進去就能得到結果一樣。所以,在這種情況下,我們只要把引數儲存起來,然後傳遞給對方可以了。對方拿到這些個引數,再構造出對應演算法所需要的KeySpec物件,最後由KeyPairFactory建立最終的Key物件。

不理解上述內容的同學,請把例項程式碼再仔細看看!

1.1.3  Certificates知識介紹

JCE中,Certificates(是複數喔!)是證書之意。“證書”也是一個日常生活中常用的詞,但是在JCE中,或者說在Java Security中,它有什麼用呢?

這個問題的答案還是和Key的傳遞有關係。前面例子中我們說,建立金鑰的人一般會把Key的書面表達形式轉換成Base64編碼後的字串發給使用者。使用者再解碼,然後還原Key就可以用了。

上面這個流程本身沒有什麼隱患,但是,是不是隨意一個人(一個組織,一個機構等等等等)給你發一個key,你就敢用呢?簡單點說,你怎麼判斷是否該信任給你發Key的某個人或某個機構呢?

好吧,這就好比現實生活中有個人說自己是警察,那你肯定得要他掏出警官證或者什麼別的東西來證明他是警察。這個警官證的作用就是證書的作用。

一般而言,我們會把Key的二進位制表示式放到證書裡,證書本身再填上其他資訊(比如此證書是誰簽發的,什麼時候簽發的,有效期多久,證書的數字簽名等等)。

初看起來,好像證書比直接發base64字串要正規點,一方面它包含了證書籤發者的資訊,而且還有數字簽名以防止內容被篡改。

但是,證書解決了信任的問題嗎?很顯然是沒有的。因為證書是誰都可以製作的。既然不是人人都可以相信,那麼,也不是隨便什麼證書都可以被信任。

怎麼辦?先來看現實生活中是怎麼解決信任問題的。

現實生活中也有很多證書,大到房產證、身份證,小到離職證明、介紹信。對方怎麼判斷你拿的這些證是真實有效的呢?

對頭,看證書是誰/或者哪個機構的“手墨”!比如,結婚證首先要看是不是民政局的章。那....民政局是否應該被信任呢???

好吧。關於民政局是否應該被信任的這個問題在技術上基本無解,它是一個死迴圈。因為,即使能找到另外一個機構證明民政局合法有效,但這個問題依然會順流而上,說民政局該被信任的那個機構其本身是否能被信任呢?....此問題最終會沒完沒了的問下去。

那怎麼辦?沒什麼好辦法。只要大家公認民政局能被信任,就可以了。

同理,對證書的是否可信任問題而言:

  • 首先,在全世界範圍內(或者是一個國家,一個地區)設定一些頂級證書籤發機構,凡是由這些證書籤發機構(CertificateAuthorities)簽發的證書,我們要無條件的信任(不考慮其他偽造等因素,證書是否被篡改可通過數字簽名來判斷)。就和民政局似的,不信民政局還信誰?:)。
  • 這麼多證書要發,頂級CA肯定忙不過來,所以還可以設立各種級別的CA,比如北京市的民政局搞一個CA,湖南省的公安廳搞一個CA。這種級別的CA是否該無條件信任呢?不一定。看你對它的瞭解。anyway,這個級別的CA可以把自己拿到頂級CA那去認個證。比方說,湖南省公安廳發了一個證書,它可以到頂級CA那去蓋個章。由於頂級CA是無條件信任的,所以湖南省公安廳這個證書可以被信任。
  • 公司A要發證書給客戶使用,最好是先拿到上一級或者相關領域的CA(不一定是頂級CA)那去蓋個章。最後,公司A把蓋了章的證書a發給客戶使用就可以了。

客戶拿到證書a後,首先要檢查下:

  • 客戶拿到證書a後,發現是某個CA簽發的。如果是頂級CA簽發的,那好辦,直接信任。如果不是頂級CA簽發的。客戶再去檢視這個發證的CA能不能被信任,溯流而上,直到跟蹤到頂級CA為止。所以,證書,往往不是一個證書的事情,而是一個證書鏈的事情!
  • 另外,如果客戶本身就信任公司A,那其實公司A也不需要去找CA認證,直接把證書a給客戶就可以了。當然,這個時候的證書a就不需要CA的章了。

......唧唧歪歪半天,其實關於證書的核心問題就一個:

證書背後往往是一個證書鏈。

  • 大家不要小看這個問題。證書鏈經常會把問題搞複雜。比如甲方有CA A簽發的證書,而乙方有CA B簽發的證書。那麼甲乙雙方要互相信任的話,得把甲乙雙方證書鏈上的證書都搞過來校驗一遍,直到頂級CA為止。當然,甲乙證書鏈的頂級CA可能還不是同一個。

.......

為了方便,系統(PC,Android,甚至瀏覽器)等都會把一些頂級CA(也叫Root CA,即根CA)的證書預設整合到系統裡。這些RootCA用作自己身份證明的證書(包含該CA的公鑰等資訊)叫根證書。根證書理論上是需要被信任的。以Android為例,它在libcore/luni/src/main/files/cacerts下放了150多個根證書(以Android 4.4為例),如圖8所示:


圖8  Android自帶的根證書檔案

我們隨便開啟一個根證書檔案看看,如下所示:

[某證書檔案的內容,用記事本開啟即可]

-----BEGIN CERTIFICATE-----

MIID5TCCAs2gAwIBAgIEOeSXnjANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UEBhMC

VVMxFDASBgNVBAoTC1dlbGxzIEZhcmdvMSwwKgYDVQQLEyNXZWxscyBGYXJnbyBD

ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEvMC0GA1UEAxMmV2VsbHMgRmFyZ28gUm9v

dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDAxMDExMTY0MTI4WhcNMjEwMTE0

MTY0MTI4WjCBgjELMAkGA1UEBhMCVVMxFDASBgNVBAoTC1dlbGxzIEZhcmdvMSww

KgYDVQQLEyNXZWxscyBGYXJnbyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEvMC0G

A1UEAxMmV2VsbHMgRmFyZ28gUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEi

MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDVqDM7Jvk0/82bfuUER84A4n13

5zHCLielTWi5MbqNQ1mXx3Oqfz1cQJ4F5aHiidlMuD+b+Qy0yGIZLEWukR5zcUHE

SxP9cMIlrCL1dQu3U+SlK93OvRw6esP3E48mVJwWa2uv+9iWsWCaSOAlIiR5NM4O

JgALTqv9i86C1y8IcGjBqAr5dE8Hq6T54oN+J3N0Prj5OEL8pahbSCOz6+MlsoCu

ltQKnMJ4msZoGK43YjdeUXWoWGPAUe5AeH6orxqg4bB4nVCMe+ez/I4jsNtlAHCE

AQgAFG5Uhpq6zPk3EPbg3oQtnaSFN9OH4xXQwReQfhkhahKpdv0SAulPIV4XAgMB

AAGjYTBfMA8GA1UdEwEB/wQFMAMBAf8wTAYDVR0gBEUwQzBBBgtghkgBhvt7hwcB

CzAyMDAGCCsGAQUFBwIBFiRodHRwOi8vd3d3LndlbGxzZmFyZ28uY29tL2NlcnRw

b2xpY3kwDQYJKoZIhvcNAQEFBQADggEBANIn3ZwKdyu7IvICtUpKkfnRLb7kuxpo

7w6kAOnu5+/u9vnldKTC2FJYxHT7zmu1Oyl5GFrvm+0fazbuSCUlFLZWohDo7qd/

0D+j0MNdJu4HzMPBJCGHHt8qElNvQRbn7a6U+oxy+hNH8Dx+rn0ROhPs7fpvcmR7

nX1/Jv16+yWt6j4pf0zjAFcysLPp7VMX2YuyFA4w6OXVE8Zkr8QA1dhYJPz1j+zx

x32l2w8n0cbyQIjmH/ZhqPRCyLk306m+LFZ4wnKbWV01QIroTmMatukgalHizqSQ

33ZwmVxwQ023tqcZZE6St8WRPH9IFmV7Fv3L/PvZ1dZPIWU7Sn9Ho/s=

Certificate:  #下面是證書的明文內容

    Data:

        Version: 3 (0x2)

        Serial Number:971282334 (0x39e4979e)

    Signature Algorithm: sha1WithRSAEncryption

        Issuer: C=US, O=Wells Fargo, OU=Wells Fargo CertificationAuthority, CN=Wells Fargo Root Certificate Authority

        Validity

            Not Before: Oct11 16:41:28 2000 GMT

            Not After : Jan14 16:41:28 2021 GMT

        Subject: C=US, O=Wells Fargo, OU=Wells Fargo CertificationAuthority, CN=Wells Fargo Root Certificate Authority

        Subject Public Key Info:#Public KeyKeySpec表示式

            Public Key Algorithm: rsaEncryption  #PublicKey的演算法

                Public-Key: (2048 bit)

                Modulus:

                   00:d5:a8:33:3b:26:f9:34:ff:cd:9b:7e:e5:04:47:

                   ce:00:e2:7d:77:e7:31:c2:2e:27:a5:4d:68:b9:31:

                   ba:8d:43:59:97:c7:73:aa:7f:3d:5c:40:9e:05:e5:

                    a1:e2:89:d9:4c:b8:3f:9b:f9:0c:b4:c8:62:19:2c:

                   45:ae:91:1e:73:71:41:c4:4b:13:fd:70:c2:25:ac:

                   22:f5:75:0b:b7:53:e4:a5:2b:dd:ce:bd:1c:3a:7a:

                   c3:f7:13:8f:26:54:9c:16:6b:6b:af:fb:d8:96:b1:

                    60:9a:48:e0:25:22:24:79:34:ce:0e:26:00:0b:4e:

                   ab:fd:8b:ce:82:d7:2f:08:70:68:c1:a8:0a:f9:74:

                   4f:07:ab:a4:f9:e2:83:7e:27:73:74:3e:b8:f9:38:

                   42:fc:a5:a8:5b:48:23:b3:eb:e3:25:b2:80:ae:96:

                    d4:0a:9c:c2:78:9a:c6:68:18:ae:37:62:37:5e:51:

                   75:a8:58:63:c0:51:ee:40:78:7e:a8:af:1a:a0:e1:

                   b0:78:9d:50:8c:7b:e7:b3:fc:8e:23:b0:db:65:00:

                   70:84:01:08:00:14:6e:54:86:9a:ba:cc:f9:37:10:

                   f6:e0:de:84:2d:9d:a4:85:37:d3:87:e3:15:d0:c1:

                   17:90:7e:19:21:6a:12:a9:76:fd:12:02:e9:4f:21:

                    5e:17

                Exponent: 65537 (0x10001)

        X509v3 extensions:

            X509v3 BasicConstraints: critical

                CA:TRUE

            X509v3Certificate Policies:

                Policy:2.16.840.1.114171.903.1.11

                  CPS:http://www.wellsfargo.com/certpolicy

    Signature Algorithm: sha1WithRSAEncryption  #數字簽名,以後再講

        d2:27:dd:9c:0a:77:2b:bb:22:f2:02:b5:4a:4a:91:f9:d1:2d:

        be:e4:bb:1a:68:ef:0e:a4:00:e9:ee:e7:ef:ee:f6:f9:e5:74:

        a4:c2:d8:52:58:c4:74:fb:ce:6b:b5:3b:29:79:18:5a:ef:9b:

        ed:1f:6b:36:ee:48:25:25:14:b6:56:a2:10:e8:ee:a7:7f:d0:

        3f:a3:d0:c3:5d:26:ee:07:cc:c3:c1:24:21:87:1e:df:2a:12:

        53:6f:41:16:e7:ed:ae:94:fa:8c:72:fa:13:47:f0:3c:7e:ae:

        7d:11:3a:13:ec:ed:fa:6f:72:64:7b:9d:7d:7f:26:fd:7a:fb:

        25:ad:ea:3e:29:7f:4c:e3:00:57:32:b0:b3:e9:ed:53:17:d9:

        8b:b2:14:0e:30:e8:e5:d5:13:c6:64:af:c4:00:d5:d8:58:24:

        fc:f5:8f:ec:f1:c7:7d:a5:db:0f:27:d1:c6:f2:40:88:e6:1f:

        f6:61:a8:f4:42:c8:b9:37:d3:a9:be:2c:56:78:c2:72:9b:59:

        5d:35:40:8a:e8:4e:63:1a:b6:e9:20:6a:51:e2:ce:a4:90:df:

        76:70:99:5c:70:43:4d:b7:b6:a7:19:64:4e:92:b7:c5:91:3c:

        7f:48:16:65:7b:16:fd:cb:fc:fb:d9:d5:d6:4f:21:65:3b:4a:

         7f:47:a3:fb

SHA1 Fingerprint=93:E6:AB:22:03:03:B5:23:28:DC:DA:56:9E:BA:E4:D1:D1:CC:FB:65

關於證書檔案,還有一些容易混淆的事情要交待:

  • 前面講過,證書有很多格式,但是目前通用格式為X.509格式。在上面的證書檔案中,從Certificate這一行開始到檔案最後,都是符合X.509格式的。那檔案前面的“-----BEGINCERTIFICATE-----”到“-----END CERTIFICATE-----”是些什麼?
  • X.509是證書的格式,但是證書和我們看到的檔案還是有一些差異。證書需要封裝在檔案裡。不同系統支援不同的證書檔案,每種證書檔案,所要求包含的具體的X.509證書內容也不一樣。另外,某些證書檔案還能包含除X.509之外的其他有用的資料。

下面是一些常見的證書檔案格式,一般用檔案字尾名標示。

  • .pem(Privacy-enhanced ElectronicMail) Base64 編碼的證書,編碼資訊(即將示例中X.509證書的明文內容用Base64編碼後得到的那個字串)放在"-----BEGIN CERTIFICATE-----" and "-----END CERTIFICATE-----"之間。所以,Android平臺裡的根CA檔案都是PEM證書檔案。
  • .cer,.crt, .der:證書內容為ASCII編碼,二進位制格式,但也可以和PEM一樣採用base64編碼。
  • .p7b,.p7c – PKCS#7(Public-Key CryptographyStandards ,是由RSA實驗室與其它安全系統開發商為促進公鑰密碼的發展而制訂的一系列標準,#7表示第7個標準,PKCS一共有15個標準)封裝的檔案。其中,p7b可包含證書鏈資訊,但是不能攜帶私鑰,而p7c只包含證書。
  •  .p12– PKCS#12標準,可包含公鑰或私鑰資訊。如果包含了私鑰資訊的話,該檔案內容可能需要輸入密碼才能檢視。

特別注意:

1  證書檔案相關的規範特別多,讀者感興趣的話可閱讀參考文獻[2][3][4][5]。

2  X509證書規範本身是不包含私鑰資訊的。但是某些證書檔案卻可以,而且某些證書檔案能包含一條證書鏈上所有證書的資訊。

關於證書的理論知識,我們先介紹到這,只要大家心中有下面幾個概念就可以了:

  • 證書包含很多資訊,但一般就是各種Key的內容。
  • 證書由CA簽發。為了校驗某個證書是否可信,往往需要把一整條證書鏈都校驗一把,直到根證書。
  • 系統一般會整合很多根證書,這樣免得使用者自己去下載根證書了。
  • 證書自己的格式通用為X.509,但是證書檔案的格式卻有很多。不同的系統可能支援不同的證書檔案。

注意:後面介紹KeyStore的時候,我們還會繼續討論證書檔案的問題。

(1)  證書例項介紹

證書例項主要關注於拿到人家給的證書後,我們怎麼匯入進來使用:

[-->DemoActivity.java::testCertificate]

void testCertificate() {

    e(TAG, "***Begintest Certificates***");

    try {

      //在res/assets目錄下放了一個名為“my-root-cert.pem”的證書檔案

      AssetManagerassetManager = this.getAssets();

      InputStream inputStream= assetManager.open("my-root-cert.pem");

      //匯入證書得需要使用CertificateFactory

      CertificateFactorycertificateFactory =

                      CertificateFactory.getInstance("X.509");

      /*

      從my-root-cert.pem中提取X509證書資訊,並儲存在X509Certificate物件中

      注意,如果一個證書檔案包含多個證書(證書鏈的情況),generateCertificate將只返回

      第一個證書

      呼叫generateCertificates函式可以返回一個證書陣列,

      */

      X509CertificatemyX509Cer =

          (X509Certificate)certificateFactory

              .generateCertificate(inputStream);

     //列印X509證書的一些資訊。DN是Distinguished Name。DN通過設定很多項(類似於地址

     //一樣,比如包括國家、省份、街道等資訊)來唯一標示一個持有東西(比如釋出此證書的機構等)

      e(TAG, "==>SubjecteDN:" + myX509Cer.getSubjectDN().getName());

      e(TAG,"==>Issuer DN:" + myX509Cer.getIssuerDN().getName());

      e(TAG,"==>Public Key:"

          +bytesToHexString(myX509Cer.getPublicKey().getEncoded()));

      inputStream.close();

    } ......

    }

    e(TAG, "***End testCertificates***");

  }

注意,CertificateFactory只能匯入pemder格式的證書檔案

(2)  證書檔案格式討論

證書格式和證書檔案格式可能是最難搞清楚的了。根據參考資料[6],我把證書格式和證書檔案格式的相關討論整理如下:

  • X.509:這個是數字證書的規範。X.509只能攜帶公鑰資訊。
  • PKCS#7和#12是證書的封裝格式。PKCS#7一般是把證書分成兩個檔案,一個公鑰一個私鑰,有PEM和DER兩種編碼方式。PEM比較多見,就是純文字的,PKCS#7一般是分發公鑰用,看到的就是一串可見字串,副檔名經常是.crt,.cer,.key等。DER是二進位制編碼。
  • PKCS#12是“個人資訊交換語法(Personal InfomationExchange)”。它可以用來將x.509的證書和證書對應的私鑰打包,進行交換。比如在windows下,可以將IE裡的證書連帶私鑰匯出,並設定一個口令保護。這個pfx格式的檔案,就是按照pkcs#12的格式打包的。當然pkcs#12不僅僅只是作以上用途的。它可以用來打包交換任何資訊。pfx檔案可以含有私鑰,同時可以有公鑰,有口令保護。

還是有點模模糊糊的感覺,我個人體會是:

  • X.509包含證書的基本資訊。
  • PKCS#7和12包含更多的一些資訊。PKCS#12由於可包含私鑰資訊,而且檔案本身還可通過密碼保護,所以更適合資訊交換。

圖9是我在Ubuntu上開啟一個p12檔案的時候,系統提示需要輸入密碼:


圖9  開啟p12檔案時候提示輸入密碼

1.1.4  Key管理

現實生活中,人這一生是會持有或使用很多證件的,比如身份證、房產證、結婚證,黨員證,學生證等。顯然,這些證件是需要好好保管的。此情況反應到JCE中,就引出了Key Management這個東西。

JCE中,Key管理有幾個基本概念:

  • keystore:keystore就是儲存Key的一個檔案(也可以不是檔案,但總之是儲存,管理Key的