網絡安全已成爲你們最關心的問題. 若是你利用服務器存儲客戶資料, 那你應該考慮使用 SSL 加密客戶跟服務器之間的通信. 隨着這幾年手機應用迅速崛起. 黑客也開始向手機應用轉移, 緣由有下列3點:php
手機系統各式各樣, 缺少統一的標準.html
許多程序員缺少手機應用開發經驗.java
更嚴重的是, 經過手機應用, 黑客能夠獲得手機用戶的隱私數據, 如:日程安排, 聯繫人信息, 網頁瀏覽歷史記錄, 我的資料, 社交數據, 短信或者手機用戶所在的地理位置.android
最爲一個網絡安全愛好者的我, 最近花了幾個月的時間對50到60安卓應用進行安全分析, 結果發現這些應用存在許多安全漏洞.程序員
下面我主要講一講, 怎樣才能寫出比較安全的安卓代碼.web
從最基本的開始講.apache
閱讀本文前, 最好先看下 Ranjan.D article 寫的一篇跟安卓鏈接有關的文章:(http://www.codeproject.com/Articles/818734/Article-Android-Connectivity).編程
下列代碼用來打開一個 http 鏈接.api
URL urlConnection = new URL("http://www.codeproject.com/"); HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
不要在 http鏈接中打開:登錄頁面, 或是傳遞用戶名, 密碼, 銀行卡之類的重要我的資料. 這些重要我的數據應該經過 HTTPS 傳輸. (具體參看HTTPS).數組
HTTPS 其實就是個安全版的 http. HTTPS 能保證電子商務的交易安全. 如:網上銀行.
像 IE 或者火狐瀏覽器, 若是出現下面的掛鎖圖標.
同時, 在瀏覽器的地址欄中以 https:// 開頭, 這表示, 你的瀏覽器跟這個網站的數據往來都是安全的.
https 跟 http 的最大區別在於 https 多加了一個保障通信安全的層.
像下列代碼這樣打開一個 https 鏈接, 能夠保障這個鏈接的數據通信安全.
URL urlConnection = new URL("https://www.codeproject.com/"); HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection(); InputStream in = new BufferedInputStream(urlConnection.getInputStream());
HTTPS 經過 SSL/TLS 傳遞數據.
SSL (Secure Sockets Layer) 是一種在客戶端跟服務器端創建一個加密鏈接的安全標準. 通常用來加密網絡服務器跟瀏覽器, 或者是郵件服務器跟郵件客戶端(如: Outlook)之間傳輸的數據.
SSL 能保障敏感信息(如:銀行卡號, 社保卡號, 登錄憑證等)的傳輸安全. 通常狀況下, 數據在瀏覽器跟服務器之間的傳輸使用的是明文格式, 這種方式存在資料被竊取的風險. 若是黑客能攔截瀏覽器跟服務器之間的通信數據, 就能看到通信的內容.
你要是對 SSL 或 X.509 證書一無所知, 那我大概解釋下. 對於那些打算用自簽名證書(self-signed certificate)的人來講, 須要瞭解自簽名證書跟花錢購買機構頒發的證書有什麼區別.
首先咱們須要瞭解下 SSL 證書到底是個什麼東東? 其實它就包含倆部分: 1) 一個身份標識, 一個用來識別身份的東西, 有點相似警察叔叔經過護照或駕照查你的身份; 2) 一個公共密鑰, 這個用來給數據加密, 並且只有證書的持有者才能解密. 簡而言之, SSL 證書就倆個功能, 身份驗證跟保障通信過程當中的數據安全.
另外還有一點很重要. 那就是一個證書能夠給另一個證書「簽字」. 用 layman 的話說就是 Bob 用他本身的證書在別的證書上蓋上 「贊成」 兩個紅紅的大字. 若是你信任 Bob (固然還有他的證書), 那麼你也能夠信任由他簽發的證書. 在這個例子中, Bob 搖身一變, 成了證書頒發機構(Certificate Authority). 如今主流的瀏覽器都自帶一大堆受信任證書頒發機構(trusted Certificate Authorities)(好比:Thawte, Verisign等).
最後咱們講一講瀏覽器是怎麼使用證書的. 籠統的講, 當你打開下列鏈接的時候 「https://www.yoursite.com」 :
服務器會給瀏覽器發一個證書.
瀏覽器會對比證書中的「common name」(有時也叫 「subject」) 跟服務器的域名是否同樣. 例如, 一個從「www.yoursite.com」 網站發過來的證書就應該有一個內容是 「www.yoursite.com」 的 common name, 不然瀏覽器就會提示該證書有問題.
瀏覽器驗證證書真僞, 有點像門衛經過證件上的全息圖辨別你的證件是否是真的. 既然在現實生活中有人僞造別人的身份. 那麼在網絡世界也就有人造假, 好比用你的域名「www.yoursite.com」 來僞造一個安全證書. 瀏覽器在驗證的時候, 會檢查這個證書是不是它信任機構頒發的, 若是不是, 那麼瀏覽器就會提示這個證書可能有問題. 固然, 用戶能夠選擇無視警告, 繼續使用.
一旦證書經過驗證 (或是用戶無視警告, 繼續使用有問題的證書), 瀏覽器就開始利用證書中的公開密鑰加密數據並傳給服務器.
一旦服務器發過來的證書經過驗證, 瀏覽器就會利用證書中包含的公共密鑰加密某個指定的共享密鑰, 而後發給服務器. 這個加密過的共享密鑰只能用服務器的私有密鑰才能解密(非對稱加密), 別人沒法解密出其中的內容. 服務器把解密出來的共享密鑰保存起來, 供本次鏈接會話專用. 從如今開始, 服務器跟瀏覽器之間的全部通信信息都用這個共享密鑰加密解密(對稱加密).
理論部分就這麼多, 下面咱們來看幾個例子.
在瀏覽器中打開網站 mail.live.com , 地址欄中會出現一個綠色圖標, http 也會變成 https.
單擊這個綠色圖標, 而後點證書信息鏈接, 就能看到下列內容.
這是個 SSL 證書, 該證書是 Verisign 給 mail.live.cm 頒發的.
Verisign 是一個證書頒發機構, 它提示你的瀏覽器正在鏈接的網站是: mail.live.com, 須要跟這個網站的服務器創建一條安全鏈接進行通信, 避免他人攔截或篡改瀏覽器跟服務器之間傳遞的數據.
MITM 攻擊(MITMA)是指: 黑客攔截篡改網絡中的通信數據
被動 MITMA 是指黑客只能竊取通信數據, 而在主動 MITMA 中, 黑客除了竊取數據, 還能篡改通信數據. 黑客利用 MITMA 方式攻擊手機要比攻擊臺式電腦容易的多. 這主要是由於使用手機的環境在不固定, 有些地方用手機鏈接上網並不安全, 尤爲是那些對公衆免費開放的無線網絡熱點.
證書頒發機構(CA)
·Symantec (which bought VeriSign's SSL interests and owns Thawte and Geotrust) 38.1% 市場份額
·Comodo SSL 29.1%
·Go Daddy 13.4%
·GlobalSign 10%
Jelly bean 版本的安卓系統中, 你能夠在下列路徑中找到證書頒發機構:
設置 -> 安全 -> 受信任的憑證.
Https 鏈接
URL url = new URL("https://www.example.com/"); HttpsURLConnection urlConnection = (HttpsURLConnection)url.openConnection(); InputStream in = urlConnection.getInputStream();
若是你鏈接的服務器(www.example.com)傳過來的證書是由機構頒發的, 這段代碼就能正常運行.
可是若是你連的服務器用的是本身頒發的證書(self-singed certificate), 那就會出現錯誤.
自簽名證書就是沒有經過受信任的證書頒發機構, 本身給本身頒發的證書.
SSL 證書大體分三類:
由安卓承認的證書頒發機構(如: VeriSign), 或這些機構的下屬機構頒發的證書.
沒有獲得安卓承認的證書頒發機構頒發的證書.
本身頒發的證書, 分臨時性的(在開發階段使用)或在發佈的產品中永久性使用的兩種.
只有第一種, 也就是那些被安卓系統承認的機構頒發的證書, 在使用過程當中不會出現安全提示.
免費. 購買受信任機構頒發的證書每一年要交 100 到 500 美圓不等的費用. 自簽名證書不花一分錢.
自簽名證書在手機應用中的普及率較高 (跟用電腦瀏覽網頁不一樣, 手機的應用通常就固定連一臺服務器.).
在開發階段寫的代碼, 測試跟發佈的時候也能夠用.
最近一項調查代表, 810萬個證書中, 只有 320萬個是由受信任機構頒發的. 剩餘490萬證書中, 自簽名的佔48%, 未知機構頒發的佔33%, 而不被信任的機構頒發的證書佔19%.
無獨有偶, 個人分析結果也代表, 起碼有 60% 安卓應用使用自簽證書.
我的覺得, 在手機應用中使用自簽名證書沒什麼很差, 既不須要花錢, 也不須要修改代碼.(注:若是你用的是機構頒發的證書, 在產品發佈階段, 須要修改代碼).
可是下面的戲法的通常性的https代碼
URL url = new URL("https://www.example.com/"); HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection(); InputStream in = urlConnection.getInputStream();
若是你使用上述的代代碼去驗證你的本身簽署的證書,因爲在android操做系統中本身簽署的不能經過驗證的,因此安卓應用軟件將會拋出錯誤。所以你須要書寫你本身的代碼來檢查你的本身簽署的證書。
可是在這個領域中,安卓開發者犯了一個很大的錯誤,本身簽署的證書在web開發中不是常見的,同時大多數安卓開發者來自於web開發者,因此開發者缺失在密碼學的概念的知識。
在我分析中,我發現開發者僅僅是簡單的複製、粘貼Stack Overflow和其餘博客中容許你的應用默認信任因此證書的答案。即便大多說的答案表述僅僅在測試模式下可使用,可是開發者簡單地複製代碼,將會致使 應用軟件在遭到中間件攻擊和session黑客攻擊是,表現的很是的脆弱!
例子:
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/#toc_3
http://stackoverflow.com/questions/2642777/trusting-all-certificates-using-httpclient-over-https
在使用本身簽署的證書時通常性的錯誤
信任因此的證書
TrustManager的主要責任是去決定提出的認證證書應該是可信任的。若是證書是不可信任的,那麼鏈接將會被終止。去認證遠程的安全套接字識別,你須要用一個或者多個TrustManager(s)初始化SSLContext對象。
import org.apache.http.conn.ssl.SSLSocketFactory; public class MySSLSocketFactory extends SSLSocketFactory { SSLContext sslContext = SSLContext.getInstance("TLS"); public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException { super(truststore); TrustManager tm = new X509TrustManager() { public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } public X509Certificate[] getAcceptedIssuers() { return null; } }; sslContext.init(null, new TrustManager[] { tm }, null); } @Override public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException { return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose); } @Override public Socket createSocket() throws IOException { return sslContext.getSocketFactory().createSocket(); } }
我發現,80到100個應用軟件中,在20到25個應用中實現了上述的代碼。
在上述的TrustManager接口中,能夠實現信任因此的證書,不管是誰簽署的或者即便他們發佈的任何主題。這個接口將會容許接受ANY證書。接受任何的證書將會危害數據的完整性、安全性等等。
在上述的例子中,檢查客戶端可信任性,得到接受事件,檢查服務器端可信任性是三點重要的功能。每一位開發者都應該留意這三點功能的實現上。可是卻不多有開發者從不一樣的網站中搜索、負責上述的功能。
忘記檢查證書是否在這個地址發佈是有可能的。當證書接受了example.com的服務器,那麼另外的一個域名也將被接受。
HostnameVerifier hostnameVerifier = org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER; DefaultHttpClient client = new DefaultHttpClient(); SchemeRegistry registry = new SchemeRegistry(); SSLSocketFactory socketFactory = SSLSocketFactory.getSocketFactory(); socketFactory.setHostnameVerifier((X509HostnameVerifier) hostnameVerifier); registry.register(new Scheme("https", socketFactory, 443)); SingleClientConnManager mgr = new SingleClientConnManager(client.getParams(), registry); DefaultHttpClient httpClient = new DefaultHttpClient(mgr, client.getParams()); // Set verifier HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier); // Example send http request final String url = "https://www.paypal.com” HttpPost httpPost = new HttpPost(url); HttpResponse response = httpClient.execute(httpPost); HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);
HostnameVerifier hostnameVerifier = org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER;
上述的代碼中,即便是錯誤的實現,也將會接受對任何域名的任何CA證書聲明。
應用軟件開發者在相同的應用中使用最大安全、不安全的鏈接,或者不使用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 或者 keystore, 須要用到下面這個文件,bcprov-jdk15on-146.jar, 版本不少, 我用的是這個:http://www.bouncycastle.org/download/bcprov-jdk15on-146.jar, 下載後, 把文件存到 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 文件都生成了. 稍後將這些文件複製到安卓應用中. 鏈接那些使用自簽名證書的服務器的時候會用到.
把 .keystore 文件複製到 /androidappdir/res/raw/
建立一個新類: MyHttpClient, 繼承 DefaultHttpClient 類. 這個新類在驗證SSL 證書的時候, 會自動加載咱們本身建立的 keystore 文件, 而不是安卓自帶的那個. 只要證書跟服務器匹配上了就沒問題. 代碼以下:
import java.io.InputStream; import java.security.KeyStore; import android.content.Context; public class MyHttpClient extends DefaultHttpClient { private static Context context; public static void setContext(Context context) { MyHttpClient.context = context; } public MyHttpClient(HttpParams params) { super(params); } public MyHttpClient(ClientConnectionManager httpConnectionManager, HttpParams params) { super(httpConnectionManager, params); } @Override protected ClientConnectionManager createClientConnectionManager() { SchemeRegistry registry = new SchemeRegistry(); registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); // 用咱們本身定義的 SSLSocketFactory 在 ConnectionManager 中註冊一個 443 端口 registry.register(new Scheme("https", newSslSocketFactory(), 443)); return new SingleClientConnManager(getParams(), registry); } private SSLSocketFactory newSslSocketFactory() { try { // Get an instance of the Bouncy Castle KeyStore format KeyStore trusted = KeyStore.getInstance("BKS"); // 從資源文件中讀取你本身建立的那個包含證書的 keystore 文件 InputStream in = MyHttpClient.context.getResources().openRawResource(R.raw.codeprojectssl); //這個參數改爲你的 keystore 文件名 try { // 用 keystore 的密碼跟證書初始化 trusted trusted.load(in, "這裏是你的 keystore 密碼".toCharArray()); } finally { in.close(); } // Pass the keystore to the SSLSocketFactory. The factory is responsible // for the verification of the server certificate. SSLSocketFactory sf = new SSLSocketFactory(trusted); // Hostname verification from certificate // http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html#d4e506 sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER); // 這個參數能夠根據須要調整, 若是對主機名的驗證不須要那麼嚴謹, 能夠將這個嚴謹程度調低些. return sf; } catch (Exception e) { throw new AssertionError(e); } } }
MyHttpClient 類的調用代碼以下:
// Instantiate the custom HttpClient DefaultHttpClient client = new MyHttpClient(getApplicationContext()); HttpGet get = new HttpGet("https://www.google.com"); // 以 GET 方式讀取服務器返回的數據 HttpResponse getResponse = client.execute(get); HttpEntity responseEntity = getResponse.getEntity();
這是我在 CodeProject 上的發佈的處女做.
祝你們開心編程, 安全第一 .
http://www.thoughtcrime.org/blog/authenticity-is-broken-in-ssl-but-your-app-ha/
http://security.stackexchange.com/questions/29988/what-is-certificate-pinning
https://www.owasp.org/index.php/Certificate_and_Public_Key_Pinning
https://tools.ietf.org/html/draft-ietf-websec-key-pinning-20
https://media.blackhat.com/bh-us12/Turbo/Diquet/BH_US_12_Diqut_Osborne_Mobile_Certificate_Pinning_Slides.pdf
http://docs.oracle.com/javase/6/docs/technotes/guides/security/jsse/JSSERefGuide.html#HowSSLWorks