【android安全】之使用ssl验证保护网络数据传输安全。

SSL pinning 限定受信SSL的范围。

https协议验证服务器身份的方式通常有三种,一是根据浏览器或者说操作系统(Android)自带的证书链;二是使用自签名证书;三是自签名证书加上SSL Pinning特性。第一种需要到知名证书机构购买证书,需要一定预算。第二种多见于内网使用。第三种在是安全性最高的,但是需要浏览器插件或客户端使用了SSL Pinning特性。


Android应用程序在使用https协议时也使用类似的3种方式验证服务器身份,分别是系统证书库、自带证书库、自带证书库 + SSL Pinning特性。


所以SSL Pinning,即SSL证书绑定,是验证服务器身份的一种方式,是在https协议建立通信时增加的代码逻辑,它通过自己的方式验证服务器身份,然后决定通信是否继续下去。它唯一指定了服务器的身份,所以安全性较高。


一、先介绍一种使用服务器证书pin码验证方式的安全传输(本方式在apk不被篡改后重编译的情况下安全,上篇文章讲了如何防止apk被篡改的方法),步骤如下:

1,获取服务器的证书pin码。


2,将pin码复制到android app的代码里面。可写在Const类里面,例如:


package com.test;


public final class Const {
	
	public static final String[] SSL_PINS={"f30012bbc18c231ac1a44b788e410ce754182513","bb0012bbc18c231ac1a44b788e410ce754182513"};


}


3,app进行网络访问时进行pin码验证。可使用如下工具类,调用时传入自己服务器的pin码集即可(该工具类依赖AndroidPinning库https://github.com/moxie0/AndroidPinning):

package com.test;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;

import javax.net.ssl.HttpsURLConnection;

import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.thoughtcrime.ssl.pinning.PinningSSLSocketFactory;
import org.thoughtcrime.ssl.pinning.util.PinningHelper;

import android.content.Context;

public class SSLHttpUtil {

	private final static int CONNET_TIMEOUT = 10 * 1000;
	private final static int READ_TIMEOUT = 10 * 1000;
	private final static String ENCODING = "GBK";

	/**
	 * 
	 * @param urlString
	 * @param params
	 * @param files
	 * @param pins
	 *            服务器各个证书的pin码集合。
	 * @param context
	 * @return
	 */
	public static String post(String urlString, Map<String, String> params,
			Map<String, File> files, String[] pins, Context context) {
		HttpsURLConnection conn = null;
		DataOutputStream outStream = null;
		try {
			String BOUNDARY = UUID.randomUUID().toString();
			String PREFIX = "--", LINEND = "\r\n";
			String MULTIPART_FROM_DATA = "multipart/form-data";
			URL url = new URL(urlString);

			conn = PinningHelper
					.getPinnedHttpsURLConnection(context, pins, url);
			conn.setReadTimeout(READ_TIMEOUT);
			conn.setConnectTimeout(CONNET_TIMEOUT);
			conn.setDoInput(true);
			conn.setDoOutput(true);
			conn.setUseCaches(false);
			conn.setRequestMethod("POST");
			conn.setRequestProperty("connection", "keep-alive");
			conn.setRequestProperty("Charsert", "UTF-8");
			conn.setRequestProperty("Content-Type", MULTIPART_FROM_DATA
					+ ";boundary=" + BOUNDARY);
			//
			outStream = new DataOutputStream(conn.getOutputStream());
			if (params != null) {
				// 棣栧厛缁勬嫾鏂囨湰绫诲瀷鐨勫弬锟�?
				StringBuilder sb = new StringBuilder();
				for (Map.Entry<String, String> entry : params.entrySet()) {
					sb.append(PREFIX);
					sb.append(BOUNDARY);
					sb.append(LINEND);
					sb.append("Content-Disposition: form-data; name=\""
							+ entry.getKey() + "\"" + LINEND);
					sb.append("Content-Type: text/plain; charset=" + ENCODING
							+ LINEND);
					sb.append("Content-Transfer-Encoding: 8bit" + LINEND);
					sb.append(LINEND);
					sb.append(entry.getValue());
					sb.append(LINEND);
				}

				outStream.write(sb.toString().getBytes());
			}
			// 鍙戯拷?锟芥枃浠舵暟锟�?
			if (files != null) {
				for (Map.Entry<String, File> file : files.entrySet()) {
					StringBuilder sb1 = new StringBuilder();
					sb1.append(PREFIX);
					sb1.append(BOUNDARY);
					sb1.append(LINEND);
					sb1.append("Content-Disposition: form-data; name=\""
							+ file.getKey() + "\"; filename=\""
							+ file.getValue().getName() + "\"" + LINEND);
					sb1.append("Content-Type: application/octet-stream; charset="
							+ ENCODING + LINEND);
					sb1.append(LINEND);
					outStream.write(sb1.toString().getBytes());

					InputStream is = new FileInputStream(file.getValue());
					byte[] buffer = new byte[1024];
					int len = 0;
					while ((len = is.read(buffer)) != -1) {
						outStream.write(buffer, 0, len);
					}
					is.close();
					outStream.write(LINEND.getBytes());
				}
			}
			// 璇锋眰缁撴潫鏍囧織
			byte[] end_data = (PREFIX + BOUNDARY + PREFIX + LINEND).getBytes();
			outStream.write(end_data);
			outStream.flush();
			String sb2 = null;
			if (conn.getResponseCode() == HttpStatus.SC_OK) {
				InputStream in = conn.getInputStream();
				sb2 = readStreamToString(in);

			}

			return sb2;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		} finally {

			try {
				outStream.close();
				conn.disconnect();
			} catch (Exception e) {
				// ignore.
				e.printStackTrace();
			}
		}

	}

	private static String readStreamToString(InputStream inStream) {
		ByteArrayOutputStream outStream = null;
		try {
			outStream = new ByteArrayOutputStream();
			byte[] buffer = new byte[1024];
			int len = 0;
			while ((len = inStream.read(buffer)) != -1) {
				outStream.write(buffer, 0, len);
			}
			inStream.close();
			outStream.flush();
			String ret = outStream.toString();
			outStream.close();
			return ret;
		} catch (Exception e) {
			try {
				inStream.close();
				outStream.close();
			} catch (Exception e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
			return null;
		}
	}

	/**
	 * 
	 * @param url
	 * @param params
	 * @param context
	 * @param pins
	 *            服务器各个证书的pin码集合。
	 * @return
	 */
	public static String get(String url, Map<String, String> params,
			Context context, String[] pins) {
		try {
			String realUrl = generateUrl(url, params);
			HttpClient client = getNewHttpClient(context, pins);
			HttpGet getMethod = new HttpGet(realUrl);
			HttpResponse response = client.execute(getMethod);
			if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
				StringBuilder builder = new StringBuilder();
				BufferedReader reader = new BufferedReader(
						new InputStreamReader(response.getEntity().getContent()));
				for (String s = reader.readLine(); s != null; s = reader
						.readLine()) {
					builder.append(s);
				}
				String result = builder.toString();
				return result;
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 
	 * @param url
	 * @param params
	 * @param context
	 * @param pins
	 *            服务器各个证书的pin码集合。
	 * @return
	 */
	public static String post(String url, Map<String, String> params,
			Context context, String[] pins) {
		try {
			HttpClient client = getNewHttpClient(context, pins);
			HttpPost postMethod = new HttpPost(url);
			List<BasicNameValuePair> pairs = new ArrayList<BasicNameValuePair>();
			if (params != null && params.size() > 0) {
				Iterator<Entry<String, String>> iterator = params.entrySet()
						.iterator();
				while (iterator.hasNext()) {
					Entry<String, String> param = iterator.next();
					String key = param.getKey();
					String value = param.getValue();
					BasicNameValuePair pair = new BasicNameValuePair(key, value);
					pairs.add(pair);
				}
				postMethod
						.setEntity(new UrlEncodedFormEntity(pairs, HTTP.UTF_8));
			}
			HttpResponse response = client.execute(postMethod);
			if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
				return EntityUtils.toString(response.getEntity());
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 鑾峰彇HttpClient
	 * 
	 * @return
	 */
	private static HttpClient getNewHttpClient(Context context, String[] pins) {
		try {

			HttpParams params = new BasicHttpParams();
			HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
			HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);
			HttpConnectionParams.setConnectionTimeout(params, CONNET_TIMEOUT);
			HttpConnectionParams.setSoTimeout(params, READ_TIMEOUT);

			SchemeRegistry registry = new SchemeRegistry();
			registry.register(new Scheme("http", PlainSocketFactory
					.getSocketFactory(), 80));
			registry.register(new Scheme("https", new PinningSSLSocketFactory(
					context, pins, 0), 443));
			ClientConnectionManager ccm = new ThreadSafeClientConnManager(
					params, registry);
			return new DefaultHttpClient(ccm, params);
		} catch (Exception e) {
			return new DefaultHttpClient();
		}
	}

	private static String generateUrl(String url, Map<String, String> params) {
		StringBuilder urlBuilder = new StringBuilder(url);
		if (null != params) {
			urlBuilder.append("?");
			Iterator<Entry<String, String>> iterator = params.entrySet()
					.iterator();
			while (iterator.hasNext()) {
				Entry<String, String> param = iterator.next();
				String key = param.getKey();
				String value = param.getValue();
				urlBuilder.append(key).append('=').append(value);
				if (iterator.hasNext()) {
					urlBuilder.append('&');
				}
			}
		}
		return urlBuilder.toString();
	}
}

备注:获取服务器证书pin码的方法:
package com.commonlib.utils;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

public class CalculatePins {

	private MessageDigest digest;

	public CalculatePins() throws NoSuchAlgorithmException {
		digest = MessageDigest.getInstance("SHA1");
	}

	/**
	 * 打印服务器的证书公钥(即pin码)
	 * @param host
	 * @param port
	 * @throws Exception
	 */
	public void fetchPrintServerPinHashs(String host, int port)
			throws Exception {
		final SSLContext conext = SSLContext.getInstance("TLS");
		PubKeyExtractingTrustManager tm = new PubKeyExtractingTrustManager();
		conext.init(null, new TrustManager[] { tm }, null);
		SSLSocketFactory fac = conext.getSocketFactory();

		SSLSocket socket = (SSLSocket) fac.createSocket(host, port);

		socket.setSoTimeout(10 * 1000);
		socket.startHandshake();
		socket.close();
	}

	private class PubKeyExtractingTrustManager implements X509TrustManager {

		@Override
		public void checkClientTrusted(X509Certificate[] chain, String authType)
				throws CertificateException {
			// TODO Auto-generated method stub

		}

		@Override
		public void checkServerTrusted(X509Certificate[] chain, String authType)
				throws CertificateException {
			for (X509Certificate cert : chain) {
				byte[] keyEncoded = cert.getPublicKey().getEncoded();
				byte[] pin = digest.digest(keyEncoded);
				String pinHex = bytesToHex(pin);
				System.out.println(pinHex);

			}

		}

		private String bytesToHex(byte[] bytes) {
			final char[] hexArray = "0123456789ABCDEF".toCharArray();
			char[] hexChars = new char[bytes.length * 2];
			for (int j = 0; j < bytes.length; j++) {
				int v = bytes[j] & 0xFF;
				hexChars[j * 2] = hexArray[v >>> 4];
				hexChars[j * 2 + 1] = hexArray[v & 0x0F];
			}
			return new String(hexChars);
		}

		@Override
		public X509Certificate[] getAcceptedIssuers() {
			// TODO Auto-generated method stub
			return null;
		}

	}

}





  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值