聊聊HTTPS与Android安全
2015-06-30 11:47
1746 查看
“互联网仍然处于开端的开端阶段(the beginning of its beginning)”《失控》——凯文.凯利
SSL/TLS协议是为了解决这三大风险而设计的。那么SSL/TLS如何解决这三大风险呢?
对称加密
加密和解密的密钥是相同的.
假设A和B之间通信,使用对称密钥是123.
A和B写信需要用123加密.
B收到A的信息,同样需要123解密。
对称加密的最大风险在于密钥一旦泄露,那么被截获的信件内容就会被破解.
非对称加密
加密和解密的密钥是不同的分为私钥和公钥。
私钥: 只有一份, 保存在收信人手中。不会在通信中传输,不会被泄露。
公钥:可以有多份,保存在写信人手中。
假设B要给A写信: A先生成一对公钥(123)和私钥(456)
A要把自己的公钥(456)通过某种安全的方式(数字证书,下文会提到)交给B B用公钥(456)加密信件内容然后发给A
A使用私钥(123)解开内容。
因此即使Step2时公钥泄露,那么即使B给A写的信被截获,由于没有A的私钥依然无法被解开。
数字证书是一个文件。此文件保存了加密过的用户的信息及公钥。
数字证书在HTTPS的什么时候会用上呢?这里就要提到HTTPS握手。
服务端返回的数字证书(此为用户证书),客户端(浏览器)会进行如下验证:
客户端(浏览器)产生一个随机的对称密钥
使用证书中服务端的公钥加密此对称密钥
将此已经加密过的对称密钥发给server。至此client和server都通过可靠的手段拥有对称密钥.
通过对称密钥加密Http的通信内容。
通过上述的握手过程可以知道,非对称密钥只是用来加密传输对称密钥的。因为可以保证传输中的对称密钥在传输的过程中无法被破解。所以握手之后的内容通信就是安全的。
握手过程中涉及到两种证书:
Https握手时Server发给Client的证书成为用户证书
Client所在计算机中原本保存的根证书(ROOT CA)和中间证书(Intermediate CA)
一般有两种解决方案:
让HttpClient信任所有的服务器证书,这种方法安全性则差一些,但实现相对简单。
在发起Https连接之前,将服务器证书加到HttpClient的信任证书列表中,这个相对来说比较复杂一些,很容易出错;
下面讲解第一种的实现原理。
当实例化HttpClinet对象时要绑定https连接所使用的端口号,这里绑定了443(443是https默认的端口号,就像http的默认端口是80)。
一般我们的操作步骤是:
导出公钥:在浏览器上用https访问tomcat,查看其证书,并另存为一个文件(存成了X.509格式:xxxx.cer),这里我们到处的是baidu.cer
导入公钥:把xxxx.cer放在Android的assets文件夹中,以方便在运行时通过代码读取此证书。
使用HttpClient请求,代码如下:
/*
* @author zhoushengtao(周圣韬)
* @since 2015年7月3日 11:04:22
* @weixin stchou_zst
* @blog http://blog.csdn.net/yzzst
* @交流学习QQ群:341989536
* @私人QQ:445914891
/
HTTPS是什么?
HTTPS并不是一个单独的协议,而是对工作在一加密连接(SSL/TLS)上的常规HTTP协议。通过在TCP和HTTP之间加入TLS(Transport Layer Security)来加密 。(注: TLS为新版本的SSL)为何需要HTTPS?
不使用SSL/TLS的HTTP通信,就是不加密的通信。所有信息明文传播,带来了三大风险。 - ***风险(eavesdropping):第三方可以获知通信内容。 - 篡改风险(tampering):第三方可以修改通信内容。 - 冒充风险(pretending):第三方可以冒充他人身份参与通信。
SSL/TLS协议是为了解决这三大风险而设计的。那么SSL/TLS如何解决这三大风险呢?
加密传播,防第三方***
HTTPS一般使用的加密与HASH算法如下: - 非对称加密算法:RSA,DSA/DSS - 对称加密算法:AES,RC4,3DES - HASH算法:MD5,SHA1,SHA256
对称加密
加密和解密的密钥是相同的.
假设A和B之间通信,使用对称密钥是123.
A和B写信需要用123加密.
B收到A的信息,同样需要123解密。
对称加密的最大风险在于密钥一旦泄露,那么被截获的信件内容就会被破解.
非对称加密
加密和解密的密钥是不同的分为私钥和公钥。
私钥: 只有一份, 保存在收信人手中。不会在通信中传输,不会被泄露。
公钥:可以有多份,保存在写信人手中。
假设B要给A写信: A先生成一对公钥(123)和私钥(456)
A要把自己的公钥(456)通过某种安全的方式(数字证书,下文会提到)交给B B用公钥(456)加密信件内容然后发给A
A使用私钥(123)解开内容。
因此即使Step2时公钥泄露,那么即使B给A写的信被截获,由于没有A的私钥依然无法被解开。
具有校验机制,防止被篡改
使用HASH算法进行校验内容是否被篡改。
配备数字证书,防止身份被冒充
数字证书就是互联网通讯中标志通讯各方身份信息的一串数字,提供了一种在Internet上验证通信实体身份的方式,数字证书不是数字***,而是身份认证机构盖在数字***上的一个章或印(或者说加在数字***上的一个签名)。它是由权威机构——CA机构,又称为证书授权(Certificate Authority)中心发行的,人们可以在网上用它来识别对方的身份。
数字证书是一个文件。此文件保存了加密过的用户的信息及公钥。
数字证书在HTTPS的什么时候会用上呢?这里就要提到HTTPS握手。
HTTPS握手过程
服务端返回的数字证书(此为用户证书),客户端(浏览器)会进行如下验证:
1)遍历计算机以及浏览器中保存的根证书(Root CA)和中间证书 (Intermediate CA),若其中某个根证书或中间证书的公钥可以解开server端的证书,获得server的公钥和server域名. 否则握手失败。 整个HTTPS通信的唯一核心保障就是可信的根证书。 2) 判断证书中的域名是否和正在访问的域名相同。否则认证失败,浏览器会提示证书不可信。但是握手加密依然可以继续。
客户端(浏览器)产生一个随机的对称密钥
使用证书中服务端的公钥加密此对称密钥
将此已经加密过的对称密钥发给server。至此client和server都通过可靠的手段拥有对称密钥.
通过对称密钥加密Http的通信内容。
通过上述的握手过程可以知道,非对称密钥只是用来加密传输对称密钥的。因为可以保证传输中的对称密钥在传输的过程中无法被破解。所以握手之后的内容通信就是安全的。
握手过程中涉及到两种证书:
Https握手时Server发给Client的证书成为用户证书
Client所在计算机中原本保存的根证书(ROOT CA)和中间证书(Intermediate CA)
Android开发中使用的Https
当Android端有使用https的需求的时候,如果继续保持http的方式进行网络请求,就容易出现连接失败的问题。这是因为大多数情况下,Https服务器所使用的根证书是自签名的。如果设备的信任证书列表中不包含此签名机构,就会连接失败。出现这样的问题。一般有两种解决方案:
让HttpClient信任所有的服务器证书,这种方法安全性则差一些,但实现相对简单。
在发起Https连接之前,将服务器证书加到HttpClient的信任证书列表中,这个相对来说比较复杂一些,很容易出错;
下面讲解第一种的实现原理。
当实例化HttpClinet对象时要绑定https连接所使用的端口号,这里绑定了443(443是https默认的端口号,就像http的默认端口是80)。
信任所有证书
一个常见的信任所有证书的Https请求,这里我们以baidu的为例。private void getHttps() { String https = "https://www.baidu.com/"; try { SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, new TrustManager[] { new ITrustManager() }, new SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory()); HttpsURLConnection.setDefaultHostnameVerifier(new IHostnameVerifier()); HttpsURLConnection conn = (HttpsURLConnection) new URL(https).openConnection(); conn.setDoOutput(true); conn.setDoInput(true); conn.connect(); BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream())); StringBuffer sb = new StringBuffer(); String line; while ((line = br.readLine()) != null) sb.append(line); text.setText(sb.toString()); } catch (Exception e) { Log.e(this.getClass().getName(), e.getMessage()); } } /** * 此类是用于主机名验证的基接口。 * 在握手期间,如果 URL 的主机名和服务器的标识主机名不匹配,则验证机制可以回调此接口的实现程序来确定是否应该允许此连接。 * 策略可以是基于证书的或依赖于其他验证方案。 * 当验证 URL 主机名使用的默认规则失败时使用这些回调。 * * @author zhoushengtao * */ private class IHostnameVerifier implements HostnameVerifier { /** * 验证主机名和服务器验证方案的匹配是可接受的。 * * (这里我们所有的都接受) * * @param hostname - 主机名 * @param session - 到主机的连接上使用的 SSLSession * * @return 如果主机名是可接受的,则返回 true */ @Override public boolean verify(String hostname, SSLSession session) { Log.d("https_test", "verify hostname = " + hostname ); for (String name : session.getValueNames()) { Log.d("https_test", "verify session "+ name +" = " + session.getValue(name)); } return true; } } /** * 此接口的实例管理使用哪一个 X509 证书来验证远端的安全套接字。 * 决定是根据信任的证书授权、证书撤消列表、在线状态检查或其他方式做出的。 * * @author zhoushengtao * */ private class ITrustManager implements X509TrustManager { /** * * 给出同位体提供的部分或完整的证书链,构建到可信任的根的证书路径, * 并且返回是否可以确认和信任将其用于基于验证类型的客户端 SSL 验证。 * 验证类型由实际使用的证书确定。 * 例如,如果使用 RSAPublicKey,则 authType 应为 "RSA"。检查是否大小写敏感的。 * * @param chain - 同位体的证书链 * @param authType - 基于客户端证书的验证类型 * @throws CertificateException - 如果证书链不受此 TrustManager 信任。 * */ @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { Log.d("https_test", "checkClientTrusted authType = " + authType); for (X509Certificate certificate : chain) { Log.d("https_test", "checkClientTrusted certificate = " + certificate.toString()); } } /** * 给出同位体提供的部分或完整的证书链,构建到可信任的根的证书路径, * 并且返回是否可以确认和信任将其用于基于验证类型的服务器 SSL 验证。 * 验证类型是表示为一个 String 的密码套件的密钥交换算法部分,例如 "RSA"、"DHE_DSS"。 * * 注:对于一些可输出的密码套件,密钥交换算法是在运行时的联络期间确定的。 * 例如,对于 TLS_RSA_EXPORT_WITH_RC4_40_MD5,当临时的 RSA 密钥 * 用于密钥交换时 authType 应为 RSA_EXPORT,当使用来自服务器证书的密钥时 authType * 应为 RSA。检查是否大小写敏感的。 * * @param chain - 同位体的证书链 * @param authType - 使用的密钥交换算法 * @throws CertificateException - 如果证书链不受此 TrustManager 信任。 */ @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { Log.d("https_test", "checkServerTrusted authType = " + authType); for (X509Certificate certificate : chain) { Log.d("https_test", "checkServerTrusted certificate = " + certificate.toString()); } } /** * 返回受验证同位体信任的认证中心的数组。 * * @return 可接受的 CA 发行者证书的非 null(可能为空)的数组。 */ @Override public X509Certificate[] getAcceptedIssuers() { return null; } }
受信任的Https请求
当然信任所有的证书,风险性还是很大的。一般我们的操作步骤是:
导出公钥:在浏览器上用https访问tomcat,查看其证书,并另存为一个文件(存成了X.509格式:xxxx.cer),这里我们到处的是baidu.cer
导入公钥:把xxxx.cer放在Android的assets文件夹中,以方便在运行时通过代码读取此证书。
使用HttpClient请求,代码如下:
public String requestHTTPSPage(String mUrl) { InputStream inputStream = null; String result = ""; try { inputStream = getAssets().open("baidu.cer"); // 下载的证书放到项目中的assets目录中 CertificateFactory cerFactory = CertificateFactory.getInstance("X.509"); Certificate cer = cerFactory.generateCertificate(inputStream); KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC"); keyStore.load(null, null); keyStore.setCertificateEntry("trust", cer); SSLSocketFactory socketFactory = new SSLSocketFactory(keyStore); // Https 默认请求端口 443 Scheme scheme = new Scheme("https", socketFactory, 443); HttpClient mHttpClient = new DefaultHttpClient(); mHttpClient.getConnectionManager().getSchemeRegistry().register(scheme); BufferedReader reader = null; try { Log.d(TAG, "executeGet is in,murl:" + mUrl); HttpGet request = new HttpGet(); request.setURI(new URI(mUrl)); HttpResponse response = mHttpClient.execute(request); if (response.getStatusLine().getStatusCode() != 200) { request.abort(); return result; } reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent())); StringBuffer buffer = new StringBuffer(); String line = null; while ((line = reader.readLine()) != null) { buffer.append(line); } result = buffer.toString(); Log.d(TAG, "mUrl=" + mUrl + "\nresult = " + result); } catch (Exception e) { e.printStackTrace(); } finally { if (reader != null) { reader.close(); } } } catch (Exception e) { e.printStackTrace(); } finally { try { if (inputStream != null) inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } return result; }
/*
* @author zhoushengtao(周圣韬)
* @since 2015年7月3日 11:04:22
* @weixin stchou_zst
* @blog http://blog.csdn.net/yzzst
* @交流学习QQ群:341989536
* @私人QQ:445914891
/
相关文章推荐
- 设置CentOS开机连接网络 Centos 开机启动网卡的设置方法
- 查看网络图片
- 实例-访问网络图片
- tomcat设置https请求步骤(单向验证)
- HTTP请求:GET与POST方法的区别
- android-async-http AsyncHttpClient介绍
- android获取网络图片简单实现
- HTTP_X_FORWARDED_FOR 和 REMOTE_ADDR的使用 php
- 查看网络图片
- Exploring the Network
- HTTP/1.1 500 Server Error
- 用Wireshark从Http数据包中得到用户的登录信息
- TCP/IP协议栈的基本工作原理
- C++ 网络编程
- android查看网络图片
- Android 从网络中获取图片
- 6月第3周网络安全报告:境内感染网络病毒主机55.4万
- HttpClient_4 用法 由HttpClient_3 升级到 HttpClient_4 必看
- lighttpd 模块化
- Android 网络通信框架Volley简介(Google IO 2013)