Andriod中HTTPS安全使用

HTTPS在android应用中的安全使用

java中ssl/stl的使用
https即http+ssl/stl组成的网络安全传输协议,目的是防止数据在传输的过程中信息被泄露或篡改。
https在正确使用的时候是可以防止中间人攻击的,由于开发者在开发过程中编码不正确导致https
的错误使用,以至于攻击者可以获取或修改https传输的数据。



Android https的开发过程中常见的安全缺陷:

1.自定义X509TrustManager,checkServerTrusted中不对证书进行校验。
2.重写WebViewClient的onReceivedSslError方法时,调用proceed忽略证书验证错误信息继续加载页面,详情参考webview安全部分。
3.自定义HostnameVerifier,没有在verify函数中校验域名。
4.使用setHostnameVerifier(ALLOW_ALL_HOSTNAME_VERIFIER)信任所以Hostname.


android中使用https协议的漏洞代码

https协议实现的两个类httpsUrlConnection和httpClient,区别就是httpClient比HttpsUrlConnection功能更全面强大,但是实现HTTPS
链接的情况基本相同。下面是开发者为了开发方便没有正确实现X509Trustmanager导致https存在缺陷的例子。


class myX509TrustManager implements X509TrustManager  
    {  
  
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException{  
        }  
  
        public void checkServerTrusted(X509Certificate[] chain, String authType)  throws CertificateExcepton{  
        }  
  
        public X509Certificate[] getAcceptedIssuers()  
        {  
            return null;  
        }  
    }  


checkClientTrusted该方法检查客户端的证书,若不信任该证书则抛出异常。由于我们不需要对客户端进行认证,因此我们只需要执行默认的信任管理器的这个方法。JSSE中,默认的信任管理器类为TrustManager。

checkServerTrusted该方法检查服务器的证书,若不信任该证书同样抛出异常。通过自己实现该方法,可以使之信任我们指定的任何证书。在实现该方法时,也可以简单的不做任何处理,即一个空的函数体,由于不会抛出异常,它就会信任任何证书。

getAcceptedIssuers返回受信任的X509证书数组。

X509trustManager的三个方法中,由于开发者重写了checkServerTrusted方法而信任所有的证书,这样会造成中间人攻击从而产生安全问题。


httpsUrlConnection对自定义对hostname的校验缺陷代码

HttpsURLConnection.setDefaultHostnameVerifier(new MyHostnameVerifier()); 


static class MyHostnameVerifier implements HostnameVerifier{  
  
        @Override  
        public boolean verify(String hostname, SSLSession session) {  
            return true;  
        }  
          
    }   
  
}

httpClient中对hostname校验的缺陷代码

SSLSocketFactory sf = new SSLSocketFactoryEx(trustStore);  
sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);//关闭host验证,允许和所有的host建立SSL通信

上面的两种方法都信任所有主机名,导致通信的过程中可能存在中间人攻击。





安全使用代码


安全的使用HTTPS编码,我们还是参考android官方指定的安全编码方式

1.如果证书是权威机构频发且存在已信任的证书列表里,一般使用以下代码即可。

package com.pc.httpstest;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.net.URL;



import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;


public class Test_HTTP{
	private static void copyInputStreamToOutputStream(java.io.InputStream in, PrintStream out) throws IOException {
	    byte[] buffer = new byte[1024];
	    int c = 0;
	    while ((c = in.read(buffer)) != -1) {
	        out.write(buffer, 0, c);
	    }
	}
	public static void main(String[] args) throws Exception {
		
		URL url = new URL("https://www.baidu.com/");
		URLConnection urlConnection = url.openConnection();
		java.io.InputStream in = urlConnection.getInputStream();
		copyInputStreamToOutputStream(in, System.out);
	}
}


2.如果频发证书的机构不是权威机构,或者系统没有内置这个机构的证书,如果使用上面的语句一般会产生SSLHandshakeException异常,正确的使用方法谷歌官方也有相应的例子。如果导入服务器证书的话,一般服务器证书的过期时间较短,如果服务器证书过期了还必须更新到最新签发的证书。 最好的方式是导入根CA证书,这个过期时间较长,一般不用考虑客户端更新证书的问题。下载跟CA证书一般到官网下载对应的证书,像国内的沃通等都会提供跟CA证书的下载。

下面是谷歌官方的一个例子。

package com.pc.httpstest;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.net.URL;



import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;


public class Test_HTTP{
	private static void copyInputStreamToOutputStream(java.io.InputStream in, PrintStream out) throws IOException {
	    byte[] buffer = new byte[1024];
	    int c = 0;
	    while ((c = in.read(buffer)) != -1) {
	        out.write(buffer, 0, c);
	    }
	}
	public static void main(String[] args) throws Exception {
		
		// Load CAs from an InputStream
		// (could be from a resource or ByteArrayInputStream or ...)
		CertificateFactory cf = CertificateFactory.getInstance("X.509");
		// From https://www.washington.edu/itconnect/security/ca/load-der.crt
		InputStream caInput = new BufferedInputStream(new FileInputStream("C:\\Users\\pc\\Desktop\\WS_CA1_NEW.crt"));
		Certificate ca;
		try {
		    ca = (Certificate) cf.generateCertificate(caInput);
		} finally {
		    caInput.close();
		}

		// Create a KeyStore containing our trusted CAs
		String keyStoreType = KeyStore.getDefaultType();
		KeyStore keyStore = KeyStore.getInstance(keyStoreType);
		keyStore.load(null, null);
		keyStore.setCertificateEntry("ca", (java.security.cert.Certificate) ca);

		// Create a TrustManager that trusts the CAs in our KeyStore
		String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
		TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
		tmf.init(keyStore);

		// Create an SSLContext that uses our TrustManager
		SSLContext context = SSLContext.getInstance("TLS");
		context.init(null, tmf.getTrustManagers(), null);

		// Tell the URLConnection to use a SocketFactory from our SSLContext
		URL url = new URL("https://www.wosign.com/");
		HttpsURLConnection urlConnection =
		    (HttpsURLConnection)url.openConnection();
		urlConnection.setSSLSocketFactory(context.getSocketFactory());
		java.io.InputStream in = urlConnection.getInputStream();
		copyInputStreamToOutputStream(in, System.out);
	}

}

3.关于hostname验证,谷歌官方也提供了相应的例子

// Create an HostnameVerifier that hardwires the expected hostname.
// Note that is different than the URL's hostname:
// example.com versus example.org
HostnameVerifier hostnameVerifier = new HostnameVerifier() {
    @Override
    public boolean verify(String hostname, SSLSession session) {
        HostnameVerifier hv =
            HttpsURLConnection.getDefaultHostnameVerifier();
        return hv.verify("example.com", session);
    }
};

// Tell the URLConnection to use our HostnameVerifier
URL url = new URL("https://example.org/");
HttpsURLConnection urlConnection =
    (HttpsURLConnection)url.openConnection();
urlConnection.setHostnameVerifier(hostnameVerifier);
InputStream in = urlConnection.getInputStream();
copyInputStreamToOutputStream(in, System.out);

对于httpClient中hostname的验证就使用hostname严格验证模式

SSLSocketFactory sf = new MySSLSocketFactory(trustStore);
sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);



参考链接

https://developer.android.com/training/articles/security-ssl.html#HttpsExample

http://blog.csdn.net/iispring/article/details/51615631

http://pingguohe.net/2016/02/26/Android-App-secure-ssl.html

http://www.droidsec.cn/浅析https中间人攻击与证书校验/

https://jaq.alibaba.com/community/art/show?spm=a313e.7916646.24000001.14.T9h6Bf&articleid=60

http://yaq.qq.com/blog/13

http://blog.csdn.net/cj649934578/article/details/47042131



猜你喜欢

转载自blog.csdn.net/nextdoor6/article/details/52638948