Java中的https证书问题

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Dancen/article/details/76682388

越来越多的网站由http迁移至https,Apple甚至要求必须使用https,现在iOS中的app一律都得使用https。

https与http之最大不同便在于安全性,多了一个证书。我们使用浏览器访问https站点的时候,会首先下载该站点的证书并安装,然后验证该证书的权威性。如果证书合法,有些浏览器会在网址栏处出现一个钩状或者绿色的标记;如果证书错误,但不正确,则无法访问;如果证书正确,但不是权威机构颁发的,例如网站自己签发的证书,则浏览器会需要用户确认是否继续访问。

Java中访问https站点和浏览器又有不同,其中一个差别便是对于权威机构的认定,一些浏览器能够正常访问的https站点,在Java中却访问不了,出现javax.net.ssl.SSLHandshakeException,其实就是因为Java认为证书不够权威。

如何解决这一问题呢?两种方式:

1 不进行证书验证,即所有https站点的证书都认为合法,可以访问所有https站点,缺点就是牺牲了https的安全性。

2 修改证书验证,即读取特定站点的证书,加入证书信任列表,这样就可以对指定的https进行访问了。

 

代码如下;

 

package com.dancen.util.https;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.util.Collection;

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

public class MyHttpsUtil
{
	public static void main(String[] args)
	{
		try
		{
			HttpsURLConnection connection = MyHttpsUtil.getMyHttpsURLConnection("https://src.test.com/home/config.json");
			System.out.println(connection.getContentType());
			InputStream is = connection.getInputStream();
			InputStreamReader isr = new InputStreamReader(is);
			BufferedReader br = new BufferedReader(isr);
			String data;
			
			while(null != (data = br.readLine()))
			{
				System.out.println(data);
			}
			
			br.close();
			isr.close();
			is.close();
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
	}
	
	/**
	 * 获取不安全的HttpsConnection
	 * 关闭证书验证,可以和任何https站点建立连接
	 * @param url
	 * @return
	 * @throws IOException
	 */
	public static HttpsURLConnection getMyHttpsURLConnection(String url) throws IOException
	{
		HttpsURLConnection rs = null;
		
		if(null != url && !url.isEmpty())
		{
			try
			{
				rs = (HttpsURLConnection)new URL(url.trim()).openConnection();
				TrustManager[] trustManagers = {new MyX509TrustManager()};
				SSLContext sslContext = SSLContext.getInstance("TLS"); 
				sslContext.init(null, trustManagers, new SecureRandom());
				rs.setSSLSocketFactory(sslContext.getSocketFactory());
				rs.setHostnameVerifier(new MyHostnameVerifier());
			}
			catch(Exception e)
			{
				throw new IOException(e);
			}
		}
		
		return rs;
	}
	
	/**
	 * 获取自定义的HttpsConnection
	 * 读取证书后,和特定的https站点建立连接
	 * @param url
	 * @param cerFilePath
	 * @return
	 * @throws IOException
	 */
	public static HttpsURLConnection getHttpsURLConnection(String url, String cerFilePath) throws IOException
	{
		HttpsURLConnection rs = null;
		
		if(null != url && !url.isEmpty())
		{
			try
			{
				if(null == cerFilePath || cerFilePath.isEmpty())
				{
					rs = getDefaultHttpsURLConnection(url);
				}
				else
				{
					rs = getHttpsURLConnection(url, new FileInputStream(cerFilePath.trim()));
				}
			}
			catch(Exception e)
			{
				throw new IOException(e);
			}
		}
		
		return rs;
	}
	
	/**
	 * 获取自定义的HttpsConnection
	 * 读取证书流后,可以和特定的https站点建立连接
	 * @param url
	 * @param cerInputStream
	 * @return
	 * @throws IOException
	 */
	public static HttpsURLConnection getHttpsURLConnection(String url, InputStream cerInputStream) throws IOException
	{
		HttpsURLConnection rs = null;
		
		if(null != url && !url.isEmpty())
		{
			try
			{
				rs = (HttpsURLConnection)new URL(url.trim()).openConnection();
				
				if(null != cerInputStream)
				{
					CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
		            Collection<? extends Certificate> certificates = certificateFactory.generateCertificates(cerInputStream);
		            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
		            keyStore.load(null, null);
		            int index = 0;
		            
		            if(null != certificates)
		            {
			            for(Certificate certificate : certificates)
			            {
			                String certificateAlias = Integer.toString(index++);
			                keyStore.setCertificateEntry(certificateAlias, certificate);
			            }
		            }
	
		            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
		            keyManagerFactory.init(keyStore, null);
		            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
		            trustManagerFactory.init(keyStore);
		            SSLContext sslContext = SSLContext.getInstance("TLS");
		            sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom());
		            rs.setSSLSocketFactory(sslContext.getSocketFactory());
				}
			}
			catch(Exception e)
			{
				throw new IOException(e);
			}
		}
		
		return rs;
	}
	
	/**
	 * 获取默认的HttpsURLConnection
	 * 只能与系统信任的https站点建立连接
	 * @param url
	 * @return
	 * @throws IOException
	 */
	public static HttpsURLConnection getDefaultHttpsURLConnection(String url) throws IOException
	{
		HttpsURLConnection rs = null;
		
		if(null != url && !url.isEmpty())
		{
			rs = (HttpsURLConnection)new URL(url.trim()).openConnection();
		}
		
		return rs;
	}
}

 

 

 

 

 

 

package com.dancen.util.https;

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

import javax.net.ssl.X509TrustManager;

public class MyX509TrustManager implements X509TrustManager
{
	@Override
	public void checkClientTrusted(X509Certificate ax509certificate[], String s) throws CertificateException
	{
		//TODO nothing
	}

	@Override
	public void checkServerTrusted(X509Certificate ax509certificate[], String s) throws CertificateException
	{
		//TODO nothing
	}

	@Override
	public X509Certificate[] getAcceptedIssuers()
	{
		return new X509Certificate[]{};
	}
}

 

package com.dancen.util.https;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;

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


附录:https相关知识

 

地址:http://kingj.iteye.com/blog/2103662

 

 

一:什么是https协议

 

     在说HTTPS之前先说说什么是HTTP,HTTP就是我们平时浏览网页时候使用的一种协议。HTTP协议传输的数据都是未加密的,也就是明文的,因此使 用HTTP协议传输隐私信息非常不安全。为了保证这些隐私数据能加密传输,于是网景公司设计了SSL(Secure Sockets Layer)协议用于对HTTP协议传输的数据进行加密,从而就诞生了HTTPS。SSL目前的版本是3.0,被IETF(Internet Engineering Task Force)定义在RFC 6101中,之后IETF对SSL 3.0进行了升级,于是出现了TLS(Transport Layer Security) 1.0,定义在RFC 2246。实际上我们现在的HTTPS都是用的TLS协议,但是由于SSL出现的时间比较早,并且依旧被现在浏览器所支持,因此SSL依然是HTTPS的 代名词,但无论是TLS还是SSL都是上个世纪的事情,SSL最后一个版本是3.0,今后TLS将会继承SSL优良血统继续为我们进行加密服务。目前 TLS的版本是1.2,定义在RFC 5246中,暂时还没有被广泛的使用。对历史感兴趣的朋友可以参考http://en.wikipedia.org/wiki/Transport_Layer_Security,这里有对TLS/SSL详尽的叙述。

 

二:https的工作原理是什么

     HTTPS在传输数据之前需要客户端(浏览器)与服务端(网站)之间进行一次握手,在握 手过程中将确立双方加密传输数据的密码信息,通常情况下会配合数字证书实现。

TLS/SSL协议不仅仅是一套加密传输的协议,更是一件经过艺术家精心设计的艺术品,TLS/SSL中使用 非对称加密对称加密以及HASH算法

这里我们先看看这上面提到的几种技术(如果你对这些技术已经非常了解,那么请跳过该段落,直接到段落三)

  1. 数字证书

        数字证书是一种权威性的电子文档,由权威公正的第三方机构,即CA中心签发的证书。它以数字证书为核心的加密技术可以对网络上传输的信息进行加密和解密、数字签名和签名验证,确保网上传递信息的机密性、完整性。 使用了数字证书,即使您发送的信息在网上被他人截获,甚至您丢失了个人的账户、密码等信息,仍可以保证您的账户、资金安全。 

     它能提供在Internet上进行身份验证的一种权威性电子文档,人们可以在互联网交往中用它来证明自己的身份和识别对方的身份。当然在数字证书认证的过程中证书认证中心(CA)作为权威的、公正的、可信赖的第三方,其作用是至关重要的.如何判断数字认证中心公正第三方的地位是权威可信的。VeriSign、GeoTrust、Thawte 是国际权威数字证书颁发认证机构的“三巨头”,其中,应用最广的为VerSign签发的电子商务数字证书。

CER(Canonical Encoding Rules,规范编码格式) 是数字证书的一种编码格式,它是BER(Basic Encoding Rules 基本编码格式) 的一个变种,比BER 规定得更严格。后缀的证书文件有两种编码:

DER(Distinguished Encoding Rule 卓越编码格式) 同样是BER的一个变种,DER使用定长模式。

PKCS(Public-Key Cryptography Standards,公钥加密标准) 由RSA实验室和其他安全系统开发商为公钥密码的发展而制定的一系列标准。

pfx是指以pkcs#12格式存储的证书和相应私钥。 

在Security编程中,有几种典型的密码交换信息文件格式: 
DER-encoded certificate: .cer, .crt 
PEM-encoded message: .pem 
PKCS#12 Personal Information Exchange: .pfx, .p12 
PKCS#10 Certification Request: .p10 .csr
PKCS#7 cert request response: .p7r 
PKCS#7 binary message: .p7b .p7c .spc

cer/.crt 是用于存放证书,它是2进制形式存放

pem 跟crt/cer的区别是它以Ascii来表示

pfx/p12 用于存放个人证书/私钥,他通常包含保护密码,2进制方式 

p10 .csr 是证书请求 

p7r是CA对证书请求的回复,只用于导入 

p7b .p7c .spc 以树状展示证书链(certificate chain),同时也支持单个证书,不含私钥

 

 

  1. 非对称加密算法

          1976年,美国学者Dime和Henman为解决信息公开传送和密钥管理问题,提出一种新的密钥交换协议,允许在不安全的媒体上的通讯双方交换信息,安全地达成一致的密钥,这就是"公开密钥系统"。相对于"对称加密算法"这种方法也叫做"非对称加密算法"。与对称加密算法不同,非对称加密算法需要两个密钥:公开密钥(publickey)和私有密(privatekey)。公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。

       非对称加密算法实现机密信息交换的基本过程是:甲方生成一对密钥并将其中的一把作为公用密钥向其它方公开;得到该公用密钥的乙方使用该密钥对机密信息进行加密后再发送给甲方;甲方再用自己保存的另一把专用密钥对加密后的信息进行解密。甲方只能用其专用密钥解密由其公用密钥加密后的任何信息。非对称加密算法的保密性比较好,它消除了最终用户交换密钥的需要,但加密和解密花费时间长、速度慢,它不适合于对文件加密而只适用于对少量数据进行加密。 经典的非对称加密算法如RSA算法等安全性都相当高. 非对称加密的典型应用是数字签名。采用双钥密码系统的加密方法,在一个过程中使用两个密钥,一个用于加密,另一个用于解密,这种加密方法称为非对称加密,也称为公钥加密,因为其中一个密钥是公开的(另一个则需要保密)。

DH (Diffie-Hellman)
       Diffie-Hellman算法(D-H算法),密钥一致协议。是由公开密钥密码体制的奠基人Diffie和Hellman所提出的一种思想。简单的说就是允许两名用户在公开媒体上交换信息以生成"一致"的、可以共享的密钥。换句话说,就是由甲方产出一对密钥(公钥、私钥),乙方依照甲方公钥产生乙方密钥对(公钥、私钥)。以此为基线,作为数据传输保密基础,同时双方使用同一种对称加密算法构建本地密钥(SecretKey)对数据加密。这样,在互通了本地密钥(SecretKey)算法后,甲乙双方公开自己的公钥,使用对方的公钥和刚才产生的私钥加密数据,同时可以使用对方的公钥和自己的私钥对数据解密。不单单是甲乙双方两方,可以扩展为多方共享数据通讯,这样就完成了网络交互数据的安全通讯!该算法源于中国的同余定理——中国馀数定理。

RSA
       RSA公钥加密算法是1977年由Ron Rivest、Adi Shamirh和LenAdleman在(美国麻省理工学院)开发的。RSA取名来自开发他们三者的名字。RSA是目前最有影响力的公钥加密算法,它能够抵抗到目前为止已知的所有密码攻击,已被ISO推荐为公钥数据加密标准。RSA算法基于一个十分简单的数论事实:将两个大素数相乘十分容易,但那时想要对其乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥。

EL Gamal
         EL Gamal算法是公钥密码体制中的一种 ,在密码学中占有重要的地位。但该算法所采用的幂剩余计算耗时太多的问题 ,一直是制约其广泛应用的瓶颈问题。提出一种通过建表 ,以及对传统二进制算法进行改进 ,即将指数进行 2 k进制化 ,减少原 BR算法迭代次数 ,提高加密解密速度的算法。

ECC 
ECC (Elliptical Curve Cryptography,椭圆曲线加密)算法不椭圆曲线理论为基础,在创建密钥时可以更快,更小,并且更有效,它是用大质数的积来产生。

目前Java 6公提供了DH和RSA两种算法实现,通过Bouncy Castle可以实现Elmal算法支持,另ECC加密算法,目前没有开源组件提支持

  1. 对称加密算法

        对加密和解密使用相同密钥的加密算法。由于其速度,对称性加密通常在消息发送方需要加密大量数据时使用。对称性加密也称为密钥加密。对称式数据加密的方式的工作原理如图。所谓对称,就是采用这种加密方法的双方使用方式用同样的密钥进行加密和解密。密钥实际上是一种算法,通信发送方使用这种算法加密数据,接收方再以同样的算法解密数据。因此对称式加密本身不是安全的。常用的对称加密有: 

DES、IDEA、RC2、RC4、SKIPJACK算法等 。

采用单钥密码系统的加密方法,同一个密钥可以同时用作信息的加密和解密,这种加密方法称为对称加密,也称为单密钥加密。

 

  1. HASH算法

常用的摘要算法包括MD5,SHA1,SHA256

消息摘要算法的特点:

① 无论输入的消息有多长,计算出来的消息摘要的长度总是固定的。
② 消息摘要看起来是“随机的”。这些比特看上去是胡乱的杂凑在一起的。
③ 一般地,只要输入的消息不同,对其进行摘要以后产生的摘要消息也必不相同;但相同的输入必会产生相同的输出。
④ 消息摘要函数是无陷门的单向函数,即只能进行正向的信息摘要,而无法从摘要中恢复出任何的消息,甚至根本就找不到任何与原信息相关的信息。
⑤ 好的摘要算法,无法找到两条消息,是它们的摘要相同。

 

消息摘要(Message Digest)又称为数字摘要(Digital Digest)。它是一个唯一对应一个消息或文本的固定长度的值,它由一个单向Hash加密函数对消息进行作用而产生。如果消息在途中改变了,则接收者通过对收到消息的新产生的摘要与原摘要比较,就可知道消息是否被改变了。因此消息摘要保证了消息的完整性。消息摘要采用单向Hash 函数将需加密 的明文"摘要"成一串128bit的密文,这一串密文亦称为数字指纹(Finger Print),它有固定的长度,且不同的明文摘要成密文,其结果总是不同的,而同样的明文其摘要必定一致 。这样这串摘要便可成为验证明文是否是"真身"的"指纹"了。


       HASH函数的抗冲突性使得如果一段明文稍有变化,哪怕只更改该段落的一个字母,通过哈希算法作用后都将产生不同的值。而HASH算法的单向性使得要找到到哈希值相同的两个不 同的输入消息,在计算上是不可能的。所以数据的哈希值,即消息摘要,可以检验数据的完整性。哈希函数的这种对不同的输入能够生成不同的值的特性使得无法找到两个具有相同哈希值的输入。因此,如果两个文档经哈希转换后成为相同的值,就可以肯定它们是同一文档。 所以,当希望有效地比较两个数据块时,就可以比较它们的哈希值。例如,可以通过比较邮件发送前和发送后的哈希值来验证该邮件在传递时是否修改


      消息摘要算法的主要特征是加密过程不需要密钥,并且经过加密的数据无法被解密,只有输入相同的明文数据经过相同的消息摘要算法才能得到相同的密文。消息摘要算法不存在 密钥的管理与分发问题,适合于分布式网络相同上使用。由于其加密计算的工作量相当可观,所以以前的这种算法通常只用于数据量有限的情况下的加密,例如计算机的口令就是 用不可逆加密算法加密的。

 

   三 https握手的过程详细描述

1.浏览器将自己支持的一套加密规则发送给网站,如RSA加密算法,DES对称加密算法,SHA1摘要算法
2.网站从中选出一组加密算法与HASH算法,并将自己的身份信息以证书的形式发回给浏览器。证书里面包含了网站地址,加密公钥,以及证书的颁发机构等信息(证书中的私钥只能用于服务器端进行解密,在握手的整个过程中,都用到了证书中的公钥和浏览器发送给服务器的随机密码以及对称加密算法


3.获得网站证书之后浏览器要做以下工作:
    a) 验证证书的合法性(颁发证书的机构是否合法,证书中包含的网站地址是否与正在访问的地址一致等),如果证书受信任,则浏览器栏里面会显示一个小锁头,否则会给出证书不受信的提示。
    b) 如果证书受信任,或者是用户接受了不受信的证书,浏览器会生成一串随机数的密码,并用证书中提供的公钥加密。
    c) 使用约定好的HASH算法计算握手消息(如SHA1),并使用生成的随机数对消息进行加密,最后将之前生成的被公钥加密的随机数密码,HASH摘要值一起发送给服务器


4.网站接收浏览器发来的数据之后要做以下的操作:
    a) 使用自己的私钥将信息解密并取出浏览器发送给服务器的随机密码,使用密码解密浏览器发来的握手消息,并验证HASH是否与浏览器发来的一致。
    b) 使用随机密码加密一段握手消息,发送给浏览器。
    5.浏览器解密并计算握手消息的HASH,如果与服务端发来的HASH一致,此时握手过程结束,之后所有的通信数据将由之前浏览器生成的随机密码并利用对称加密算法进行加密。

 

从上面的4个大的步骤可以看到,握手的整个过程使用到了数字证书、对称加密、HASH摘要算法。

 

展开阅读全文

没有更多推荐了,返回首页