对jdk1.6下https 处理异常的分析

在使用httpclient 工具类发送https请求时会出现在不构造ssl安全套接字的情况下,有的https可以直接发出去,而有些会报错

现象描述:


原因是jdk1.6对HTTPS处理不够完善,升级jdk1.7及以上版本可解决,以下是我自己分析过程

测试代码如下:

 public static void main(String[] args) throws Exception {
//       URL url = new URL("https://wx.zhimo.co");
//       String ="https://wx.zhimo.co/mslive/lives/video/check";
//       URL url = new URL("https://p.bokecc.com");
//       URL url = new URL("https://wx.zhimo.co/mslive/lives/video/check");
		 long start = System.currentTimeMillis();
       URL url = new URL("https://www.boxuegu.com");
       URLConnection con = url.openConnection();
       con.connect();  
       BufferedReader reader = new BufferedReader(new InputStreamReader(con.getInputStream(), "utf-8"));  
       String s;  
       while ((s = reader.readLine()) != null) {  
           System.out.println(s);  
       }  
       reader.close();  
       long end = System.currentTimeMillis();
       System.out.println(end-start);
   }

而在jdk 自带的HttpsURLConnection 构造ssl情况下也会出现发送https 失败的情况:代码如下:

public static String httpsRequestPost(String authplayurl, HashMap<String, String> param, int timeout) throws Exception {
		InputStream instream = null;
		BufferedReader br = null;
		
		try {
			//创建信任管理器TrustManager,其包含可信任材料TestTrustManager
			TrustManager[] tm = { new CCTrustManager() };
			//构造SSL环境,请求协议的标准名称为TLS
			SSLContext sc = SSLContext.getInstance("TLS");
//			SSLContext sc = SSLContext.getInstance("SSL");
			//初始化SSLContext,指定信任管理器TrustManager
			sc.init(null, tm, new SecureRandom());
			
			
			URL url = new URL(authplayurl);
			HttpsURLConnection httpsConn = (HttpsURLConnection) url.openConnection();
			httpsConn.setSSLSocketFactory(sc.getSocketFactory());
			httpsConn.setConnectTimeout(timeout * 1000);
			//对服务器证书经常改变做兼容(存在HTTPS中间人攻击漏洞)
//			httpsConn.setHostnameVerifier(new TrustAnyHostnameVerifier());
			httpsConn.setHostnameVerifier(new StrictHostnameVerifier());
//			HostnameVerifier   AllowAllHostnameVerifier
			httpsConn.setRequestMethod("POST");
			//设置是否使用缓存
		    httpsConn.setUseCaches(false);
			//设置是否添加参数
			httpsConn.setDoOutput(true);
			httpsConn.getURL();
			
			//参数处理
			StringBuffer buffer = new StringBuffer();
			for(Entry<String,String> entry : param.entrySet()){
				buffer.append("&").append(entry.getKey()).append("=").append(entry.getValue());
			}
			String buf = buffer.toString().isEmpty() ? "" : buffer.substring(1);
			OutputStream os = httpsConn.getOutputStream();
		    os.write(buf.getBytes("ISO-8859-1"));
            os.flush();
            os.close();
			
			//获取https请求的输入流
			instream = httpsConn.getInputStream();
			br = new BufferedReader(new InputStreamReader(instream,"ISO-8859-1"));
			String responseContent = "";
			String tmp = "";
			while ((tmp = br.readLine()) != null) {
				responseContent += tmp + "\r\n";
			}
			if (!responseContent.equals("")) {
				responseContent = responseContent.substring(0, responseContent.lastIndexOf("\r\n"));
			}
			Certificate[] certs = httpsConn.getServerCertificates();    //会拿到完整的证书链
	        X509Certificate cert = (X509Certificate)certs[0];    //cert[0]是证书链的最下层
	        System.out.println("序号:" + cert.getSerialNumber());
	        System.out.println("颁发给:" + cert.getSubjectDN().getName());
	        System.out.println("颁发者:" + cert.getIssuerDN().getName());
	        System.out.println("起始:" + cert.getNotBefore());
	        System.out.println("过期:" + cert.getNotAfter());
	        System.out.println("算法:" + cert.getSigAlgName());
	        System.out.println("指纹:" + getThumbPrint(cert));
			return new String(responseContent.getBytes("ISO-8859-1"), "UTF-8");
		} catch (KeyManagementException e) {
			logger.error("", e);
		} catch (NoSuchAlgorithmException e) {
			logger.error("", e);
		} catch (IOException e) {
			logger.error("", e);
		} finally {
			try {
				if (instream != null) {
					instream.close();
				}
				if (br != null) {
					br.close();
				}
			} catch (IOException e) {
				logger.error("", e);
			}
		}
		return null;
	}
	 private static String getThumbPrint(X509Certificate cert) throws Exception {
	        MessageDigest md = MessageDigest.getInstance("SHA-1");
	        byte[] der = cert.getEncoded();
	        md.update(der);
	        byte[] digest = md.digest();
	        return bytesToHexString(digest);
	    }
	     
	    private static String bytesToHexString(byte[] src) {
	        StringBuilder stringBuilder = new StringBuilder("");
	        if (src == null || src.length <= 0) {
	            return null;
	        }
	        for (int i = 0; i < src.length; i++) {
	            int v = src[i] & 0xFF;
	            String hv = Integer.toHexString(v);
	            if (hv.length() < 2) {
	                stringBuilder.append(0);
	            }
	            stringBuilder.append(hv);
	        }
	        return stringBuilder.toString();
	    }
	    public static String httpsRequestGet(String authplayurl, HashMap<String, String> param, int timeout) throws Exception {
			InputStream instream = null;
			BufferedReader br = null;
			
			try {
				//创建信任管理器TrustManager,其包含可信任材料TestTrustManager
				TrustManager[] tm = { new CCTrustManager() };
				//构造SSL环境,请求协议的标准名称为TLS
				SSLContext sc = SSLContext.getInstance("TLS");
//				SSLContext sc = SSLContext.getInstance("SSL");
				//初始化SSLContext,指定信任管理器TrustManager
				sc.init(null, tm, new SecureRandom());
				
				
				URL url = new URL(authplayurl);
				HttpsURLConnection httpsConn = (HttpsURLConnection) url.openConnection();
				httpsConn.setSSLSocketFactory(sc.getSocketFactory());
				httpsConn.setConnectTimeout(timeout * 1000);
				//对服务器证书经常改变做兼容(存在HTTPS中间人攻击漏洞)
//				httpsConn.setHostnameVerifier(new TrustAnyHostnameVerifier());
				httpsConn.setHostnameVerifier(new StrictHostnameVerifier());
//				HostnameVerifier   AllowAllHostnameVerifier
				httpsConn.setRequestMethod("POST");
				//设置是否使用缓存
			    httpsConn.setUseCaches(false);
				//设置是否添加参数
				httpsConn.setDoOutput(true);
				httpsConn.getURL();
				
				//参数处理
				StringBuffer buffer = new StringBuffer();
				for(Entry<String,String> entry : param.entrySet()){
					buffer.append("&").append(entry.getKey()).append("=").append(entry.getValue());
				}
				String buf = buffer.toString().isEmpty() ? "" : buffer.substring(1);
				OutputStream os = httpsConn.getOutputStream();
			    os.write(buf.getBytes("ISO-8859-1"));
	            os.flush();
	            os.close();
				
				//获取https请求的输入流
				instream = httpsConn.getInputStream();
				br = new BufferedReader(new InputStreamReader(instream,"ISO-8859-1"));
				String responseContent = "";
				String tmp = "";
				while ((tmp = br.readLine()) != null) {
					responseContent += tmp + "\r\n";
				}
				if (!responseContent.equals("")) {
					responseContent = responseContent.substring(0, responseContent.lastIndexOf("\r\n"));
				}
				Certificate[] certs = httpsConn.getServerCertificates();    //会拿到完整的证书链
		        X509Certificate cert = (X509Certificate)certs[0];    //cert[0]是证书链的最下层
		        System.out.println("序号:" + cert.getSerialNumber());
		        System.out.println("颁发给:" + cert.getSubjectDN().getName());
		        System.out.println("颁发者:" + cert.getIssuerDN().getName());
		        System.out.println("起始:" + cert.getNotBefore());
		        System.out.println("过期:" + cert.getNotAfter());
		        System.out.println("算法:" + cert.getSigAlgName());
		        System.out.println("指纹:" + getThumbPrint(cert));
				return new String(responseContent.getBytes("ISO-8859-1"), "UTF-8");
			} catch (KeyManagementException e) {
				logger.error("", e);
			} catch (NoSuchAlgorithmException e) {
				logger.error("", e);
			} catch (IOException e) {
				logger.error("", e);
			} finally {
				try {
					if (instream != null) {
						instream.close();
					}
					if (br != null) {
						br.close();
					}
				} catch (IOException e) {
					logger.error("", e);
				}
			}
			return null;
		}

经过几天的查资料发现,根本原因是HostnameVerifier host校验的时候出的问题, 代码中拿到的证书的host跟url的不一致导致的域名验证一直不通过, 

而host校验失败的原因是jdk 1.6 不支持SNI (Server Name Indication)的校验, 

1.jdk 1.7以后新特性   安全/加密
椭圆曲线加密算法 (ECC),提供了一个可移植的标准椭圆曲线加密算法实现,所有的 Java 应用都可以使用椭圆曲线加密算法。
JSSE(SSL/TLS)
在证书链认证中设置关闭弱加密算法,比如 MD2 算法已经被证实不太安全。
增加对 TLS(Transport Layer Security) 1.1 和 1.2 的支持,它们对应的规范分别是 RFC 4346 和 RFC 5246。
SNI(Server Name Indication) 支持,其规范定义在 RFC 4366。
TLS 密钥重新协商机制,RFC 5746。

附上自己查的大神的文章

SNI

https://blog.csdn.net/makenothing/article/details/53292335

https主机名校验

https://www.aliyun.com/jiaocheng/547639.html

忽略hostname 校验的隐患

http://www.droidsec.cn/android-https%E4%B8%AD%E9%97%B4%E4%BA%BA%E5%8A%AB%E6%8C%81%E6%BC%8F%E6%B4%9E%E6%B5%85%E6%9E%90/


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值