摘要:而且文章經過統計發現的應用程序沒有正確地在通信過程中進行證書驗證。確認服務器端證書的和代碼中的證書主體一致。根據開發安全的應用提出的觀點,可以避免最終用戶證書有效期可能比較短的問題。
轉載請注明出處 http://www.paraller.com
原文排版地址 點擊獲取更好閱讀體驗
轉載:http://xhrwang.me/2015/06/06/https-and-android.html 部分修改
背景Google 在 Android 6.0 Changes 中聲稱在 Android 6.0 系統中,移除了 Apache HttpClient 組件“
Android 6.0 release removes support for the Apache HTTP client. If your app is using this client and targets Android 2.3 (API level 9) or higher, use the HttpURLConnection class instead. This API is more efficient because it reduces network use through transparent compression and response caching, and minimizes power consumption. To continue using the Apache HTTP APIs, you must first declare the following compile-time dependency in your build.gradle file:
所以,為了適配最新的 Android 6.0 系統,作為開發者,如果之前使用了 Apache HttpClient 開發的網絡通信模塊,是時候使用 URLConnection 以及 Derived 類型來重新實現,畢竟調整后能得到上面 Google 提到的各種好處,而且不需要依賴一個額外的包。
在我們使用智能移動設備的時候,很多時候我們可能處于不安全的 Wifi 環境中,考慮到用戶數據在傳輸過程中的安全性,Https 協議越來越成為網絡通信的主流,但是使用 Https 的時候如果實現有誤,還是避免不了 MITMA (Man in the middle attack),國外有篇論文 Detection of SSL-related securityvulnerabilities in Androidapplications 對這種情況作了很好的總結,推薦大家看看。而且 SSL Vulnerabilities at Large 文章經過統計發現 73% 的 Android 應用程序沒有正確地在 Https 通信過程中進行證書驗證。
這篇文章就是想討論一下如何正確地使用 HttpsURLConnection 實現 Https 通信。除了本文,Android security - Implementation of Self-signed SSL certificate for your App. 也非常推薦,但遺憾的是該文最后給出的完整實例使用了 Apache HttpClient 的實現方式,不過該文對 Android 應用開發中如何正確處理 Https 通信做了非常好的分析。
Https 證書 證書鏈(Certificate Chains)我們一般常見的證書鏈分為兩種:
二級證書:直接由 受信任的根證書頒發機構 頒發的證書(CRT 文件),由于這種情況下一旦 Root CA 證書遭到破壞或者泄露,提供這個 Certificate Authority 的機構之前頒發的證書就全部失去安全性了,需要全部換掉,對這個 CA 也是毀滅性打擊,現在主流的商業 CA 都提供三級證書。
三級證書:由 受信任的根證書頒發機構 下的 中級證書頒發機構 頒發的證書,這樣 ROOT CA 就可以離線放在一個物理隔離的安全的地方,即使這個 CA 的中級證書被破壞或者泄露,雖然后果也很嚴重,但根證書還在,可以再生成一個中級證書重新頒發證書,而且這種情況對 HTTPS 的性能和證書安裝過程也沒有太大影響,這種方式也基本成為主流做法。
我們的互聯網就是運行在這個基于信任關系的基礎上,國際上的 受信任的根證書頒發機構 是有限的幾個機構,具體信息可以參考 維基百科的介紹。
如何獲得可用于 HTTPS 的證書生成使用 RSA 非對稱加密類型的私鑰,使用 DES3 算法,輸出 OpenSSL 格式,采用 2048 位強度。server.key是文件名,生成過程需要提供一個至少四位的密碼。
openssl genrsa -des3 -out server.key 2048
在部署支持 HTTPS 網站的時候 Web Server 每次啟動都需要提供使用的私鑰密碼,可以使用下面的命令去除剛生成的私鑰密碼:
openssl rsa -in server.key -out server_nopwd.key
openssl req -new -key server_nopwd.key -out server.csr
在這個過程中需要提供像國家、地區、組織名稱以及 E-mail 等信息,對于用于 HTTPS 的 CSR 來說,Common Name 必須和網站域名一致,以便之后進行 Host Name 校驗,所以 Common Name 的選擇比較重要,可以選擇 Single Name 或者 WildCard 類型的,二者區別可見 Choosing the SSL Certificate Common Name。上面的命令執行時會需要輸入一些信息:
Country Name (2 letter code) [AU]:CN State or Province Name (full name) [Some-State]:BJ Locality Name (eg, city) []:BJ Organization Name (eg, company) [Internet Widgits Pty Ltd]:OrgName Organizational Unit Name (eg, section) []:OrgUnit Common Name (eg, YOUR name) []:www.DOMAIN.com Email Address []:Name@DOMAIN.com
Self-Signed Certificate 自簽名證書,如果只是用于比如應用 API 接口,不需要用于可通過瀏覽器訪問的網頁展示,為了節省開支或者僅僅是內部使用可以考慮。
openssl x509 -req -days 365 -in server.csr -signkey server_nopwd.key -out server.crt
提交 CSR 文件并購買授信中級證書機構頒發的證書
openssl x509 -md5 -days 3560 -req -CA ca.crt -CAkey ca.key -CAcreateserial -CAserial ca.srl -in server.csr -out server.crt
有的 CA 簽名后會提供兩個 CRT 文件,一個是對提交的 CSR 文件簽名后的 CRT,一個是 CA 自己的 Intermediate CRT 文件,這個 CRT 文件是公開的,比如 Symantic 的常見 Intermediate CRT 就可以從 Symantec Class 3 Secure Server CA - G4 下載到,GoDaddy 的所有不同格式的 CRT 文件都公開在 GoDaddy Repository,為了讓配置的 Https 網站被瀏覽器認為是可信的,這兩個 CRT 文件可以合并后配置到 Web Server 中,這相當于在 Web Server 服務器上安裝了該 CA 的 Intermediate CRT 證書。合并方式可以參考 How do I make my own bundle file from CRT files?
使用 CRT 文件和 私鑰配置 HTTPS 網站服務
這里的具體操作方式取決于使用的 Web Server 類型,比如 Apache 和 Nginx 有自己的配置方法,就不列了,各個 Web Server 的文檔都有詳細說明,GoDaddy 的文檔 INSTALL SSL CERTIFICATES 對主流的 Web Server 如何進行 Https 配置作了非常全面的總結。
Https 網站這里我們可以使用一個可用于測試的 Python Flask 實現的 Https Web Server:
from OpenSSL import SSL from flask import Flask from flask import render_template app = Flask(__name__) context = SSL.Context(SSL.SSLv23_METHOD) context.use_privatekey_file("/Users/rwang/testssl/test.key") context.use_certificate_file("/Users/rwang/testssl/test.crt") @app.route("/") def hello(): return "Howdy, there!", 200 if __name__ == "__main__": app.run(host="127.0.0.1",port=8443, debug = True, ssl_context=context)
啟動這個測試服務器,就可以從瀏覽器中通過 https://127.0.0.1:8443 這個地址訪問了,如果你使用了 Self signed Certificate,瀏覽器會提示危險,選擇仍然繼續就可以看到 Howdy, there! 的歡迎語了。注意,如果需要通過在同一個局域網內的 Android 設備訪問這個測試服務器,請把 127.0.0.1 替換為本機在局域網內的 ip。
HttpsURLConnection這里分兩種情況來討論。
使用 Self signed Certificate
自簽名證書可以用于 Android 應用程序的 Api 接口通信中,按照 Certificate pinning with self-singed certificate 的說法,有下面的有點:
Increased security - with pinned SSL certificates, the app is independent of the device’s trust store. Compromising the hard coded trust store in the app is not so easy - the app would need to be decompiled, changed and then recompiled again - and it can’t be signed using the same Android keystore that the original developer of the app used.
**Reduced costs** - SSL certificate pinning gives you the possibility to use a self-signed certificate that can be trusted. For example, you’re developing an app that uses your own API server. You can reduce the costs by using a self-signed certificate on your server (and pinning that certificate in your app) instead of paying for a certificate. Although a bit convoluted, this way, you’ve actually improved security and saved yourself some money.
當然,文中也提到使用自簽名證書也有不足的地方:
Less flexibility - when you do SSL certificate pinning, changing the SSL certificate is not that easy. For every SSL certificate change, you have to make an update to the app, push it to Google Play and hope the users will install it.
使用自簽名證書,需要自定義 TrustManager:
class MyTrustManager implements X509TrustManager { X509Certificate cert; MyTrustManager(X509Certificate cert) { this.cert = cert; } @Override // for server only public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { // 我們在客戶端只做服務器端證書校驗。 } @Override // only trust the given certificate or certificate issued by it public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { // 確認服務器端證書和代碼中 hard code 的 CRT 證書相同。 if (chai[0].equals(this.cert)){ if(Utils.DEBUG){ Log.i(Utils.DEBUG_TAG, "checkServerTrusted Certificate from server is valid!"); } return;// found match } throw new CertificateException("checkServerTrusted No trusted server cert found!"); } @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } }
然后使用其定義 SSLContext 實例:
SSLContext sc = SSLContext.getInstance("TLS"); TrustManager tm = new MyTrustManager(readCert(certStr)); sc.init(null, new TrustManager[]{ tm }, null); `
` private static X509Certificate readCert(String cer) {
if (cer == null || cer.trim().isEmpty())
return null;
InputStream caInput = new ByteArrayInputStream(cer.getBytes());
X509Certificate cert = null;
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
cert = (X509Certificate) cf.generateCertificate(caInput);
} catch (Exception e) {
if (Utils.DEBUG) {
e.printStackTrace();
}
} finally {
try {
if (caInput != null) {
caInput.close();
}
} catch (Throwable ex) {
}
}
return cert;
}
最后使用 HttpsURLConnection 進行網絡通信:
HttpsURLConnection urlConnection = (HttpsURLConnection)url.openConnection(); conn.setSSLSocketFactory(sc.getSocketFactory());
// By default, this implementation of HttpURLConnection requests that servers use gzip compression
// and it automatically decompresses the data for callers of getInputStream().
// The Content-Encoding and Content-Length response headers are cleared in this case.
// Gzip compression can be disabled by setting the acceptable encodings in the request header:
// http://developer.android.com/reference/java/net/HttpURLConnection.html
conn.setRequestProperty("Accept-Encoding", "");
StringBuffer response = new StringBuffer();
OutputStream os = null;
BufferedReader rd = null;
InputStream is = null;
int statusCode = -1;
try {
conn.setRequestMethod("POST");
os = conn.getOutputStream();
os.write(payload);
os.close();
// Get Response
statusCode = conn.getResponseCode();
InputStream is;
if(statusCode > HttpURLConnection.HTTP_BAD_REQUEST){
is = conn.getErrorStream();
}else{
is = conn.getInputStream();
}
rd = new BufferedReader(new InputStreamReader(is));
String line;
while ((line = rd.readLine()) != null) {
response.append(line);
response.append("
");
}
} catch (Throwable e) {
if (Utils.DEBUG) {
e.printStackTrace();
}
} finally {
try {
if (is != null) {
is.close();
}
} catch (Throwable ex) {
}
try {
if (os != null) {
os.close();
}
} catch (Throwable ex) {
}
try {
if (rd != null) {
rd.close();
}
} catch (Throwable ex) {
}
try {
if (conn != null) {
conn.disconnect();
}
} catch (Throwable ex) {
}
}
需要注意的是:
conn.setRequestProperty(“Accept-Encoding”, “”); 如果自己實現了壓縮算法,不希望系統自動添加 gzip 壓縮的話必須添加。詳情請參考上面代碼注釋中的文檔。
conn.setSSLSocketFactory(sc.getSocketFactory()); 這樣設置后,自定義的 SSLContext 將被用于本次 Https 通信,不會影響應用中其他可能的 Https 通信。
使用經過 CA 認證的證書
如果服務器端正確配置了使用 CA 認證后的證書,Android 客戶端應用程序可以直接使用 `HTTPSURLConnection` 訪問:
URL url = new URL("https://www.example.com/"); HttpsURLConnection urlConnection = (HttpsURLConnection)url.openConnection(); InputStream in = urlConnection.getInputStream();
如果需要做更嚴格的驗證,也可以這樣自定義 TrustManager:
class MyTrustManager implements X509TrustManager {
X509Certificate cert;
MyTrustManager(X509Certificate cert) {
this.cert = cert;
}
@Override
// for server only
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
// 我們在客戶端只做服務器端證書校驗。
}
@Override
// only trust the given certificate or certificate issued by it
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
// 確認服務器端證書的 Intermediate CRT 和代碼中 hard code 的 CRT 證書主體一致。
if (!chain[0].getIssuerDN().equals(certificate.getSubjectDN())) {
throw new CertificateException("Parent certificate of server was different than expected signing certificate");
}
try {
// 確認服務器端證書被代碼中 hard code 的 Intermediate CRT 證書的公鑰簽名。
chain[0].verify(certificate.getPublicKey());
// 確認服務器端證書沒有過期
chain[0].checkValidity();
} catch (Exception e) {
throw new CertificateException("Parent certificate of server was different than expected signing certificate");
}
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
思路就是在代碼中 hard code CA 認證后交付的 Intermediate CRT 文件作為客戶端證書對服務器端證書進行校驗。根據 開發安全的Android應用 提出的觀點,可以避免最終用戶證書有效期可能比較短的問題。
注釋:
證書失效可以根據Intermediate CRT 重新續期
之后使用 HttpsURLConnection 的方式和使用自簽名證書時相同就不特別說明了。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/11304.html
摘要:不同的前端框架,配合等打包工具,可以更高效的使用這些插件,完成移動端適配的配置工作。 簡介 【目標】:前端開發移動端及H5時候,不需要再關心移動設備的大小,只需要按照固定設計稿的px值布局!【基礎】 dpr(設備像素比)css的像素px不等于設備像素/分辨率/各種值,css的px可以簡單理解為虛擬像素,與設備無關,css的px需要乘dpr計算為設備像素; css3 的 rem,即ro...
摘要:官方文檔演示地址請在移動端查看,端查看請打開移動端調試模式前言看了挺多的框架都不帶過渡動畫,今天心血來潮,就把自己平時用的動效抽離出來。原理模版中使用了提供的封裝組件,配合類名在的六種不同的狀態過渡中切換。 官方文檔:https://cn.vuejs.org/v2/guide... 演示地址:http://www.coderlife.com (請在移動端查看,PC端查看請打開移動端調試...
摘要:轉自偽類實現占位符交互效果一規范中占位符交互效果風格占位符交互效果官方示意見此頁面。我們可以采用絕對定位最后,對這個元素在輸入框時候,以及非顯示的時候進行重定位縮小并位移到上方四清除按鈕部分上是必要屬性,配合偽類實現我們的效果。 轉自: https://github.com/yougola/bl... CSS :placeholder-shown偽類實現Material Design占...
摘要:基于的動態數據管理神器介紹什么是基于模塊化的動態數據管理平臺。什么是用于動態生成表單的,參考使用案例官方文檔使用場景有哪些無論前端后端移動端運維,理論上所有需要動態配置數據的場景都可以使用。針對運維可以作為區分環境的配置中心等。 基于Json Schema的動態數據管理神器-DMS 介紹 什么是DMS? DMS Github:基于Json Schema/UI Schema模塊化的Jso...
閱讀 1111·2021-11-23 09:51
閱讀 1076·2021-10-18 13:31
閱讀 2976·2021-09-22 16:06
閱讀 4263·2021-09-10 11:19
閱讀 2202·2019-08-29 17:04
閱讀 430·2019-08-29 10:55
閱讀 2477·2019-08-26 16:37
閱讀 3375·2019-08-26 13:29