Https通信過程

1 客戶端發(fā)起一個https的請求,把自身支持的一系列Cipher Suite(密鑰算法套件,簡稱Cipher)發(fā)送給服務(wù)端
2 服務(wù)端,接收到客戶端所有的Cipher后與自身支持的對比,如果不支持則連接斷開,反之則會從中選出一種加密算法和HASH算法
以證書的形式返回給客戶端 證書中還包含了 公鑰 頒證機(jī)構(gòu) 網(wǎng)址 失效日期等等。
3 客戶端收到服務(wù)端響應(yīng)后會做以下幾件事
驗(yàn)證證書的合法性
頒發(fā)證書的機(jī)構(gòu)是否合法與是否過期,證書中包含的網(wǎng)站地址是否與正在訪問的地址一致等證書驗(yàn)證通過后,在瀏覽器的地址欄會加上一把小鎖(每家瀏覽器驗(yàn)證通過后的提示不一樣 不做討論)生成隨機(jī)密碼
如果證書驗(yàn)證通過,或者用戶接受了不授信的證書,此時瀏覽器會生成一串隨機(jī)數(shù),然后用證書中的公鑰加密。-
HASH握手信息
用最開始約定好的HASH方式,把握手消息取HASH值, 然后用 隨機(jī)數(shù)加密 “握手消息+握手消息HASH值(簽名)” 并一起發(fā)送給服務(wù)端在這里之所以要取握手消息的HASH值,主要是把握手消息做一個簽名,用于驗(yàn)證握手消息在傳輸過程中沒有被篡改過。 服務(wù)端拿到客戶端傳來的密文,用自己的私鑰來解密握手消息取出隨機(jī)數(shù)密碼,再用隨機(jī)數(shù)密碼 解密 握手消息與HASH值,并與傳過來的HASH值做對比確認(rèn)是否一致。然后用隨機(jī)密碼加密一段握手消息(握手消息+握手消息的HASH值 )給客戶端。
客戶端用隨機(jī)數(shù)解密并計算握手消息的HASH,如果與服務(wù)端發(fā)來的HASH一致,此時握手過程結(jié)束,之后所有的通信數(shù)據(jù)將由之前瀏覽器生成的隨機(jī)密碼并利用對稱加密算法進(jìn)行加密因?yàn)檫@串密鑰只有客戶端和服務(wù)端知道,所以即使中間請求被攔截也是沒法解密數(shù)據(jù)的,以此保證了通信的安全
非對稱加密算法:RSA,DSA/DSS 在客戶端與服務(wù)端相互驗(yàn)證的過程中用的是對稱加密
對稱加密算法:AES,RC4,3DES 客戶端與服務(wù)端相互驗(yàn)證通過后,以隨機(jī)數(shù)作為密鑰時,就是對稱加密
HASH算法:MD5,SHA1,SHA256 在確認(rèn)握手消息沒有被篡改時
Android OKHttp Https配置
X509TrustManager:
在JSSE中,證書信任管理器類就是實(shí)現(xiàn)了接口X509TrustManager的類。
接口X509TrustManager有下述三個公有的方法需要我們實(shí)現(xiàn):
⑴ void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException
該方法檢查客戶端的證書,若不信任該證書則拋出異常。由于我們不需要對客戶端進(jìn)行認(rèn)證,因此我們只需要執(zhí)行默認(rèn)的信任管理器的這個方法。JSSE中,默認(rèn)的信任管理器類為TrustManager。
⑵ void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException
該方法檢查服務(wù)器的證書,若不信任該證書同樣拋出異常。通過自己實(shí)現(xiàn)該方法,可以使之信任我們指定的任何證書。在實(shí)現(xiàn)該方法時,也可以簡單的不做任何處理,即一個空的函數(shù)體,由于不會拋出異常,它就會信任任何證書。
⑶ X509Certificate[] getAcceptedIssuers()
返回受信任的X509證書數(shù)組。
- 若server使用的證書是由認(rèn)證的CA機(jī)構(gòu)頒發(fā)的,則如此配置即可(OkHttp等均已支持):
private X509TrustManager systemDefaultTrustManager() {
try {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:"
+ Arrays.toString(trustManagers));
}
return (X509TrustManager) trustManagers[0];
} catch (GeneralSecurityException e) {
throw new AssertionError(); // The system has no TLS. Just give up.
}
}
private SSLSocketFactory systemDefaultSslSocketFactory(X509TrustManager trustManager) {
try {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[] { trustManager }, null);
return sslContext.getSocketFactory();
} catch (GeneralSecurityException e) {
throw new AssertionError(); // The system has no TLS. Just give up.
}
}
- 若使用自簽名證書,則客戶端需要將server生成的xx.cer放置到assets目錄下:
/**
* 設(shè)置https certificate
*
* @param context
* @param certificateName must be in assets directory such as "abf.cer"
*/
public void setSSLSocketFactory(Context context, String certificateName) {
try {
CertificateFactory tCertFactory = CertificateFactory.getInstance("X.509");
X509Certificate tJDBCert = readJDBCertFromAsset(context, certificateName, tCertFactory);
X509TrustManager tTrustMgr = newTrustManager(tJDBCert);
SSLContext tSSLContext = newSSLContext(tTrustMgr);
mOkHttpClient.newBuilder().sslSocketFactory(new NoSSLv3SocketFactory(tSSLContext.getSocketFactory()), tTrustMgr);
} catch (CertificateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
}
/**
* xxx.cer證書 -> X509Certificate
*/
private X509Certificate readJDBCertFromAsset(Context context, String certificateName, CertificateFactory pCertFactory) throws IOException, CertificateException {
InputStream caInput = context.getAssets().open(certificateName);
if (caInput == null) {
return null;
}
try {
X509Certificate tCert = (X509Certificate)pCertFactory.generateCertificate(caInput);
tCert.checkValidity();
return tCert;
} finally {
caInput.close();
}
}
/**
* X509TrustManager實(shí)例化 Android 自帶的一套https認(rèn)證機(jī)制
*/
private X509TrustManager newTrustManager(final X509Certificate privateCert) {
// Accepted CA is only the one installed in the store.
return new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
if (null == chain || 0 == chain.length) {
throw new CertificateException("Certificate chain is invalid.");
}
if (null == authType || 0 == authType.length()) {
throw new CertificateException("Authentication type is invalid.");
}
for (X509Certificate cert : chain) {
// Make sure that it hasn't expired.
cert.checkValidity();
// Verify the certificate's public key chain.
try {
cert.verify(privateCert.getPublicKey());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (NoSuchProviderException e) {
e.printStackTrace();
} catch (SignatureException e) {
e.printStackTrace();
}
}
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[]{};
}
};
}
/**
* X509TrustManager -> SSLContext
*/
private SSLContext newSSLContext(X509TrustManager pTrustManager) throws NoSuchAlgorithmException, KeyManagementException {
SSLContext tSSLContext = SSLContext.getInstance("TLS");
tSSLContext.init(null, new TrustManager[]{pTrustManager}, null);
return tSSLContext;
}
解釋一下代理如何抓取Https請求:
一般我們可以抓取的https請求,server端的證書都是由認(rèn)證CA頒發(fā)的,所以
X509TrustManager可以使用默認(rèn)的那套,從系統(tǒng)根證書信任列表中去挨個認(rèn)證。
所以那Charles為例,如何抓取https信息呢? 首先電腦上(由于電腦是代理,相當(dāng)于服務(wù)器)
信任Charles證書,然后在手機(jī)上安裝Charles證書,兩端證書的指紋是一樣的。當(dāng)手機(jī)發(fā)出
https請求時,根據(jù)https通信過程,電腦會把charles的證書下發(fā)給手機(jī),而手機(jī)上由于之前
安裝了Charles證書顯然可以驗(yàn)證通過,所以手機(jī)端會生成對稱秘鑰,從此以它進(jìn)行加解密。但
是,對于自簽名的證書,由于使用的X509TrustManager是自定義的,也就是只能驗(yàn)證自簽名
證書,不會向手機(jī)的根信任列表去挨個校驗(yàn),所以這種方式無法抓取https。