漫话https编程

长话短说,复杂的协议简单说。HTTPS(全称:Hyper Text Transfer Protocol over Secure Socket Layer),是以安全为目标的HTTP通道,简单讲是HTTP的安全版。为啥安全呢,简单的说,他在建立连接前,会通过对互相持有的证书检验,对彼此身份进行检验。证书使用的非对称的RSA算法实现。分三种情况,一。如果是单向验证的话,在开放系统里,客户端会通过证书对服务的身份进行检验,如登录https://www.baidu.com,如果是http直接就建立连接了,如果是https,客户端会检查服务器端的证书是不是合法,是不是假冒的,防止钓鱼网站和木马。二。如果是单向验证的封闭系统,比如只对公司员工开放的内网服务,使用https就可以在服务器端对客户端证书检验,公司电脑可以正常访问,非公司电脑不建立连接。三。如果双向验证,那就严格了,不过原理同一二,只是把一二结合了起来。当然这是从编程角度来看的简化过程,是编写程序需要了解的基本知识。事实上https协议十分复杂,检验过程繁琐,除了用到不对称加密,可能还用到对称加密。但这些复杂的过程前辈们已经封装好了,不需要我们过分操心,我们只要准备好私钥,公钥,了解了他们的运用过程就可以写程序了。

通信过程用到证书,不可不说。用到的证书使用非对称加密算法生成。一套证书一般包含一对两个密钥,一个公钥和一个私钥,公钥给对方,私钥自己持有。这对密钥一个特点是公钥加密只能私钥解密,私钥加密只能公钥解密,公钥与公钥互相不能解密。单向验证一般只需要一个证书,双向验证需要两个证书。如果是类似百度开放系统,一般会对外公布他的公钥,客户端通过公钥在中介机构查询该证书是不是合法,拥有者是不是百度,如果是了,会有进一步验证,用该公钥加密信息传给这个网站,网站使用私钥解密,如果解密正常,一般认为网站就是百度,如假包换。如果有钓鱼网站下载百度的公钥,假冒百度,虽然第一步能通过,但因为他缺少百度私钥,无法解密公钥加密的信息,所以第二步就会被认出来是假的。单向的封闭系统原理一样,服务器会存储客户端的公钥,客户端保存私钥,通信时如果不是互相解密,则系统认为互相不可信任,则无法建立安全连接。双向验证需要两对证书,客户端存储自己的私钥和服务器的公钥,服务器存储自己的私钥和客户端公钥,客服端通过公钥验证服务器是否假冒,服务反过来再验证客户端是否可信。详细验证过程同单向。不赘述。

开放性的网站证书需要在中介机构申请,封闭性可申请,也可不申请,自己使用可以keytool工具根据需要生成。服务器和客户端都要保管好自己的私钥,私钥被别人获取,他们就可以假冒对方和你通信或者假冒你和另一方通信,系统将不再安全,所以一旦发现被偷盗或者遗失,就要快速废弃重新申请或者重新生成。

https编程就是把个人的证书和服务器的证书在https初始化时做为参数然后传递给服务器。证书各类非常多,应用场景也非常多,研究起来十分复杂。这里只做简单的研究。通常用于https通信的证书标准是X509,按编码格式分两大类,一类是二进制证书DER,一类是BASE编码的字符型证书PEM,按存储类型又可以分为以下:pfx/p12,jks,bks,pem,cer,der,cert等,大都和平台相关,BKS对应安卓,JKS用于JAVA默认,p12对应WIN,pem对应linux, cer对应服务器。这些格式之间都可以相互转换,而且可以包含相同内容的密钥。这意味着在不同两台,android,linux,mac,win下载,对于编程来说,哪怕使用的是跨平台的JAVA语言,考虑到apache,nignix等各种服务器架构,证书的初始化,加载方式可能都会不一样,一样的代码没办法通吃,需要根据服务器和客户端平台调整,这就是网上看到的程序在实用上有人可以成功,有人却总是失败,有时你在这个平台成功了,换台电脑或者平台又失败,再加上各种证书认证机构,所以最大的坑其实出现在证书异常上。所以在客户端编程上,对服务器证书是否假冒,对服务器身份认证上,编程访问可能会有很多种失败的因素,有些知名的浏览器也会对知名网站访问失败,可见这一切的坑有大呀。所以很多知名软件,app为了提高访问成功概率,在对https网站访问时,使用了跳过验证和全信任的策略,这虽然降低了安全性,但提升了用户体验,而且极大的降低了编程的难度。但这明显是以降低用户安全性为代价的,不可取。

常用的证书命令和转换命令:

1.生成带密钥的证书,设置别名,时效,其他使用默认,别名在证书操作过程十分重要:

keytool -genkey -alias certificatekey  -keyalg RSA -validity 700  -keystore keystore.jks

另一个比较懒人用的命令,可以减少输入,节省时间和出错之机会,保存成文件后,可以备忘证书名字,密码,私钥密码,期限之类,把其他参数改成自己需要的就好了:

keytool -genkey -keyalg RSA -keysize 512 -dname "cn=hyq,o=eagle,c=cn" -alias weblogic -keypass 123456 -keystore C:/mykeystore/weblogic.jks -storepass 123456 -validity 365

2.查看服务器端证书,这是个很有用的命令,连接失败时,可以通过查看服务器和客户端证书之区别

扫描二维码关注公众号,回复: 3408200 查看本文章

openssl s_client -connect www.baidu.com:443 -showcerts

3.查看证书,如果需要查看某个刚导入证书库的证书,可以使用下面一条验证证书是否导入成功

keytool -list -keystore client.jks

keytool -list -v -keystore client.jks

keytool -list -rfc -keystore client.jks

keytool -list -keystore client.jks -alias baiducom -storepass password

4.从浏览器导出的证书一般为pem,cer,der等格式,查看der,cer二进制格式证书和pem字符证书,pem可以使用CURL直接验证服务连接

openssl x509 -in baidu.cer -inform der -noout -text

openssl x509 -in keytool_crt.pem -inform pem -noout -text

也可以使用keytool看

keytool -printcert -file  baidu.cer

keytool -printcert -rfc -file  baidu.cer

5.把cer证书导入java可信库

keytool -import -keystore "jre6\lib\security\cacerts"  -storepass changeit -keypass changeit -alias baiducom -trustcacerts -file baidu.cer

6.把cer证书导出

keytool -import -keystore "jre6\lib\security\cacerts"  -storepass changeit -keypass changeit -alias baiducom -file baidu2.cer

7.删除证书,担心证书库变乱,测试的证书就会删除

keytool -delete -alias baiducom -keystore "jre6\lib\security\cacerts" -storepass changeit

8.p12(pkcs12 )和jks转换,p12证书可以包含公钥和私钥,常用于个人或者客户端证书存储

keytool -importkeystore -srckeystore test.p12 -srcstoretype PKCS12 -deststoretype JKS -destkeystore test.jks

keytool -importkeystore -srckeystore test.jks -srcstoretype JKS -deststoretype PKCS12 -destkeystore test1.p12

9.p12包含私钥和公钥,可以用来作为客户端的证书,也可以导出pem,pem也可以转p12

openssl pkcs12 -clcerts -nokeys -in cert.pfx -out client.pem    #客户端个人证书的公钥  

openssl pkcs12 -nocerts -nodes -in cert.pfx -out key.pem #客户端个人证书的私钥

也可以转换为公钥与私钥合二为一的文件

openssl pkcs12 -in  cert.pfx -out all.pem -nodes                                   #客户端公钥与私钥,一起存在all.pem中

//PEM-->PKCS12

openssl pkcs12 -export -out cacert.p12 -in cacert.pem

//PKCS12-->CRT

openssl pkcs12 -in cacert.p12 -out mycerts.crt -nokeys -clcerts

//CRT-->CER

openssl x509 -inform pem -in mycerts.crt -outform der -out mycerts.cer

去除pem格式的key的密码

openssl rsa -in cert2.key -out cert22.key

合并pem格式输出pfx(p12)

openssl pkcs12 -export -inkey cert22.key -in cert2.crt -out cert2.pfx

可以使用curl调用PEM访问https来测试客户端证书

a、使用client.pem+key.pem

curl -k --cert client.pem --key key.pem https://www.xxxx.com

 

b、使用all.pem

curl -k --cert all.pem  https://www.xxxx.com

curl -d '@wxpay_refund.txt' --cacert rootca.pem  -k --cert ./apiclient_cert.pem --key apiclient_key.pem https://api.mch.weixin.qq.com/secapi/pay/refund

10.pem,cer,der.cert互相转换

openssl x509 -in cert22.pem -out cert22.crt
openssl x509 -in cert2.cer -out cert2.pem -outform PEM
openssl x509 -in cert22.pem -inform PEM -out cert22.der -outform DER
openssl x509 -in cert22.cer -inform DER -out cert22.pem -outform  PEM

11JKS文件转换为KEYSTORE文件

//JKS--->P12

keytool -importkeystore -srckeystore D:\test.keystore -srcstoretype JKS -deststoretype PKCS12 -destkeystore test1.p12

//P12---->KEYSTORE

keytool -v -importkeystore -srckeystore D:\test.p12 -srcstoretype PKCS12 -destkeystore D:\test.keystore -deststoretype JKS

//keystore--->crt

keytool -export -alias test -file D:\test.crt -keystore D:\test.keystore

//CRT-->CER

openssl x509 -inform pem -in test.crt -outform der -out test.cer

//CER--->JKS

keytool -import -v -alias test -file test.cer -keystore test.jks -storepass 123456 -noprompt

12 cer转bks,bks用于安卓APP开发,主要是用于存储服务器端证书,需要bcprov-jdk16-141.jar

keytool -import -file service.cer -keystore client.bks -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider 

keytool -importcert -keystore keyStore.bks -file peer.cert -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider
eytool -importcert -trustcacerts -keystore trustStore.bks -file peer.cert -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider

13.一个好用的命令行工具importcert.sh,修改后可以用来导出bks,这个bcprov文件使用脚本不需要导入java库,因为导入好像会和android冲突

#!/bin/bash

if [ -z $1 ]; then
  echo "Usage: importcert.sh <CA cert PEM file>"
  exit 1
fi

CACERT=$1
BCJAR=bcprov-jdk16-146.jar

TRUSTSTORE=res/raw/mytruststore.bks
ALIAS=`openssl x509 -inform PEM -subject_hash -noout -in $CACERT`

if [ -f $TRUSTSTORE ]; then
    rm $TRUSTSTORE || exit 1
fi

echo "Adding certificate to $TRUSTSTORE..."
keytool -import -v -trustcacerts -alias $ALIAS \
      -file $CACERT \
      -keystore $TRUSTSTORE -storetype BKS \
      -providerclass org.bouncycastle.jce.provider.BouncyCastleProvider \
      -providerpath $BCJAR \
      -storepass secret

echo "" 
echo "Added '$CACERT' with alias '$ALIAS' to $TRUSTSTORE...


单向验证,开放的服务器端验证,不需要验证客户端,只需要服务器端证书:

import java.io.File;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;

import javax.net.ssl.SSLContext;

import org.apache.http.HttpHost;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;

public class OneTLSPool {
    public static CloseableHttpClient httpclient;
    // 获得池化得HttpClient
    static {
        // 设置truststore
        SSLContext sslcontext = null;
        try {
            sslcontext = SSLContexts
                    .custom()
                    .loadTrustMaterial(
                            new File("D://https//ca//cl.jks"),
                            "123456".toCharArray(),
                            new TrustSelfSignedStrategy()).build();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        // 客户端支持TLSV1,TLSV2,TLSV3这三个版本
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
                sslcontext, new String[] { "TLSv1", "TLSv2", "TLSv3" }, null,
                SSLConnectionSocketFactory.getDefaultHostnameVerifier());// 客户端验证服务器身份的策略

        // Create a registry of custom connection socket factories for supported
        // protocol schemes.
        Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder
                .<ConnectionSocketFactory> create()
                .register("http", PlainConnectionSocketFactory.INSTANCE)
                .register("https", new SSLConnectionSocketFactory(sslcontext))
                .build();
        PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(
                socketFactoryRegistry);
        // Configure total max or per route limits for persistent connections
        // that can be kept in the pool or leased by the connection manager.
        connManager.setMaxTotal(100);
        connManager.setDefaultMaxPerRoute(10);
        // 个性化设置某个url的连接
        connManager.setMaxPerRoute(new HttpRoute(new HttpHost("www.y.com",
                80)), 20);
        httpclient = HttpClients.custom().setConnectionManager(connManager)
                .build();

    }

  /**
     * 单向验证且服务端的证书可信
     * @throws IOException 
     * @throws ClientProtocolException 
     */
    public static void oneWayAuthorizationAccepted() throws ClientProtocolException, IOException {
        // Execution context can be customized locally.
        HttpClientContext context = HttpClientContext.create();
        HttpGet httpget = new HttpGet("https://www.yunzhu.com:8443");
        // 设置请求的配置
        RequestConfig requestConfig = RequestConfig.custom()
                .setSocketTimeout(5000).setConnectTimeout(5000)
                .setConnectionRequestTimeout(5000).build();
        httpget.setConfig(requestConfig);

        System.out.println("executing request " + httpget.getURI());
        CloseableHttpResponse response = httpclient.execute(httpget, context);
        try {
            System.out.println("----------------------------------------");
            System.out.println(response.getStatusLine());
            System.out.println(EntityUtils.toString(response.getEntity()));
            System.out.println("----------------------------------------");

            // Once the request has been executed the local context can
            // be used to examine updated state and various objects affected
            // by the request execution.

            // Last executed request
            context.getRequest();
            // Execution route
            context.getHttpRoute();
            // Target auth state
            context.getTargetAuthState();
            // Proxy auth state
            context.getTargetAuthState();
            // Cookie origin
            context.getCookieOrigin();
            // Cookie spec used
            context.getCookieSpec();
            // User security token
            context.getUserToken();

        } catch (Exception e) {
            e.printStackTrace();
        }

    }
    public static void main(String[] a) throws KeyManagementException,
            NoSuchAlgorithmException, KeyStoreException, CertificateException,
            IOException {
        oneWayAuthorizationAccepted();
    }
}

一个改进的单向验证

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

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

import org.apache.http.HttpHost;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;

public class OneTLSPool {
    public static CloseableHttpClient httpclient;
    public static final String KEY_STORE_TRUST_PATH = "D://https//ca//cl.jks"; // truststore的路径
    public static final String KEY_STORE_TYPE_JKS = "jks"; // truststore的类型
    private static final String KEY_STORE_TRUST_PASSWORD = "123456"; // truststore的密码
    // 获得池化得HttpClient
    static {
        SSLContext sslcontext = null;
        try {
            // 设置truststore
            KeyStore trustStore = KeyStore.getInstance(KEY_STORE_TYPE_JKS);
            InputStream tsIn = new FileInputStream(new File(KEY_STORE_TRUST_PATH));
            try {
                trustStore.load(tsIn, KEY_STORE_TRUST_PASSWORD.toCharArray());
            } finally {
                try {
                    tsIn.close();
                } catch (Exception ignore) {
                }
            }
            sslcontext = SSLContexts.custom().loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()).build();
            //解决jdk7的ssl的自签名会有问题的bug,如果不是jdk7,则下面的代码可以没有
            //bug地址:http://bugs.java.com/bugdatabase/view_bug.do?bug_id=7018897
            X509TrustManager xtm = new X509TrustManager(){   //创建TrustManager    
                public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}    
                public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {}    
                public X509Certificate[] getAcceptedIssuers() {   
                    return null;   //return new java.security.cert.X509Certificate[0];    
                }  
            };   
            sslcontext.init(null, new TrustManager[]{xtm}, null);
            //解决bug结束
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 客户端支持TLSV1,TLSV2,TLSV3这三个版本
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext,
                new String[] { "TLSv1", "TLSv2", "TLSv3" }, null,
                SSLConnectionSocketFactory.getDefaultHostnameVerifier());// 客户端验证服务器身份的策略

        // Create a registry of custom connection socket factories for supported
        // protocol schemes.
        Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory> create()
                .register("http", PlainConnectionSocketFactory.INSTANCE)
                .register("https", new SSLConnectionSocketFactory(sslcontext)).build();
        PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
        // Configure total max or per route limits for persistent connections
        // that can be kept in the pool or leased by the connection manager.
        connManager.setMaxTotal(100);
        connManager.setDefaultMaxPerRoute(10);
        // 个性化设置某个url的连接
        connManager.setMaxPerRoute(new HttpRoute(new HttpHost("www.y.com", 80)), 20);
        httpclient = HttpClients.custom().setConnectionManager(connManager).build();

    }

    /**
     * 单向验证且服务端的证书可信
     * 
     * @throws IOException
     * @throws ClientProtocolException
     */
    public static void oneWayAuthorizationAccepted() throws ClientProtocolException, IOException {
        // Execution context can be customized locally.
        HttpClientContext context = HttpClientContext.create();
        HttpGet httpget = new HttpGet("https://www.yunzhu.com:8443");
        // 设置请求的配置
        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(5000).setConnectTimeout(5000)
                .setConnectionRequestTimeout(5000).build();
        httpget.setConfig(requestConfig);

        System.out.println("executing request " + httpget.getURI());
        CloseableHttpResponse response = httpclient.execute(httpget, context);
        try {
            System.out.println("----------------------------------------");
            System.out.println(response.getStatusLine());
            System.out.println(EntityUtils.toString(response.getEntity()));
            System.out.println("----------------------------------------");

            // Once the request has been executed the local context can
            // be used to examine updated state and various objects affected
            // by the request execution.

            // Last executed request
            context.getRequest();
            // Execution route
            context.getHttpRoute();
            // Target auth state
            context.getTargetAuthState();
            // Proxy auth state
            context.getTargetAuthState();
            // Cookie origin
            context.getCookieOrigin();
            // Cookie spec used
            context.getCookieSpec();
            // User security token
            context.getUserToken();

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public static void main(String[] a) throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException,
            CertificateException, IOException {
        oneWayAuthorizationAccepted();
    }
}

双向验证,客户端使用p12,服务器使用jks

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import javax.net.ssl.SSLContext;
import org.apache.http.HttpHost;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;

public class DoubleWayTlsPool {
    public static CloseableHttpClient httpclient;
    public static final String KEY_STORE_TRUST_PATH = "D://https//ca//cl.jks"; // truststore的路径
    public static final String KEY_STORE_TYPE_JKS = "jks"; // truststore的类型
    private static final String KEY_STORE_TRUST_PASSWORD = "123456"; // truststore的密码
    public static final String KEY_STORE_CLIENT_PATH="D://https//client//client.p12";
    public static final String KEY_STORE_TYPE_P12="PKCS12";  
    private static final String KEY_STORE_PASSWORD="123456";
    // 获得池化得HttpClient
    static {
        SSLContext sslcontext = null;
        try {
            // 设置truststore
            KeyStore trustStore = KeyStore.getInstance(KEY_STORE_TYPE_JKS);
            KeyStore keyStore  = KeyStore.getInstance(KEY_STORE_TYPE_P12);  
            InputStream ksIn = new FileInputStream(KEY_STORE_CLIENT_PATH);  
            InputStream tsIn = new FileInputStream(new File(KEY_STORE_TRUST_PATH));
            try {
                 keyStore.load(ksIn, KEY_STORE_PASSWORD.toCharArray());  
                trustStore.load(tsIn, KEY_STORE_TRUST_PASSWORD.toCharArray());
            } finally {
                try {
                    ksIn.close();
                    tsIn.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            sslcontext = SSLContexts.custom().loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()).loadKeyMaterial(keyStore, KEY_STORE_PASSWORD.toCharArray()).build();
            //下面的代码可以动态的设置握手验证证书的策略,可以不用手工导入证书,而只要程序控制即可
            //bug地址:http://bugs.java.com/bugdatabase/view_bug.do?bug_id=7018897
        /*    X509TrustManager xtm = new X509TrustManager(){   //创建TrustManager    
                public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}    
                public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {}    
                public X509Certificate[] getAcceptedIssuers() {   
                    return null;   //return new java.security.cert.X509Certificate[0];    
                }  
            };   
            sslcontext.init(null, new TrustManager[]{xtm}, null);*/
            //解决bug结束
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 客户端支持TLSV1,TLSV2,TLSV3这三个版本
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext,
                new String[] { "TLSv1", "TLSv2", "TLSv3" }, null,
                SSLConnectionSocketFactory.getDefaultHostnameVerifier());// 客户端验证服务器身份的策略

        // Create a registry of custom connection socket factories for supported
        // protocol schemes.
        Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory> create()
                .register("http", PlainConnectionSocketFactory.INSTANCE)
                .register("https", new SSLConnectionSocketFactory(sslcontext)).build();
        PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
        // Configure total max or per route limits for persistent connections
        // that can be kept in the pool or leased by the connection manager.
        connManager.setMaxTotal(100);
        connManager.setDefaultMaxPerRoute(10);
        // 个性化设置某个url的连接
        connManager.setMaxPerRoute(new HttpRoute(new HttpHost("www.y.com", 80)), 20);
        httpclient = HttpClients.custom().setConnectionManager(connManager).build();

    }

    /**
     * 单向验证且服务端的证书可信
     * 
     * @throws IOException
     * @throws ClientProtocolException
     */
    public static void doubleWayAuthorizationAccepted() throws ClientProtocolException, IOException {
        // Execution context can be customized locally.
        HttpClientContext context = HttpClientContext.create();
        HttpGet httpget = new HttpGet("https://www.yunzhu.com:8443");
        // 设置请求的配置
        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(5000).setConnectTimeout(5000)
                .setConnectionRequestTimeout(5000).build();
        httpget.setConfig(requestConfig);

        System.out.println("executing request " + httpget.getURI());
        CloseableHttpResponse response = httpclient.execute(httpget, context);
        try {
            System.out.println("----------------------------------------");
            System.out.println(response.getStatusLine());
            System.out.println(EntityUtils.toString(response.getEntity()));
            System.out.println("----------------------------------------");

            // Once the request has been executed the local context can
            // be used to examine updated state and various objects affected
            // by the request execution.

            // Last executed request
            context.getRequest();
            // Execution route
            context.getHttpRoute();
            // Target auth state
            context.getTargetAuthState();
            // Proxy auth state
            context.getTargetAuthState();
            // Cookie origin
            context.getCookieOrigin();
            // Cookie spec used
            context.getCookieSpec();
            // User security token
            context.getUserToken();

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public static void main(String[] a) throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException,
            CertificateException, IOException {
        doubleWayAuthorizationAccepted();
    }
}

如果连接出错,很可能是证书出错,需要把服务器端证书导入java证书库。也可以使用下面方法,在运行时加载库

  1. Properties systemProps = System.getProperties();  
  2. systemProps.put( "javax.net.ssl.trustStore""/path/to/yourTruststore.jks");  
  3. systemProps.put( "javax.net.ssl.trustStorePassword""trustStorePassword");  
  4. System.setProperties(systemProps);  

也可以使用命令运行参数加载证书

  1. java -Djavax.net.ssl.trustStore=yourTruststore.jks -Djavax.net.ssl.trustStorePassword=123456 YourApp  

导出服务器端证书可以使用浏览器,导出之后需要设置导入证书,过程简单,但有些人总是搞不好,有位大牛提供一个脚本,运行后可以把服务器直接下载导入java证书库,其他就不用管了,直接使用就行了

import java.io.*;  
import java.net.URL;  
  
import java.security.*;  
import java.security.cert.*;  
  
import javax.net.ssl.*;  
  
public class InstallCert {  
  
    public static void main(String[] args) throws Exception {  
    String host;  
    int port;  
    char[] passphrase;  
    if ((args.length == 1) || (args.length == 2)) {  
        String[] c = args[0].split(":");  
        host = c[0];  
        port = (c.length == 1) ? 443 : Integer.parseInt(c[1]);  
        String p = (args.length == 1) ? "changeit" : args[1];  
        passphrase = p.toCharArray();  
    } else {  
        System.out.println("Usage: java InstallCert <host>[:port] [passphrase]");  
        return;  
    }  
  
    File file = new File("jssecacerts");  
    if (file.isFile() == false) {  
        char SEP = File.separatorChar;  
        File dir = new File(System.getProperty("java.home") + SEP  
            + "lib" + SEP + "security");  
        file = new File(dir, "jssecacerts");  
        if (file.isFile() == false) {  
        file = new File(dir, "cacerts");  
        }  
    }  
    System.out.println("Loading KeyStore " + file + "...");  
    InputStream in = new FileInputStream(file);  
    KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());  
    ks.load(in, passphrase);  
    in.close();  
  
    SSLContext context = SSLContext.getInstance("TLS");  
    TrustManagerFactory tmf =  
        TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());  
    tmf.init(ks);  
    X509TrustManager defaultTrustManager = (X509TrustManager)tmf.getTrustManagers()[0];  
    SavingTrustManager tm = new SavingTrustManager(defaultTrustManager);  
    context.init(null, new TrustManager[] {tm}, null);  
    SSLSocketFactory factory = context.getSocketFactory();  
  
    System.out.println("Opening connection to " + host + ":" + port + "...");  
    SSLSocket socket = (SSLSocket)factory.createSocket(host, port);  
    socket.setSoTimeout(10000);  
    try {  
        System.out.println("Starting SSL handshake...");  
        socket.startHandshake();  
        socket.close();  
        System.out.println();  
        System.out.println("No errors, certificate is already trusted");  
    } catch (SSLException e) {  
        System.out.println();  
        e.printStackTrace(System.out);  
    }  
  
    X509Certificate[] chain = tm.chain;  
    if (chain == null) {  
        System.out.println("Could not obtain server certificate chain");  
        return;  
    }  
  
    BufferedReader reader =  
        new BufferedReader(new InputStreamReader(System.in));  
  
    System.out.println();  
    System.out.println("Server sent " + chain.length + " certificate(s):");  
    System.out.println();  
    MessageDigest sha1 = MessageDigest.getInstance("SHA1");  
    MessageDigest md5 = MessageDigest.getInstance("MD5");  
    for (int i = 0; i < chain.length; i++) {  
        X509Certificate cert = chain[i];  
        System.out.println  
            (" " + (i + 1) + " Subject " + cert.getSubjectDN());  
        System.out.println("   Issuer  " + cert.getIssuerDN());  
        sha1.update(cert.getEncoded());  
        System.out.println("   sha1    " + toHexString(sha1.digest()));  
        md5.update(cert.getEncoded());  
        System.out.println("   md5     " + toHexString(md5.digest()));  
        System.out.println();  
    }  
  
    System.out.println("Enter certificate to add to trusted keystore or 'q' to quit: [1]");  
    String line = reader.readLine().trim();  
    int k;  
    try {  
        k = (line.length() == 0) ? 0 : Integer.parseInt(line) - 1;  
    } catch (NumberFormatException e) {  
        System.out.println("KeyStore not changed");  
        return;  
    }  
  
    X509Certificate cert = chain[k];  
    String alias = host + "-" + (k + 1);  
    ks.setCertificateEntry(alias, cert);  
  
    OutputStream out = new FileOutputStream("jssecacerts");  
    ks.store(out, passphrase);  
    out.close();  
  
    System.out.println();  
    System.out.println(cert);  
    System.out.println();  
    System.out.println  
        ("Added certificate to keystore 'jssecacerts' using alias '"  
        + alias + "'");  
    }  
  
    private static final char[] HEXDIGITS = "0123456789abcdef".toCharArray();  
  
    private static String toHexString(byte[] bytes) {  
    StringBuilder sb = new StringBuilder(bytes.length * 3);  
    for (int b : bytes) {  
        b &= 0xff;  
        sb.append(HEXDIGITS[b >> 4]);  
        sb.append(HEXDIGITS[b & 15]);  
        sb.append(' ');  
    }  
    return sb.toString();  
    }  
  
    private static class SavingTrustManager implements X509TrustManager {  
  
    private final X509TrustManager tm;  
    private X509Certificate[] chain;  
  
    SavingTrustManager(X509TrustManager tm) {  
        this.tm = tm;  
    }  
  
    public X509Certificate[] getAcceptedIssuers() {  
        throw new UnsupportedOperationException();  
    }  
  
    public void checkClientTrusted(X509Certificate[] chain, String authType)  
        throws CertificateException {  
        throw new UnsupportedOperationException();  
    }  
  
    public void checkServerTrusted(X509Certificate[] chain, String authType)  
        throws CertificateException {  
        this.chain = chain;  
        tm.checkServerTrusted(chain, authType);  
    }  
    }  
  
}  

如果你仍然没有办法访问成功https网站,可以参考附录的一些分析方法查找原因。如果在某些平台不成功,如果安全性要求不高,可以考虑使用能用证书和设置全信任的方法解决。

X509TrustManager[] xtms = new X509TrustManager[] {new X509TrustManager() { // 创建TrustManager
				@Override
				public void checkClientTrusted(java.security.cert.X509Certificate[] arg0, String arg1)
						throws CertificateException {
					// TODO Auto-generated method stub
				}

				@Override
				public void checkServerTrusted(java.security.cert.X509Certificate[] arg0, String arg1)
						throws CertificateException {
					// TODO Auto-generated method stub
				}

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


参考:

https://www.cnblogs.com/YDDMAX/p/5380131.html

https://blog.csdn.net/fenglibing/article/details/16842543

https://blog.csdn.net/joker_honey/article/details/71487220

https://blog.csdn.net/u012874222/article/details/50339259

提供证书转换的网站

https://myssl.com/cert_convert.html

证书转换方法:

https://blog.csdn.net/ONLYMETAGAIN/article/details/78782056

一些问题解决策略

http://www.trinea.cn/android/android-java-https-ssl-exception-2/

一些加载证书方式

https://blog.csdn.net/fenglibing/article/details/16842543


猜你喜欢

转载自blog.csdn.net/blogercn/article/details/80465214