一:关于Https
HTTP协议被用于在浏览器和服务器之间传递信息。HTTP协议以明文方式发送内容,不提供任何方式的数据加密,如果攻击者截取了浏览器和服务器之间的传输报文,就可以直接读懂其中的信息,因此HTTP不适合传输一些敏感信息,为了解决HTTP协议的这一缺陷,需要使用安全套接字层超文本传输协议HTTPS。为了数据传输的安全,HTTPS在HTTP的基础上加入了SSL协议(SSL( 安全套接层),及其传输层安全(TLS)是为网络通信提供安全及数据完整性的一种安全协议。TLS与SSL在传输层对网络连接进行加密。SSL协议可分为两层:SSL记录协议(SSL Record Protocol):它建立在可靠的传输协议(如TCP)之上,为高层协议提供数据封装、压缩、加密等基本功能的支持。SSL握手协议(SSL Handshake Protocol):它建立在SSL记录协议之上,用于在实际的数据传输开始前,通讯双方进行身份认证、协商加密算法、交换加密密钥等。),SSL依靠证书来验证服务器的身份,并为浏览器和服务器之间的通信加密。HTTP是超文本传输协议,信息是明文传输,HTTPS 则是具有安全性的ssl加密传输协议。HTTP和HTTPS使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。HTTP的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
二:对称加密与非对称加密
由于个人能力有限,也只是在概念的层次上做出解释。
- 对称加密就是加密和解密用的是同一个密钥k。优:速度快
- 非对称加密是发送端使用公开的公钥A加密,然后接收端使用私密的私钥B解密。优:更安全
那Https中使用了哪种加密方式呢?如果不知道,可以从以下的Https请求流程来获取答案:
关于上述流程:
- 1. 浏览器往服务器的443 端口发起请求,请求携带了浏览器支持的加密,希算法。
- 2. 服务器收到请求,选择浏览器支持的加密算法和哈希算法。
- 3. 服务器下将数字证书返回给浏览器。(证书包括以下这些内容:1. 证书序列号。2. 证书过期时间。3. 站点组织名。4. 站点DNS主机名。5. 站点公钥。6. 证书颁发者名。7. 证书签名)
- 4. 浏览器进入证书认证环节,这一部分由浏览器内置的 TSL 完成。
- 4.1. 首先浏览器会从内置的证书列表中查找找到服务器返回的证书对应的机构,如果没有找到,此时就会提示用户该证书是不是由权威机构颁发,是不可信任的。如果查到了对应的机构,则取出该机构颁发的公钥。
- 4.2. 用机构的证书公钥解密得到证书的内容和证书签名,内容包括网站的网址、网站的公钥、证书的有效期等。浏览器会先验证证书签名的合法性。签名通过后,浏览器验证证书记录的网址是否和当前网址是一致的,不一致会提示用户。如果网址一致会检查证书有效期,证书过期同样会提示用户。当这些都通过认证时,浏览器就可以安全使用证书中的网站公钥了。
- 4.3. 浏览器生成一个随机 R,并使用网站公钥对 R 进行加密。
- 5、浏览器将加密的 R 传送给服务器。
- 6、服务器用自己的私钥解密得到 R。
- 7、服务器以 R 为密钥使用了对称加密算法加密网页内容并传输给浏览器。
- 8、浏览器以 R 为密钥使用之前约定好的解密算法获取网页内容。
总结:
使用非对称加密传输一个对称密钥R,让服务器和客户端都得知。然后两边都使用这个对称密钥R来加密解密收发数据。因为传输密钥R是用非对称加密方式,很难破解比较安全。而具体传输数据则是用对称加密方式,加快传输速度。两全其美。所以,Https使用了对称与非对称两种加密方式。
三:Https通信安全
https确保安全通信的三个原则:
- 通讯双方身份校验
- 数据内容的加密
- 数据内容的完整性
关于Https中间者攻击:
一般解决方案:
- 使用第三方权威机构(CA(专门对公钥进行认证担保))向安全的服务器颁发证书,证明这台服务器的合法性
- 服务器利用此证书来把(得到担保之后的)公钥加密以后发给客户端
- 客户端收到这个加密后的公钥以后 ,就用第CA的公钥,把这个服务器返回的加密后的公钥进行解密,最后得到真正的服务器的公钥
(当然也可以自己制作证书,然后将此证书放于App的安装目录下,这样App使用自己的证书公钥进行解密)
三:Https在Android中的应用
此处我会用一个小demo进行说明,请注意查看注释部分。
我使用的测试网站是12306,证书等下载请自行百度,
还可以到我的github项目(https://github.com/ban-z/andHttps)自行导出测试
myHttps类文件就是我用来测试https网络连接的代码(相关的解释已经在代码中给出,请大家自行查看):
package com.banzh.andhttps.network;
import android.content.Context;
import android.util.Log;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
/*
*@author: banzh
*@function: 使用Android的HttpURLConnection完成Https的请求,关于OkHttps等会在以后实现
*@time 2019/10/10 21:34
**/
public class myHttps {
protected String TAG = "IN_MY_HTTPS::";
private Context mContext;
private URL url;
private String keyStoreType;
private String tmfAlgorithm;
String result = new String();
HttpURLConnection httpURLConnection;
HttpsURLConnection httpsURLConnection;
InputStream is_for_connection;
InputStream is_for_certifiy;
InputStream is_for_httpsInput;
KeyStore keyStore;
TrustManagerFactory trustManagerFactory;
SSLContext sslContext;
public myHttps(Context context){
mContext = context;
}
/*
* 如果你有一个知名CA颁发证书的网络服务器,那么如下一样简单使用
* */
public String RequestForExample(String urlStr) throws IOException {
try {
/*
*openConnection()使用“ https”方案 调用网址将返回HttpsURLConnection
* 从而可以覆盖默认值HostnameVerifier和SSLSocketFactory。
* */
url = new URL(urlStr);
httpURLConnection = (HttpURLConnection) url.openConnection();
is_for_connection = httpURLConnection.getInputStream();
result = getResult(is_for_connection);
} catch (IOException e) {
e.printStackTrace();
}finally {
if (is_for_connection != null){
is_for_connection.close();
}
}
return result;
}
/*
*未知的证书授权机构
* */
/*
*导致出现 SSLHandshakeException出现的其他两种情况:
*
* 1.自签名的服务器证书:
* 服务器将按照自己的 CA 进行操作。这与证书授权机构未知的情况相似
* 方法:
* 创建自己的 TrustManager,这次直接信任服务器证书。
* 这种方法具有前面所述的将应用与证书直接关联的所有弊端,但可以安全地操作。
*
* 2.缺少中间证书授权机构
* */
public String RequestForCA(String urlStr) throws IOException {
try {
/*
* 如果是未知的证书授权机构(系统不信任的CA),将会发生SSLHandshakeException
* 原因:
* 由 Android 尚不信任的新 CA 颁发的证书,或应用在没有 CA 的较旧版本上运行
* 通常是因为它不是公共 CA,而是政府、公司或教育机构等组织颁发的仅供自己使用的私有 CA
*
* 解决:
* 可以指示 HttpsURLConnection 信任特定的 CA 集
* */
//用输入流加载CAs
CertificateFactory cf = CertificateFactory.getInstance("X.509");
//root.crt:https://www.12306.cn/index/
is_for_certifiy = mContext.getAssets().open("-_12306_cn.crt");
Certificate ca = cf.generateCertificate(is_for_certifiy);
Log.d(TAG, "+++++++++++++++++++++++Request: ca = " + ((X509Certificate) ca).getSubjectDN());
//创建一个KeyStore联系我们信任的CAs
keyStoreType = KeyStore.getDefaultType();
keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null,null);
keyStore.setCertificateEntry("ca", ca);
//创建一个TrustManager信任我们KeyStore中的CAs
tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
trustManagerFactory = TrustManagerFactory.getInstance(tmfAlgorithm);
trustManagerFactory.init(keyStore);
//使用TrustManager创建一个SSLContext
sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
url = new URL(urlStr);
httpsURLConnection = (HttpsURLConnection) url.openConnection();
//为HttpURLConnection设置SSLSocketFactory
httpsURLConnection.setSSLSocketFactory(sslContext.getSocketFactory());
is_for_httpsInput = httpsURLConnection.getInputStream();
result = getResult(is_for_httpsInput);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (CertificateException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
} finally {
if (is_for_certifiy != null){
is_for_certifiy.close();
}
if (is_for_connection != null) {
is_for_connection.close();
}
if (is_for_httpsInput != null){
is_for_httpsInput.close();
}
}
return result;
}
private String getResult(InputStream inputStream) throws IOException {
/*
* 若没有从 getInputStream() 接收内容,系统会抛出异常
* javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
* 原因如下:
* 颁发服务器证书的 CA 未知
* 服务器证书不是 CA 签名的,而是自签名的
* 服务器配置缺少中间 CA
* */
StringBuilder result = new StringBuilder();
String line;
boolean flag = false;
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
while ((line = reader.readLine()) != null){
result.append(line);
flag = true;
}
return result.toString();
}
}
ManActivity主要是用了一个ScrollView显示是否请求成功:
package com.banzh.andhttps;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
import android.widget.Toast;
import com.banzh.andhttps.network.myHttps;
import java.io.IOException;
public class MainActivity extends AppCompatActivity {
myHttps myhttps;
TextView tv_result;
String urlStr = "https://blog.csdn.net/qq_40834350";
String urlStrforCA = "https://www.12306.cn/index/";
String httpResult;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv_result = findViewById(R.id.tv_result);
myhttps = new myHttps(MainActivity.this);
new Thread(new Runnable() {
@Override
public void run() {
try {
httpResult = myhttps.RequestForCA(urlStrforCA);
} catch (IOException e) {
e.printStackTrace();
}
runOnUiThread(new Runnable() {
@Override
public void run() {
if (httpResult != null){
tv_result.setText(httpResult);
}else {
Toast.makeText(MainActivity.this, "网络请求未返回数据!", Toast.LENGTH_SHORT).show();
}
}
});
}
}).start();
}
}
运行结果如下所示
若无证书验证则会出现如下的异常:
相信对于https部分,这个异常大家已经很熟悉。所以这就是使用HttpURLConnection进行Https请求,其他的例如OkHttp等也只是调用的API不同,其含义都相似。希望大家对于我的错误多多指出。