Android HTTPS、TLS版本支援相關解決方案
轉自:http://blog.csdn.net/s003603u/article/details/53907910
前言
在網際網路安全通訊方式上,目前用的最多的就是https配合ssl和數字證書來保證傳輸和認證安全
簡介
- 結合okhttp實現https訪問,並解決其中遇到的問題
- okhttp預設情況下是支援https協議的,不過要注意的是,支援https的網站如果是CA機構頒發的證書,預設情況下是可以信任的。
HTTPS相關
名詞解釋
- https:在http(超文字傳輸協議)基礎上提出的一種安全的http協議,因此可以稱為安全的超文字傳輸協議。http協議直接放置在TCP協議之上,而https提出在http和TCP中間加上一層加密層。從傳送端看,這一層負責把http的內容加密後送到下層的TCP,從接收方看,這一層負責將TCP送來的資料解密還原成http的內容。
- SSL(Secure Socket Layer):是Netscape公司設計的主要用於WEB的安全傳輸協議。從名字就可以看出它在https協議棧中負責實現上面提到的加密層。因此,一個https協議棧大致是這樣的:
- 補充:IP(網路層)、TCP(傳輸層),HTTP(應用層),SSL處於TCP和HTTP之間
- 數字證書:一種檔案的名稱,好比一個機構或人的簽名,能夠證明這個機構或人的真實性。其中包含的資訊,用於實現上述功能。
- 加密和認證:加密是指通訊雙方為了防止敏感資訊在通道上被第三方竊聽而洩漏,將明文通過加密變成密文,如果第三方無法解密的話,就算他獲得密文也無能為力;認證是指通訊雙方為了確認對方是值得信任的訊息傳送或接受方,而不是使用假身份的騙子,採取的確認身份的方式。只有同時進行了加密和認真才能保證通訊的安全,因此在SSL通訊協議中這兩者都很重要。
因此,這三者的關係已經十分清楚了:https依賴一種實現方式,目前通用的是SSL,數字證書是支援這種安全通訊的檔案。另外有SSL衍生出TLS和WTLS,前者是IEFT將SSL標準化之後產生的(TLS1.0),與SSL差別很小,後者是用於無線環境下的TSL。
圖解HTTPS協議加密解密全過程
我們都知道HTTPS能夠加密資訊,以免敏感資訊被第三方獲取。所以很多銀行網站或電子郵箱等等安全級別較高的服務都會採用HTTPS協議。
HTTPS其實是由兩部分組成:HTTP + SSL / TLS,也就是在HTTP上又加了一層處理加密資訊的模組。服務端和客戶端的資訊傳輸都會通過TLS進行加密,所以傳輸的資料都是加密後的資料。具體是如何進行加密,解密,驗證的,且看下圖。
- 客戶端發起HTTPS請求
這個沒什麼好說的,就是使用者在瀏覽器裡輸入一個https網址,然後連線到server的443埠。- 服務端的配置
採用HTTPS協議的伺服器必須要有一套數字證書,可以自己製作,也可以向組織申請。區別就是自己頒發的證書需要客戶端驗證通過,才可以繼續訪問,而使用受信任的公司申請的證書則不會彈出提示頁面(startssl就是個不錯的選擇,有1年的免費服務)。這套證書其實就是一對公鑰和私鑰。如果對公鑰和私鑰不太理解,可以想象成一把鑰匙和一個鎖頭,只是全世界只有你一個人有這把鑰匙,你可以把鎖頭給別人,別人可以用這個鎖把重要的東西鎖起來,然後發給你,因為只有你一個人有這把鑰匙,所以只有你才能看到被這把鎖鎖起來的東西。- 傳送證書
這個證書其實就是公鑰,只是包含了很多資訊,如證書的頒發機構,過期時間等等。- 客戶端解析證書
這部分工作是有客戶端的TLS來完成的,首先會驗證公鑰是否有效,比如頒發機構,過期時間等等,如果發現異常,則會彈出一個警告框,提示證書存在問題。如果證書沒有問題,那麼就生成一個隨機值。然後用證書對該隨機值進行加密。就好像上面說的,把隨機值用鎖頭鎖起來,這樣除非有鑰匙,不然看不到被鎖住的內容。- 傳送加密資訊
這部分傳送的是用證書加密後的隨機值,目的就是讓服務端得到這個隨機值,以後客戶端和服務端的通訊就可以通過這個隨機值來進行加密解密了。- 服務端解密資訊
服務端用私鑰解密後,得到了客戶端傳過來的隨機值(私鑰),然後把內容通過該值進行對稱加密。所謂對稱加密就是,將資訊和私鑰通過某種演算法混合在一起,這樣除非知道私鑰,不然無法獲取內容,而正好客戶端和服務端都知道這個私鑰,所以只要加密演算法夠彪悍,私鑰夠複雜,資料就夠安全。- 傳輸加密後的資訊
這部分資訊是服務端用私鑰加密後的資訊,可以在客戶端被還原。- 客戶端解密資訊
客戶端用之前生成的私鑰解密服務端傳過來的資訊,於是獲取瞭解密後的內容。整個過程第三方即使監聽到了資料,也束手無策。
異常解決
問題描述
在4.x系統上通過HTTPS進行訪問產生如下異常:
- javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0x65bc0ad8: Failure in SSL library, usually a protocol error
error:1407743E:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1 alert inappropriate fallback (external/openssl/ssl/s23_clnt.c:744 0x5cf4ed74:0x00000000)
原因
Android4.x系統對TLS的支援存在版本差異,具體細節請看以下分析
分析
首先我們檢視一下Google關於SSLEngine的官方文件說明
這裡擷取不同Android版本針對於TLS協議的預設配置圖如下:
從上圖可以得出如下結論:
- TLSv1.0從API 1+就被預設開啟
- TLSv1.1和TLSv1.2只有在API 20+ 才會被預設開啟
- 也就是說低於API 20+的版本是預設關閉對TLSv1.1和TLSv1.2的支援,若要支援則必須自己開啟
有了以上關於Android SSLEngine相關知識的鋪墊,讓我們來測試一下這次目標案例的域名 fort.sports.baofeng.com
我們可以在QUALYS SSL LABS測試它對ssl支援的版本
這裡擷取SSL報告中對我們有用的一部分,如下圖
- 剛開始伺服器配置只支援TLS1.2,SSL報告的結果也驗證了這一點
- 可以看出大部分2.x、4.x的Android系統都會報Server sent fatal alert:handshake_failure,而5.0、6.0、7.0的Android系統在Hanshake Simulation中表現正常,因為它們支援TLS1.2
這就能解釋為什麼大部分4.xAndroid系統在進行HTTPS訪問時產生上述異常
解決方案
- 我們首先想到的是,可以讓伺服器配置相容支援TLS1.0、TLS1.1、TLS1.2,這樣客戶端就不需要做任何處理,完美相容
- 經過測試,這個是可以的(測試手機包括Lenovo K920 4.4.2,Lenovo K30-E 4.4.4)
我們再次檢視SSL報告中的幾個關鍵結果:
從上圖可以看出,伺服器配置已經可以支援TLS1.0、TLS1.1、TLS1.2
從下圖可以看出,Handshake Simulation在Android 4.x系統也可以正常運作了
或許,你以為這樣就完美了,但是,你有沒有想到過這樣一種情況,當你所訪問的域名伺服器只支援TLS1.2,那Android4.x系統應該如何應對那
答案:想辦法讓Android4.x開啟對TLS1.1、TLS1.2的支援
- 假設你的網路請求庫使用的是okhttp,在APP中可以這樣初始化OkHttpClient,這裡通過在AppParams中配置isBypassAuthen,來判斷是否繞過認證,也就是無條件信任所有HTTPS網站
- 這裡只是 單向認證 客戶端對服務端證書的單向認證
private void initHttpsClient() {
OkHttpClient.Builder builder = new OkHttpClient.Builder() .connectTimeout(30000L, TimeUnit.MILLISECONDS)
.readTimeout(30000L, TimeUnit.MILLISECONDS)
.addInterceptor(new LoggerInterceptor("OkHttpClient"))
.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
if(AppParams.isBypassAuthen){
HttpsUtils.SSLParams sslParams = HttpsUtils.getSslSocketFactory(null, null, null);
builder.sslSocketFactory(sslParams.sSLSocketFactory, sslParams.trustManager);
}else{
SSLContext sslContext = null;
try {
sslContext = SSLContext.getInstance("TLS");
try {
sslContext.init(null, null, null);
} catch (KeyManagementException e) {
e.printStackTrace();
}
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
SSLSocketFactory socketFactory = new Tls12SocketFactory(sslContext.getSocketFactory());
builder.sslSocketFactory(socketFactory,new HttpsUtils.UnSafeTrustManager());
}
OkHttpClient okHttpClient = builder
.build();
OkHttpUtils.initClient(okHttpClient);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
具體怎麼使用HTTPS,參考HttpsUtils:
public class HttpsUtils
{
public static class SSLParams
{
public SSLSocketFactory sSLSocketFactory;
public X509TrustManager trustManager;
}
public static SSLParams getSslSocketFactory(InputStream[] certificates, InputStream bksFile, String password)
{
SSLParams sslParams = new SSLParams();
try
{
TrustManager[] trustManagers = prepareTrustManager(certificates);
KeyManager[] keyManagers = prepareKeyManager(bksFile, password);
SSLContext sslContext = SSLContext.getInstance("TLS");
X509TrustManager trustManager = null;
if (trustManagers != null)
{
trustManager = new MyTrustManager(chooseTrustManager(trustManagers));
} else
{
trustManager = new UnSafeTrustManager();
}
sslContext.init(keyManagers, new TrustManager[]{trustManager},null);
sslParams.sSLSocketFactory = sslContext.getSocketFactory();
sslParams.trustManager = trustManager;
return sslParams;
} catch (NoSuchAlgorithmException e)
{
throw new AssertionError(e);
} catch (KeyManagementException e)
{
throw new AssertionError(e);
} catch (KeyStoreException e)
{
throw new AssertionError(e);
}
}
private class UnSafeHostnameVerifier implements HostnameVerifier
{
@Override
public boolean verify(String hostname, SSLSession session)
{
return true;
}
}
public static class UnSafeTrustManager implements X509TrustManager
{
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException
{
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException
{
}
@Override
public X509Certificate[] getAcceptedIssuers()
{
return new java.security.cert.X509Certificate[]{};
}
}
private static TrustManager[] prepareTrustManager(InputStream... certificates)
{
if (certificates == null || certificates.length <= 0) return null;
try
{
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
int index = 0;
for (InputStream certificate : certificates)
{
String certificateAlias = Integer.toString(index++);
keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));
try
{
if (certificate != null)
certificate.close();
} catch (IOException e)
{
}
}
TrustManagerFactory trustManagerFactory = null;
trustManagerFactory = TrustManagerFactory.
getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
return trustManagers;
} catch (NoSuchAlgorithmException e)
{
e.printStackTrace();
} catch (CertificateException e)
{
e.printStackTrace();
} catch (KeyStoreException e)
{
e.printStackTrace();
} catch (Exception e)
{
e.printStackTrace();
}
return null;
}
private static KeyManager[] prepareKeyManager(InputStream bksFile, String password)
{
try
{
if (bksFile == null || password == null) return null;
KeyStore clientKeyStore = KeyStore.getInstance("BKS");
clientKeyStore.load(bksFile, password.toCharArray());
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(clientKeyStore, password.toCharArray());
return keyManagerFactory.getKeyManagers();
} catch (KeyStoreException e)
{
e.printStackTrace();
} catch (NoSuchAlgorithmException e)
{
e.printStackTrace();
} catch (UnrecoverableKeyException e)
{
e.printStackTrace();
} catch (CertificateException e)
{
e.printStackTrace();
} catch (IOException e)
{
e.printStackTrace();
} catch (Exception e)
{
e.printStackTrace();
}
return null;
}
private static X509TrustManager chooseTrustManager(TrustManager[] trustManagers)
{
for (TrustManager trustManager : trustManagers)
{
if (trustManager instanceof X509TrustManager)
{
return (X509TrustManager) trustManager;
}
}
return null;
}
private static class MyTrustManager implements X509TrustManager
{
private X509TrustManager defaultTrustManager;
private X509TrustManager localTrustManager;
public MyTrustManager(X509TrustManager localTrustManager) throws NoSuchAlgorithmException, KeyStoreException
{
TrustManagerFactory var4 = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
var4.init((KeyStore) null);
defaultTrustManager = chooseTrustManager(var4.getTrustManagers());
this.localTrustManager = localTrustManager;
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException
{
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException
{
try
{
defaultTrustManager.checkServerTrusted(chain, authType);
} catch (CertificateException ce)
{
localTrustManager.checkServerTrusted(chain, authType);
}
}
@Override
public X509Certificate[] getAcceptedIssuers()
{
return new X509Certificate[0];
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
自行實現SSLSocketFactory ,實現對TLSv1.1、TLSv1.2的支援
public class Tls12SocketFactory extends SSLSocketFactory {
private static final String[] TLS_SUPPORT_VERSION = {"TLSv1.1", "TLSv1.2"};
final SSLSocketFactory delegate;
public Tls12SocketFactory(SSLSocketFactory base) {
this.delegate = base;
}
@Override
public String[] getDefaultCipherSuites() {
return delegate.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return delegate.getSupportedCipherSuites();
}
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
return patch(delegate.createSocket(s, host, port, autoClose));
}
@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
return patch(delegate.createSocket(host, port));
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
return patch(delegate.createSocket(host, port, localHost, localPort));
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
return patch(delegate.createSocket(host, port));
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
return patch(delegate.createSocket(address, port, localAddress, localPort));
}
private Socket patch(Socket s) {
if (s instanceof SSLSocket) {
((SSLSocket) s).setEnabledProtocols(TLS_SUPPORT_VERSION);
}
return s;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52