【android安全】之使用ssl验证保护网络数据传输安全。
2015-07-15 23:25
751 查看
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类里面,例如:
3,app进行网络访问时进行pin码验证。可使用如下工具类,调用时传入自己服务器的pin码集即可(该工具类依赖AndroidPinning库https://github.com/moxie0/AndroidPinning):
备注:获取服务器证书pin码的方法:
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 c2fc 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; } } }
相关文章推荐
- 从零开始学Android应用安全测试
- 【android安全】之防止apk被篡改后重编译。
- 金山手机毒霸工作原理
- Android操作系统安全
- 【Android病毒分析报告】 - BadNews
- SEAndroid概述
- Andorid APK反逆向解决方案---梆梆加固原理探寻
- Android签名机制
- Android手机Root后的安全问题汇总
- Android4.0内存Dex数据动态加载技术
- Android DEX安全攻防战
- Android第二个绕过签名认证漏洞原理
- 【Android病毒分析报告】 - ZooTiger “集恶意推广、隐私窃取、恶意吸费于一体”
- 【Android病毒分析报告】 - Andorid新病毒“UkyadPay”
- 【Android病毒分析报告】 - 新病毒FakeUmg “假面友盟”
- LBE 安全大师支持android 4.4注入分析
- HTC One X AT&T Root漏洞
- "伪中国移动客户端"--伪基站诈骗
- Android电话拨打权限绕过漏洞(CVE-2013-6272)分析