Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 為你的android App實現自簽名的ssl證書(https)

為你的android App實現自簽名的ssl證書(https)

編輯:關於Android編程

 

不願意看啰嗦的可以直接去 實現步驟大笑

 

 

最近公司項目用到https的接口形式,對於一般的網絡請求 我們用的是http://****** 使用的是

 

代碼用來打開一個http 連接.

URLurlConnection=newURL("http://www.codeproject.com/");

HttpURLConnectionurlConnection=(HttpURLConnection)url.openConnection();

不要在 http連接中打開:登陸頁面, 或是傳遞用戶名, 密碼, 銀行卡之類的重要個人資料. 這些重要個人數據應該通過 HTTPS (英文的介紹)傳輸.

 

HTTPS是什麼?

 

HTTPS 其實就是個安全版的http.HTTPS能保證電子商務的交易安全. 如:網上銀行.像 IE 或者火狐浏覽器, 如果出現下面的掛鎖圖標.

為你的Android App實現自簽名的 SSL 證書

同時, 在浏覽器的地址欄中以 https:// 開頭, 這表示, 你的浏覽器跟這個網站的數據往來都是安全的.https 跟 http 的最大區別在於 https 多加了一個保障通訊安全的層.

為你的Android App實現自簽名的 SSL 證書

像下列代碼這樣打開一個 https 連接, 可以保障這個連接的數據通訊安全.

URLurlConnection=newURL("https://www.codeproject.com/");

HttpsURLConnectionurlConnection=(HttpsURLConnection)url.openConnection();

InputStreamin=newBufferedInputStream(urlConnection.getInputStream());//然而對於有的鏈接(需要證書的)是連接不上的後面會介紹

HTTPS通過 SSL/TLS傳遞數據.

 

SSL/TLS:

 

SSL (Secure Sockets Layer) 是一種在客戶端跟服務器端建立一個加密連接的安全標准. 一般用來加密網絡服務器跟浏覽器,或者是郵件服務器跟郵件客戶端(如: Outlook)之間傳輸的數據.

SSL 能保障敏感信息(如:銀行卡號, 社保卡號, 登陸憑證等)的傳輸安全. 一般情況下, 數據在浏覽器跟服務器之間的傳輸使用的是明文格式, 這種方式存在資料被竊取的風險.如果黑客能攔截浏覽器跟服務器之間的通訊數據,就能看到通訊的內容.

SSL/HTTPS and X.509 證書概述

你要是對 SSL或 X.509 證書一無所知, 那我大概解釋下. 對於那些打算用自簽名證書(self-signed certificate)的人來說, 需要了解自簽名證書跟花錢購買機構頒發的證書有什麼區別.

首先我們需要了解下 SSL 證書究竟是個什麼東東? 其實它就包含倆部分: 1) 一個身份標識,一個用來識別身份的東西, 有點類似警察叔叔通過護照或駕照查你的身份;2) 一個公共密鑰, 這個用來給數據加密, 而且只有證書的持有者才能解密.簡而言之,SSL 證書就倆個功能, 身份驗證跟保障通訊過程中的數據安全.

另外還有一點很重要. 那就是一個證書可以給另外一個證書“簽字”. 舉個栗子說:就是 Bob 用他自己的證書在別的證書上蓋上 “同意” 兩個紅紅的大字. 如果你信任 Bob (當然還有他的證書), 那麼你也可以信任由他簽發的證書. 在這個例子中, Bob 搖身一變, 成了證書頒發機構(Certificate Authority). 現在主流的浏覽器都自帶一大堆受信任證書頒發機構(trusted Certificate Authorities)(比如:Thawte,Verisign等).

最後我們講一講浏覽器是怎麼使用證書的.籠統的講,當你打開下列連接的時候 “https://www.yoursite.com” :

  1. 服務器會給浏覽器發一個證書.

  2. 浏覽器會對比證書中的“common name”(有時也叫 “subject”) 跟服務器的域名是否一樣.例如, 一個從“www.yoursite.com”網站發過來的證書就應該有一個內容是 “www.yoursite.com” 的 common name, 否則浏覽器就會提示該證書有問題.

  3. 浏覽器驗證證書真偽, 有點像門衛通過證件上的全息圖辨別你的證件是不是真的. 既然在現實生活中有人偽造別人的身份. 那麼在網絡世界也就有人造假, 比如用你的域名“www.yoursite.com” 來偽造一個安全證書.浏覽器在驗證的時候, 會檢查這個證書是否是它信任機構頒發的,如果不是, 那麼浏覽器就會提示這個證書可能有問題.當然用戶也可以無視警告,繼續使用;(相信大家一定會遇到的,比如12306網頁訪問的時候)

  4. 一旦證書通過驗證(或是用戶無視警告, 繼續使用有問題的證書),浏覽器就開始利用證書中的公開密鑰加密數據並傳給服務器.

    TLS (SSL)中的加密(詳細的可以看上一篇文章http://blog.csdn.net/u013424496/article/details/51161370點擊打開鏈接)

    一旦服務器發過來的證書通過驗證, 浏覽器就會利用證書中包含的公共密鑰加密某個指定的共享密鑰, 然後發給服務器. 這個加密過的共享密鑰只能用服務器的私有密鑰才能解密(非對稱加密), 別人無法解密出其中的內容. 服務器把解密出來的共享密鑰保存起來, 供本次連接會話專用. 從現在開始, 服務器跟浏覽器之間的所有通訊信息都用這個共享密鑰加密解密(對稱加密).

    為你的Android App實現自簽名的 SSL 證書

    理論部分就這麼多, 下面我們來看幾個例子.

    單擊這個綠色圖標, 然後點證書信息連接, 就能看到下列內容.

    為你的Android App實現自簽名的 SSL 證書

    這是個 SSL 證書, 該證書是 Verisign 給 mail.live.cm 頒發的.

    Verisign是一個證書頒發機構,它提示你的浏覽器正在連接的網站是: mail.live.com, 需要跟這個網站的服務器建立一條安全連接進行通訊, 避免他人攔截或篡改浏覽器跟服務器之間傳遞的數據.

    MITM 攻擊

    MITM 攻擊(MITMA)是指: 黑客攔截篡改網絡中的通訊數據

    被動MITMA 是指黑客只能竊取通訊數據, 而在主動 MITMA中,黑客除了竊取數據,還能篡改通訊數據. 黑客利用 MITMA 方式攻擊手機要比攻擊台式電腦容易的多. 這主要是因為使用手機的環境在不固定,有些地方用手機連接上網並不安全, 尤其是那些對公眾免費開放的無線網絡熱點.(這個時候也就需要我們有證書的保證了)

    證書頒發機構(CA)

    ·Symantec(which boughtVeriSign's SSL interests and owns Thawte and Geotrust) 38.1% 市場份額

    ·Comodo SSL29.1%

    ·Go Daddy13.4%

    ·GlobalSign10%

    Jelly bean 版本的安卓系統中, 你可以在下列路徑中找到證書頒發機構: 設置->安全 ->受信任的憑證.

    說回來Https 連接

     

    URLurl=newURL("https://www.example.com/");
    HttpsURLConnectionurlConnection=(HttpsURLConnection)url.openConnection();
    InputStreamin=urlConnection.getInputStream();

     

    如果你連接的服務器(www.example.com)傳過來的證書是由機構頒發的, 這段代碼就能正常運行.

    但是如果你連的服務器用的是自己頒發的證書(self-singed certificate), 那就會出現錯誤.

    什麼是自簽名證書(self-signed certicates)

    自簽名證書就是沒有通過受信任的證書頒發機構, 自己給自己頒發的證書.

    SSL證書大致分三類:

    • 由安卓認可的證書頒發機構(如: VeriSign), 或這些機構的下屬機構頒發的證書.

    • 沒有得到安卓認可的證書頒發機構頒發的證書.

    • 自己頒發的證書, 分臨時性的(在開發階段使用)或在發布的產品中永久性使用的兩種.

      只有第一種, 也就是那些被安卓系統認可的機構頒發的證書, 在使用過程中不會出現安全提示.

      為什麼有人喜歡用自簽名證書

      1. 免費. 購買受信任機構頒發的證書每年要交 100 到 500 美元不等的費用. 自簽名證書不花一分錢.

      2. 自簽名證書在手機應用中的普及率較高(跟用電腦浏覽網頁不同, 手機的應用一般就固定連一台服務器.).

      3. 在開發階段寫的代碼,測試跟發布的時候也可以用.

        最近一項調查表明, 810萬個證書中, 只有 320萬個是由受信任機構頒發的. 剩余490萬證書中, 自簽名的占48%, 未知機構頒發的占33%, 而不被信任的機構頒發的證書占19%.

        無獨有偶, 我的分析結果也表明,起碼有 60% 安卓應用使用自簽證書.

        個人以為, 在手機應用中使用自簽名證書沒什麼不好, 既不需要花錢, 也不需要修改代碼.(注:如果你用的是機構頒發的證書, 在產品發布階段, 需要修改代碼).

        但是下面的戲法的一般性的https代碼

        URLurl=newURL("https://www.example.com/");
        HttpsURLConnectionurlConnection=(HttpsURLConnection)
        url.openConnection();
        InputStreamin=urlConnection.getInputStream();

         

        如果你使用上述的代代碼去驗證你的自己簽署的證書,由於在android操作系統中自己簽署的不能通過驗證的,所以安卓應用軟件將會拋出錯誤。因此你需要書寫你自己的代碼來檢查你的自己簽署的證書。但是在這個時候,安卓開發者犯了一個很大的錯誤,我發現開發者僅僅是簡單的復制、粘貼Stack Overflow和其他博客中允許你的應用默認信任所以證書的答案。即使大多說的答案表述僅僅在測試模式下可以使用,但是開發者簡單地復制代碼,將會導致應用軟件在遭到中間件攻擊和session黑客攻擊是,表現的非常的脆弱!

        例子:http://stackoverflow.com/questions/2703161/how-to-ignore-ssl-certificate-errors-in-apache-httpclient-4-0

         

        http://stackoverflow.com/questions/2012497/accepting-a-certificate-for-https-on-android?lq=1

        http://www.caphal.com/android/using-self-signed-certificates-in-android/

        http://stackoverflow.com/questions/2642777/trusting-all-certificates-using-httpclient-over-https

         

         

        在使用自己簽署的證書時一般性的錯誤

         

        信任所有的證書。

         

        TrustManager的主要責任是去決定提出的認證證書應該是可信任的。如果證書是不可信任的,那麼連接將會被終止。去認證遠程的安全套接字識別,你需要用一個或者多個TrustManager(s)初始化SSLContext對象。

         

        importorg.apache.http.conn.ssl.SSLSocketFactory;
        publicclassMySSLSocketFactoryextendsSSLSocketFactory{
        SSLContextsslContext=SSLContext.getInstance("TLS");
        
        publicMySSLSocketFactory(KeyStoretruststore)throwsNoSuchAlgorithmException,KeyManagementException,KeyStoreException,UnrecoverableKeyException{
        super(truststore);
        
        TrustManagertm=newX509TrustManager(){
        publicvoidcheckClientTrusted(X509Certificate[]chain,StringauthType)throwsCertificateException{
        }
        
        publicvoidcheckServerTrusted(X509Certificate[]chain,StringauthType)throwsCertificateException{
        }
        
        publicX509Certificate[]getAcceptedIssuers(){
        returnnull;
        }
        };
        
        sslContext.init(null,newTrustManager[]{tm},null);
        }
        
        @Override
        publicSocketcreateSocket(Socketsocket,Stringhost,intport,booleanautoClose)throwsIOException,UnknownHostException{
        returnsslContext.getSocketFactory().createSocket(socket,host,port,autoClose);
        }
        
        @Override
        publicSocketcreateSocket()throwsIOException{
        returnsslContext.getSocketFactory().createSocket();
        }
        }

        我發現,80到100個應用軟件中,在20到25個應用中實現了上述的代碼。(在asyncHttp的網絡封裝中就用到了這段代碼,不過你只要不使用AsyncHttpClient(boolean fixNoHttpResponseException, int httpPort, int httpsPort)構造方法,將fixNoHttpResponseException設置為true是不會出問題的,當時如果你要用到https時的證書該如何,可看http://blog.csdn.net/u013424496/article/details/51133544文章)

        在上述的TrustManager接口中,可以實現信任所以的證書,不論是誰簽署的或者即使他們發布的任何主題。這個接口將會允許接受ANY證書。接受任何的證書將會危害數據的完整性、安全性等等。

        在上述的例子中,檢查客戶端可信任性,獲得接受事件,檢查服務器端可信任性是三點重要的功能。每一位開發者都應該留意這三點功能的實現上。但是卻很少有開發者從不同的網站中搜索、負責上述的功能。

        允許所有的主機名。

        忘記檢查證書是否在這個地址發布是有可能的。當證書接受了example.com的服務器,那麼另外的一個域名也將被接受。

        HostnameVerifierhostnameVerifier=org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER;
        
        DefaultHttpClientclient=newDefaultHttpClient();
        
        SchemeRegistryregistry=newSchemeRegistry();
        SSLSocketFactorysocketFactory=SSLSocketFactory.getSocketFactory();
        socketFactory.setHostnameVerifier((X509HostnameVerifier)hostnameVerifier);
        registry.register(newScheme("https",socketFactory,443));
        SingleClientConnManagermgr=newSingleClientConnManager(client.getParams(),registry);
        DefaultHttpClienthttpClient=newDefaultHttpClient(mgr,client.getParams());
        
        //Setverifier
        HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);
        
        //Examplesendhttprequest
        finalStringurl="https://www.paypal.com”
        HttpPosthttpPost=newHttpPost(url);
        HttpResponseresponse=httpClient.execute(httpPost);
        
        HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);
        HostnameVerifierhostnameVerifier=org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER;

        上述的代碼中,即使是錯誤的實現,也將會接受對任何域名的任何CA證書聲明。

         

        Mixed-Mode/No SSL.

         

        應用軟件開發者在相同的應用中使用最大安全、不安全的連接,或者不使用SSL將是免費的。這不是直接的SSL聲明,但是和提到的沒有向外的簽署個有關,同時對於一般軟件的使用者,檢查是否使用一個安全的連接是不可能的。這將會為例如SSL剝離,或者像FireSheep這樣的攻擊開後門。

        SSL剝離是另外的一種可以使MITMA登陸並抵制SSL連接方式。利用使用最大HTTP和HTTPS應用。SSL剝離依賴大量建立在點擊鏈接、或者來自於沒有SSL重定向保護的網站SSL連接。在SSL剝離中,Mallory用HTTPS://取代了沒有保護的網站中http:// 鏈接中。因此,除非使用者注意到了鏈接被篡改了,Mallory可以完全地規避SSL保護。這樣的攻擊主要與浏覽器應用、或者原生使用安卓WebView應用有關。

        更多關於SSL剝離的信息:

         

        http://security.stackexchange.com/questions/41988/how-does-sslstrip-work

        http://www.thoughtcrime.org/software/sslstrip/

        接下來才是真正的進入主題了

        證書鎖定

        直接在代碼中固定寫死使用某個服務器的證書.然後在應用中使用自己定義的信任存儲(trust store)代替手機系統自帶的那個, 去連接指定的服務器.

        這樣做的好處是, 我們既能使用自簽名證書, 又不需要額外安裝其他證書.

        優勢

        • 安全性提升- 采用這種方式, 應用不再依賴系統自帶的信任存儲(trust store). 使得破解這種應用變得復雜:首先你要反編譯,修改完後,還要重新編譯.關鍵是你不可能使用應用作者原先用的那個 keystore 文件重新頒發證書.

        • 成本降低- 證書鎖定方式讓我們可以在自己的服務器上使用免費的自簽名證書, 調用自己寫的 API. 雖說復雜了點, 可是像這種既不花錢, 還能提高應用安全的好事上哪找去?

           

          缺點

          • 適應性較差- 一旦 SSL 證書出現變動, 應用也要跟著升級.再發布到 Google Play. 然後祈禱用戶能都升級到最新版本.

            安卓的 SSLContext 自帶的TrustManager無法讓本文示例中提到的自簽名證書通過驗證.解決的辦法是自己定義一個TrustManager 類. 然後用這個類去驗證自簽名證書.

            先把證書加載到KeyStore,然後用 KeyStore生成一個TrustManager 數組, 最後再用這個 TrustManager 數組創建SSLContext.

            本文的應用把服務器的證書直接存進應用的資源.(畢竟這個文件是所有用戶都共用的, 而且也不會經常改動), 當然你可以把它存到別的地方.

            實現的步驟

            第1步: 創建自簽名證書和.bks 文件

            1)

            創建BKS或者 keystore,需要用到下面這個文件,bcprov-jdk15on-146.jar, 版本很多, 我用的是這個:http://download.csdn.net/detail/johnson106/3870999, 下載後, 把文件存到C:\codeproject.

            然後用Keytool 生成 keystore 文件.(keytool是 Java SDK 自帶的文件, 跟javac 放在同一個目錄下)在命令提示符窗口中輸入 keytool 就能看到這個工具的各種選項說明. 或者輸入下列路徑運行.

            "C:\Program Files (x86)\Java\jre7\bin>keytool".

            2)

            下面是用keytool 生成 keysotre 文件的命令.要是這個文件已經存在, 這一步可以忽略.

            keytool -genkey -alias codeproject -keystore C:\codeproject\codeprojectssl.keystore -validity 365

            這行命令創建一個別名為code project 的密鑰(key),生成的文件名是 codeprojectssl.keystore. 執行文件生成過程中會要求輸入密鑰(key)跟keystore的密碼諸如此類的東東. 這裡需要注意下,當要求你錄入 Common name的時候, 要填你的主機名(我的電腦-右鍵-屬性就可以看到). 本文例子用的是: codeproject.com

            3)

            keytool -export -alias codeproject -keystore C:\codeproject\codeprojectssl.keystore -file C:\codeproject\codeprojectsslcert.cer

            這行命令將密鑰(key)從 .keystore文件導入 .cer 文件.

            4)

            keytool -import -alias codeproject -file C:\codeproject\codeprojectsslcert.cer -keystore C:\codeproject\codeprojectssl.bks -storetype BKS -providerClass org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath C:\codeproject\bcprov-jdk15on-146.jar

            搞定! 現在, 全部.bks文件都生成了.稍後將這些文件復制到安卓應用中. 連接那些使用自簽名證書的服務器的時候會用到.

            第2步

            把.keystore文件復制到/androidappdir/res/raw/

            第3步

            創建一個新類: MyHttpClient, 繼承DefaultHttpClient 類. 這個新類在驗證SSL 證書的時候, 會自動加載我們自己創建的 keystore 文件, 而不是安卓自帶的那個.只要證書跟服務器匹配上了就沒問題. 代碼如下:

            importjava.io.InputStream;
            importjava.security.KeyStore;
            
            importandroid.content.Context;
            
            publicclassMyHttpClientextendsDefaultHttpClient{
            
            privatestaticContextcontext;
            
            publicstaticvoidsetContext(Contextcontext){
            MyHttpClient.context=context;
            }
            
            publicMyHttpClient(HttpParamsparams){
            super(params);
            }
            
            publicMyHttpClient(ClientConnectionManagerhttpConnectionManager,HttpParamsparams){
            super(httpConnectionManager,params);
            }
            
            @Override
            protectedClientConnectionManagercreateClientConnectionManager(){
            SchemeRegistryregistry=newSchemeRegistry();
            registry.register(newScheme("http",PlainSocketFactory.getSocketFactory(),80));
            
            //用我們自己定義的 SSLSocketFactory 在ConnectionManager 中注冊一個 443 端口 
            registry.register(newScheme("https",newSslSocketFactory(),443));
            returnnewSingleClientConnManager(getParams(),registry);
            }
            
            privateSSLSocketFactorynewSslSocketFactory(){
            try{
            //GetaninstanceoftheBouncyCastleKeyStoreformat
            KeyStoretrusted=KeyStore.getInstance("BKS");
            //從資源文件中讀取你自己創建的那個包含證書的 keystore 文件
            
            InputStreamin=MyHttpClient.context.getResources().openRawResource(R.raw.codeprojectssl);//這個參數改成你的keystore文件名
            try{
            //用 keystore 的密碼跟證書初始化 trusted 
            trusted.load(in,"這裡是你的 keystore 密碼".toCharArray());
            }finally{
            in.close();
            }
            //PassthekeystoretotheSSLSocketFactory.Thefactoryisresponsible
            //fortheverificationoftheservercertificate.
            SSLSocketFactorysf=newSSLSocketFactory(trusted);
            //Hostnameverificationfromcertificate
            //http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html#d4e506
            sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);//這個參數可以根據需要調整, 如果對主機名的驗證不需要那麼嚴謹, 可以將這個嚴謹程度調低些.
            returnsf;
            }catch(Exceptione){
            thrownewAssertionError(e);
            }
            }
            }

            MyHttpClient 類的調用代碼如下:

            //InstantiatethecustomHttpClient
            DefaultHttpClientclient=newMyHttpClient(getApplicationContext());
            HttpGetget=newHttpGet("https://www.google.com");
            //以 GET 方式讀取服務器返回的數據
            HttpResponsegetResponse=client.execute(get);
            HttpEntityresponseEntity=getResponse.getEntity();
             
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved