HTTPS单向认证和双向认证

Android客户端开发中,经常用到http或https做网络请求。
二者的区别如下:
1、http是超文本传输协议,信息是明文传输,对于网络访问不安全; 而https则是具有安全性的ssl加密传输协议。
2、http和https使用的端口也不一样,前者是80,后者是443。
3、二者用的是完全不同的连接方式:http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
4、https协议需要到ca申请证书,一般免费证书很少,需要交费。

SSL功能:
1)客户端对服务器的身份认证:
SSL服务器允许客户端的浏览器使用标准的公钥加密技术和一些可靠的认证中心(CA)的证书,来确认服务器的合法性。

2)服务器对客户端的身份认证:
可通过公钥技术和证书进行认证,也可通过用户名、password来认证。

3)建立服务器与客户端之间安全的数据通道:
SSL要求客户端与服务器之间的所有发送的数据都被发送端加密、接收端解密,同时还检查数据的完整性。

SSL简介:
SSL协议位于TCP/IP协议与各种应用层协议(比如http协议)之间,为数据通讯提供安全支持。
SSL协议可分为两层:
SSL记录协议(SSL Record Protocol):它建立在可靠的传输协议(如TCP)之上,为高层协议提供数据封装、压缩、加密等基本功能的支持。
SSL握手协议(SSL Handshake Protocol):它建立在SSL记录协议之上,用于在实际的数据传输开始前,通讯双方进行身份认证、协商加密算法、交换加密密钥等。

对于使用HttpClient作为请求方式的客户端,这里用AsyncHttpClient举例实现https单向和双向认证。
通过SSLSocketFactory方式创建。

一、单向认证

/************************************* https单向认证 ******************************************/
    public static class SSLCustomSocketFactory extends SSLSocketFactory {
        private static final String TAG = "SSLCustomSocketFactory";

        public SSLCustomSocketFactory(KeyStore trustStore) throws Throwable {
            super(trustStore);
        }

        public static SSLSocketFactory getSocketFactory(){
            try {
                //客户端证书
                InputStream ins = App.getContext().getAssets().open("client.cer");
                CertificateFactory cerFactory = CertificateFactory.getInstance("X.509");
                Certificate cer = cerFactory.generateCertificate(ins);
                //创建一个证书库,并将证书导入证书库
                KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
                trustStore.load(null, null);
                trustStore.setCertificateEntry("trust", cer);
                SSLSocketFactory factory = new SSLSocketFactory(trustStore);
                return factory;
            }catch (Exception e){
                logger.w("%s", e.getMessage());
                e.printStackTrace();
            }

            return null;
        }
    }
/************************************* https单向认证 end ***************************************/

二、双向认证。这里介绍两种实现。

/************************************* https 双向认证1 *****************************************/
    public static SSLSocketFactory createSSLSocketFactory() {
        final String KEY_PASS = "123456";
        MySSLSocketFactory sf = null;
        try {
            // 服务器的证书, 供客户端校验
            InputStream ins = App.getContext().getAssets().open("client.bks");
            KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
            try {
                trustStore.load(ins, KEY_PASS.toCharArray());
            } finally {
                ins.close();
            }
            sf = new MySSLSocketFactory(trustStore);
            sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
        } catch (Exception e) {
            e.printStackTrace();
            logger.e("%s", e, e.getClass().toString());
        }
        return sf;
    }

    public static class MySSLSocketFactory extends SSLSocketFactory {
        private static final String CLIENT_KEY_MANAGER = "X509"; // 密钥管理器
        private static final String CLIENT_TRUST_MANAGER = "X509"; // 信任证书管理器
        private static final String CLIENT_KEY_KEYSTORE = "BKS";
        private static final String CLIENT_KET_PASSWORD = "123456";

        SSLContext sslContext = SSLContext.getInstance("TLS");

        public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException,
                KeyStoreException, UnrecoverableKeyException {
            super(truststore);
            // 取得keyManageFactory和TrustManagerFactory的x509密钥管理实例
            KeyManagerFactory keyManager = KeyManagerFactory.getInstance(CLIENT_KEY_MANAGER);
            TrustManagerFactory trustManager = TrustManagerFactory.getInstance(CLIENT_TRUST_MANAGER);

            KeyStore kks = KeyStore.getInstance(CLIENT_KEY_KEYSTORE);
            InputStream ins;
            try {
                // 客户端证书, 供服务器端校验
                ins = App.getContext().getAssets().open("verify_by_server.bks");
                kks.load(ins, CLIENT_KET_PASSWORD.toCharArray());
                ins.close();
            } catch (IOException e) {
                e.printStackTrace();
                logger.e("%s", e, e.getClass().toString());
            } catch (CertificateException e) {
                e.printStackTrace();
                logger.e("%s", e, e.getClass().toString());
            }

            keyManager.init(kks, CLIENT_KET_PASSWORD.toCharArray());
            trustManager.init(truststore);

            sslContext.init(keyManager.getKeyManagers(), trustManager.getTrustManagers(), null);
        }

        @Override
        public Socket createSocket(Socket socket, String host, int port,
                                   boolean autoClose) throws IOException, UnknownHostException {
            injectHostname(socket, host);
            return sslContext.getSocketFactory().createSocket(socket, host,
                    port, autoClose);
        }

        @Override
        public Socket createSocket() throws IOException {
            return sslContext.getSocketFactory().createSocket();
        }

        private void injectHostname(Socket socket, String host) {
            try {
                Field field = InetAddress.class.getDeclaredField("hostName");
                field.setAccessible(true);
                field.set(socket.getInetAddress(), host);
            } catch (Exception ignored) {

            }
        }
    }
    /******************************** https 双向认证1 end ****************************************/


另一种实现:

/******************************** https 双向认证2 start **************************************/
    private static final String KEY_STORE_TYPE_BKS = "bks";//证书类型 固定值
    private static final String KEY_STORE_TYPE_P12 = "PKCS12";//证书类型 固定值

    private static final String KEY_STORE_CLIENT_PATH = "client.p12";//客户端要给服务器端认证的证书
    private static final String KEY_STORE_TRUST_PATH = "client.bks";//客户端验证服务器端的证书库
    private static final String KEY_STORE_PASSWORD = "123456";// 客户端证书密码
    private static final String KEY_STORE_TRUST_PASSWORD = "123456";//客户端证书库密码

    /**
     * 获取SslSocketFactory
     *
     * @param context 上下文
     * @return SSLSocketFactory
     */
    public static SSLSocketFactory getSslSocketFactory(Context context) {
        try {
            // 服务器端需要验证的客户端证书
            KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE_P12);
            // 客户端信任的服务器端证书
            KeyStore trustStore = KeyStore.getInstance(KEY_STORE_TYPE_BKS);

            InputStream ksIn = context.getResources().getAssets().open(KEY_STORE_CLIENT_PATH);
            InputStream tsIn = context.getResources().getAssets().open(KEY_STORE_TRUST_PATH);
            try {
                keyStore.load(ksIn, KEY_STORE_PASSWORD.toCharArray());
                trustStore.load(tsIn, KEY_STORE_TRUST_PASSWORD.toCharArray());
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    ksIn.close();
                } catch (Exception ignore) {
                }
                try {
                    tsIn.close();
                } catch (Exception ignore) {
                }
            }
            return new SSLSocketFactory(keyStore, KEY_STORE_PASSWORD, trustStore);
        } catch (KeyManagementException | UnrecoverableKeyException | KeyStoreException | FileNotFoundException | NoSuchAlgorithmException | ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
    
    /******************************** https 双向认证2 end ***************************************/

使用:

if(httpsClient == null){
	httpsClient = new AsyncHttpClient();
	SSLSocketFactory sf = SSLCustomSocketFactory.getSocketFactory(); //单向认证
	SSLSocketFactory sf  = createSSLSocketFactory(); //双向认证1
	SSLSocketFactory sf = getSslSocketFactory(App.getContext());//双向认证2
	 
    if(sf != null){
		httpsClient.setSSLSocketFactory(sf);
		
    }
	HttpProtocolParams.setUseExpectContinue(httpsClient.getHttpClient().getParams(), false);
}

如果提供的证书是.truststore和.p12格式的,Android客户端不能直接使用.struststore,会报错:Wrong version of key store,需要转成.bks格式的。


证书转换

client_xxx.p12, 客户端证书,用于请求的时候给服务器来验证客户端身份之用.

client_xxx.truststore, 客户端证书库,用于验证服务器端身份.

: client_xxx.truststore需要转换为client_xxx.bks格式后才能使用.

转换示例

使用Portecle工具进行转换, portecle-1.11.zip.

1.  启动工具cmd, 键入:

java -jar portecle.jar

2.  打开要转换的文件

HTTPS单向认证和双向认证_第1张图片

3.  选择Tool->Change Keystore Type->Bks.

4.  步骤3提示成功后,点击保存,即转换为.bks格式文件, 保存为client.bks。

5. 同理,将p12文件转换为bks文件, 保存为verify_by_server.bks。


另,使用bcprov-jdk15on-159.jar 将.cer转成.bks:

C:\Users\Administrator>keytool -importcert -v -trustcacerts -alias server -file 需要转换的.cer -keystore  转换后要存放的.bks地址 -storetype BKS -providerclass org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath bcprov-jdk15on-159.jar的本地地址 -storepass 123456

你可能感兴趣的:(Android)