java-信息安全(十四)-https003-综合https、以及tomcat导入证书

一、概述

参看:010-HTTP协议005-TCP传输控制协议

java-信息安全(七)-https001-基于非对称加密,对称加密等理解

java-信息安全(九)-https002-基于DH,非对称加密,对称加密等理解

1.1、现有Http缺点

  通信使用明文(不加密),内容可能会被窃听

  不验证通信方的身份,因此有可能遭遇伪装

  无法证明报文的完整性,所以有可能已遭篡改

1.2、https诞生

  为了解决 HTTP 协议的以上缺点,在上世纪90年代中期,由网景(NetScape)公司设计了 SSL 协议。SSL 是“Secure Sockets Layer”的缩写,中文叫做“安全套接层”。

  到了1999年,SSL 因为应用广泛,已经成为互联网上的事实标准。IETF 就在那年把 SSL 标准化。标准化之后的名称改为 TLS(是“Transport Layer Security”的缩写),中文叫做“传输层安全协议”。

  互联网加密协议历史:

    1994年,NetScape 公司设计了 SSL 协议的1.0版,但是未发布。

    1995年,NetScape 公司发布 SSL 2.0版,很快发现有严重漏洞。

    1996年,SSL 3.0 版问世,得到大规模应用。

    1999年,互联网标准化组织 ISOC 接替 NetScape 公司,发布了 SSL 的升级版 TLS 1.0 版。

    2006年和2008年,TLS 进行了两次升级,分别为 TLS 1.1 版和 TLS 1.2 版。最新的变动是2011年 TLS 1.2 的修订版。

  目前,应用最广泛的是TLS 1.0,接下来是SSL 3.0。但是,主流浏览器都已经实现了TLS 1.2的支持。

  TLS 1.0通常被标示为SSL 3.1,TLS 1.1为SSL 3.2,TLS 1.2为SSL 3.3。

  所谓的 HTTPS 其实是“HTTP over SSL”或“HTTP over TLS”,它是 HTTP 与 SSL/TSL 的结合使用而已。

          

 1.3、数字证书

参看:java-信息安全(十二)-数字证书【Java证书体系实现】

HTTPS 中 CA 证书的获取

  

  注:上图步骤 2 之后,客户端获取到“CA 证书”会进行本地验证,即使用本地系统或者浏览器中的公钥进行解密,每个“CA 证书”都会有一个证书编号可用于解密后进行比对(具体验证算法请查阅相关资料)。

  步骤 5 之前使用的是对称加密,之后将使用对称加密来提高通讯效率。

1.4、spdy【过度品】

  2012年google提出了SPDY的方案,大家才开始从正面看待和解决老版本HTTP协议本身的问题,SPDY可以说是综合了HTTPS和HTTP两者有点于一体的传输协议,缩短 Web 页面的加载时间(50%)。

  SPDY- The Chromium Projects

    

 

  SPDY位于HTTP之下,TCP和SSL之上,这样可以轻松兼容老版本的HTTP协议(将HTTP1.x的内容封装成一种新的frame格式),同时可以使用已有的SSL功能。 

  具体详见:http://baike.baidu.com/item/SPDY 

1.5、HTTP2.0

  顾名思义有了HTTP1.x,那么HTTP2.0也就顺理成章的出现了。HTTP2.0可以说是SPDY的升级版(其实原本也是基于SPDY设计的),但是,HTTP2.0 跟 SPDY 仍有不同的地方,主要是以下两点:

  HTTP2.0 支持明文 HTTP 传输,而 SPDY 强制使用 HTTPS。

  HTTP2.0 消息头的压缩算法采用 HPACK,而非 SPDY 采用的 DEFLATE。

  具体详见:http://baike.baidu.com/item/HTTP 2.0

二、 tomcat配置ssl

我们需要构建一个由CA机构签发的有效证书,这里我们使用上文中生成的自签名证书lhx.cer 

    这里,我们将证书导入到我们的密钥库。

keytool -import -alias www.lhx.org -file d:/lhx.cer -keystore d:/lhx.keystore  

其中 
-import表示导入 
-alias指定别名,这里是www.lhx.org 
-file指定算法,这里是d:/lhx.cer 
-keystore指定存储位置,这里是d:/lhx.keystore 
在这里我使用的密码为654321 

控制台输出: 

输入keystore密码:  
再次输入新密码:  
所有者:CN=www.zlex.org, OU=zlex, O=zlex, L=BJ, ST=BJ, C=CN  
签发人:CN=www.zlex.org, OU=zlex, O=zlex, L=BJ, ST=BJ, C=CN  
序列号:4a1e48df  
有效期: Thu May 28 16:18:39 CST 2009 至Wed Aug 26 16:18:39 CST 2009  
证书指纹:  
         MD5:19:CA:E6:36:E2:DF:AD:96:31:97:2F:A9:AD:FC:37:6A  
         SHA1:49:88:30:59:29:45:F1:69:CA:97:A9:6D:8A:CF:08:D2:C3:D5:C0:C4  
         签名算法名称:SHA1withRSA  
         版本: 3  
信任这个认证? [否]:  y  
认证已添加至keystore中  

OK,最复杂的准备工作已经完成。 
接下来我们将域名www.lhx.org定位到本机上。打开C:\Windows\System32\drivers\etc\hosts文件,将www.lhx.org绑定在本机上。在文件末尾追加127.0.0.1       www.zlex.org。现在通过地址栏访问http://www.lhx.org,或者通过ping命令,如果能够定位到本机,域名映射就搞定了。 
现在,配置tomcat。先将lhx.keystore拷贝到tomcat的conf目录下,然后配置server.xml。将如下内容加入配置文件:

<Connector  
    SSLEnabled="true"  
    URIEncoding="UTF-8"  
    clientAuth="false"  
    keystoreFile="conf/lhx.keystore"  
    keystorePass="123456"  
    maxThreads="150"  
    port="443"  
    protocol="HTTP/1.1"  
    scheme="https"  
    secure="true"  
    sslProtocol="TLS" />  

注意clientAuth="false"测试阶段,置为false,正式使用时建议使用true。现在启动tomcat,访问https://www.lhx.org/

显然,证书未能通过认证,这个时候你可以选择安装证书(上文中的zlex.cer文件就是证书),作为受信任的根证书颁发机构导入,再次重启浏览器(IE,其他浏览器对于域名www.lhx.org不支持本地方式访问),访问https://www.lhx.org/,你会看到地址栏中会有个小锁,就说明安装成功。所有的浏览器联网操作已经在RSA加密解密系统的保护之下了。但似乎我们感受不到。 

这个时候很多人开始怀疑,如果我们要手工做一个这样的https的访问是不是需要把浏览器的这些个功能都实现呢?不需要! 

接着上篇内容,给出如下代码实现: 

import java.io.FileInputStream;  
import java.security.KeyStore;  
import java.security.PrivateKey;  
import java.security.PublicKey;  
import java.security.Signature;  
import java.security.cert.Certificate;  
import java.security.cert.CertificateFactory;  
import java.security.cert.X509Certificate;  
import java.util.Date;  
  
import javax.crypto.Cipher;  
import javax.net.ssl.HttpsURLConnection;  
import javax.net.ssl.KeyManagerFactory;  
import javax.net.ssl.SSLContext;  
import javax.net.ssl.SSLSocketFactory;  
import javax.net.ssl.TrustManagerFactory;  
  
/** 
 * 证书组件 
 *  
 * @author 梁栋 
 * @version 1.0 
 * @since 1.0 
 */  
public abstract class CertificateCoder extends Coder {  
  
    /** 
     * Java密钥库(Java Key Store,JKS)KEY_STORE 
     */  
    public static final String KEY_STORE = "JKS";  
  
    public static final String X509 = "X.509";  
    public static final String SunX509 = "SunX509";  
    public static final String SSL = "SSL";  
  
    /** 
     * 由KeyStore获得私钥 
     *  
     * @param keyStorePath 
     * @param alias 
     * @param password 
     * @return 
     * @throws Exception 
     */  
    private static PrivateKey getPrivateKey(String keyStorePath, String alias,  
            String password) throws Exception {  
        KeyStore ks = getKeyStore(keyStorePath, password);  
        PrivateKey key = (PrivateKey) ks.getKey(alias, password.toCharArray());  
        return key;  
    }  
  
    /** 
     * 由Certificate获得公钥 
     *  
     * @param certificatePath 
     * @return 
     * @throws Exception 
     */  
    private static PublicKey getPublicKey(String certificatePath)  
            throws Exception {  
        Certificate certificate = getCertificate(certificatePath);  
        PublicKey key = certificate.getPublicKey();  
        return key;  
    }  
  
    /** 
     * 获得Certificate 
     *  
     * @param certificatePath 
     * @return 
     * @throws Exception 
     */  
    private static Certificate getCertificate(String certificatePath)  
            throws Exception {  
        CertificateFactory certificateFactory = CertificateFactory  
                .getInstance(X509);  
        FileInputStream in = new FileInputStream(certificatePath);  
  
        Certificate certificate = certificateFactory.generateCertificate(in);  
        in.close();  
  
        return certificate;  
    }  
  
    /** 
     * 获得Certificate 
     *  
     * @param keyStorePath 
     * @param alias 
     * @param password 
     * @return 
     * @throws Exception 
     */  
    private static Certificate getCertificate(String keyStorePath,  
            String alias, String password) throws Exception {  
        KeyStore ks = getKeyStore(keyStorePath, password);  
        Certificate certificate = ks.getCertificate(alias);  
  
        return certificate;  
    }  
  
    /** 
     * 获得KeyStore 
     *  
     * @param keyStorePath 
     * @param password 
     * @return 
     * @throws Exception 
     */  
    private static KeyStore getKeyStore(String keyStorePath, String password)  
            throws Exception {  
        FileInputStream is = new FileInputStream(keyStorePath);  
        KeyStore ks = KeyStore.getInstance(KEY_STORE);  
        ks.load(is, password.toCharArray());  
        is.close();  
        return ks;  
    }  
  
    /** 
     * 私钥加密 
     *  
     * @param data 
     * @param keyStorePath 
     * @param alias 
     * @param password 
     * @return 
     * @throws Exception 
     */  
    public static byte[] encryptByPrivateKey(byte[] data, String keyStorePath,  
            String alias, String password) throws Exception {  
        // 取得私钥  
        PrivateKey privateKey = getPrivateKey(keyStorePath, alias, password);  
  
        // 对数据加密  
        Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());  
        cipher.init(Cipher.ENCRYPT_MODE, privateKey);  
  
        return cipher.doFinal(data);  
  
    }  
  
    /** 
     * 私钥解密 
     *  
     * @param data 
     * @param keyStorePath 
     * @param alias 
     * @param password 
     * @return 
     * @throws Exception 
     */  
    public static byte[] decryptByPrivateKey(byte[] data, String keyStorePath,  
            String alias, String password) throws Exception {  
        // 取得私钥  
        PrivateKey privateKey = getPrivateKey(keyStorePath, alias, password);  
  
        // 对数据加密  
        Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());  
        cipher.init(Cipher.DECRYPT_MODE, privateKey);  
  
        return cipher.doFinal(data);  
  
    }  
  
    /** 
     * 公钥加密 
     *  
     * @param data 
     * @param certificatePath 
     * @return 
     * @throws Exception 
     */  
    public static byte[] encryptByPublicKey(byte[] data, String certificatePath)  
            throws Exception {  
  
        // 取得公钥  
        PublicKey publicKey = getPublicKey(certificatePath);  
        // 对数据加密  
        Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());  
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);  
  
        return cipher.doFinal(data);  
  
    }  
  
    /** 
     * 公钥解密 
     *  
     * @param data 
     * @param certificatePath 
     * @return 
     * @throws Exception 
     */  
    public static byte[] decryptByPublicKey(byte[] data, String certificatePath)  
            throws Exception {  
        // 取得公钥  
        PublicKey publicKey = getPublicKey(certificatePath);  
  
        // 对数据加密  
        Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());  
        cipher.init(Cipher.DECRYPT_MODE, publicKey);  
  
        return cipher.doFinal(data);  
  
    }  
  
    /** 
     * 验证Certificate 
     *  
     * @param certificatePath 
     * @return 
     */  
    public static boolean verifyCertificate(String certificatePath) {  
        return verifyCertificate(new Date(), certificatePath);  
    }  
  
    /** 
     * 验证Certificate是否过期或无效 
     *  
     * @param date 
     * @param certificatePath 
     * @return 
     */  
    public static boolean verifyCertificate(Date date, String certificatePath) {  
        boolean status = true;  
        try {  
            // 取得证书  
            Certificate certificate = getCertificate(certificatePath);  
            // 验证证书是否过期或无效  
            status = verifyCertificate(date, certificate);  
        } catch (Exception e) {  
            status = false;  
        }  
        return status;  
    }  
  
    /** 
     * 验证证书是否过期或无效 
     *  
     * @param date 
     * @param certificate 
     * @return 
     */  
    private static boolean verifyCertificate(Date date, Certificate certificate) {  
        boolean status = true;  
        try {  
            X509Certificate x509Certificate = (X509Certificate) certificate;  
            x509Certificate.checkValidity(date);  
        } catch (Exception e) {  
            status = false;  
        }  
        return status;  
    }  
  
    /** 
     * 签名 
     *  
     * @param keyStorePath 
     * @param alias 
     * @param password 
     *  
     * @return 
     * @throws Exception 
     */  
    public static String sign(byte[] sign, String keyStorePath, String alias,  
            String password) throws Exception {  
        // 获得证书  
        X509Certificate x509Certificate = (X509Certificate) getCertificate(  
                keyStorePath, alias, password);  
        // 获取私钥  
        KeyStore ks = getKeyStore(keyStorePath, password);  
        // 取得私钥  
        PrivateKey privateKey = (PrivateKey) ks.getKey(alias, password  
                .toCharArray());  
  
        // 构建签名  
        Signature signature = Signature.getInstance(x509Certificate  
                .getSigAlgName());  
        signature.initSign(privateKey);  
        signature.update(sign);  
        return encryptBASE64(signature.sign());  
    }  
  
    /** 
     * 验证签名 
     *  
     * @param data 
     * @param sign 
     * @param certificatePath 
     * @return 
     * @throws Exception 
     */  
    public static boolean verify(byte[] data, String sign,  
            String certificatePath) throws Exception {  
        // 获得证书  
        X509Certificate x509Certificate = (X509Certificate) getCertificate(certificatePath);  
        // 获得公钥  
        PublicKey publicKey = x509Certificate.getPublicKey();  
        // 构建签名  
        Signature signature = Signature.getInstance(x509Certificate  
                .getSigAlgName());  
        signature.initVerify(publicKey);  
        signature.update(data);  
  
        return signature.verify(decryptBASE64(sign));  
  
    }  
  
    /** 
     * 验证Certificate 
     *  
     * @param keyStorePath 
     * @param alias 
     * @param password 
     * @return 
     */  
    public static boolean verifyCertificate(Date date, String keyStorePath,  
            String alias, String password) {  
        boolean status = true;  
        try {  
            Certificate certificate = getCertificate(keyStorePath, alias,  
                    password);  
            status = verifyCertificate(date, certificate);  
        } catch (Exception e) {  
            status = false;  
        }  
        return status;  
    }  
  
    /** 
     * 验证Certificate 
     *  
     * @param keyStorePath 
     * @param alias 
     * @param password 
     * @return 
     */  
    public static boolean verifyCertificate(String keyStorePath, String alias,  
            String password) {  
        return verifyCertificate(new Date(), keyStorePath, alias, password);  
    }  
  
    /** 
     * 获得SSLSocektFactory 
     *  
     * @param password 
     *            密码 
     * @param keyStorePath 
     *            密钥库路径 
     *  
     * @param trustKeyStorePath 
     *            信任库路径 
     * @return 
     * @throws Exception 
     */  
    private static SSLSocketFactory getSSLSocketFactory(String password,  
            String keyStorePath, String trustKeyStorePath) throws Exception {  
        // 初始化密钥库  
        KeyManagerFactory keyManagerFactory = KeyManagerFactory  
                .getInstance(SunX509);  
        KeyStore keyStore = getKeyStore(keyStorePath, password);  
        keyManagerFactory.init(keyStore, password.toCharArray());  
  
        // 初始化信任库  
        TrustManagerFactory trustManagerFactory = TrustManagerFactory  
                .getInstance(SunX509);  
        KeyStore trustkeyStore = getKeyStore(trustKeyStorePath, password);  
        trustManagerFactory.init(trustkeyStore);  
  
        // 初始化SSL上下文  
        SSLContext ctx = SSLContext.getInstance(SSL);  
        ctx.init(keyManagerFactory.getKeyManagers(), trustManagerFactory  
                .getTrustManagers(), null);  
        SSLSocketFactory sf = ctx.getSocketFactory();  
  
        return sf;  
    }  
  
    /** 
     * 为HttpsURLConnection配置SSLSocketFactory 
     *  
     * @param conn 
     *            HttpsURLConnection 
     * @param password 
     *            密码 
     * @param keyStorePath 
     *            密钥库路径 
     *  
     * @param trustKeyStorePath 
     *            信任库路径 
     * @throws Exception 
     */  
    public static void configSSLSocketFactory(HttpsURLConnection conn,  
            String password, String keyStorePath, String trustKeyStorePath)  
            throws Exception {  
        conn.setSSLSocketFactory(getSSLSocketFactory(password, keyStorePath,  
                trustKeyStorePath));  
    }  
}  
View Code

增加了configSSLSocketFactory方法供外界调用,该方法为HttpsURLConnection配置了SSLSocketFactory。当HttpsURLConnection配置了SSLSocketFactory后,我们就可以通过HttpsURLConnection的getInputStream、getOutputStream,像往常使用HttpURLConnection做操作了。尤其要说明一点,未配置SSLSocketFactory前,HttpsURLConnection的getContentLength()获得值永远都是-1。 

给出相应测试类: 

import static org.junit.Assert.*;  
  
import java.io.DataInputStream;  
import java.io.InputStream;  
import java.net.URL;  
  
import javax.net.ssl.HttpsURLConnection;  
  
import org.junit.Test;  
  
/** 
 *  
 * @author 梁栋 
 * @version 1.0 
 * @since 1.0 
 */  
public class CertificateCoderTest {  
    private String password = "123456";  
    private String alias = "www.zlex.org";  
    private String certificatePath = "d:/zlex.cer";  
    private String keyStorePath = "d:/zlex.keystore";  
    private String clientKeyStorePath = "d:/zlex-client.keystore";  
    private String clientPassword = "654321";  
  
    @Test  
    public void test() throws Exception {  
        System.err.println("公钥加密——私钥解密");  
        String inputStr = "Ceritifcate";  
        byte[] data = inputStr.getBytes();  
  
        byte[] encrypt = CertificateCoder.encryptByPublicKey(data,  
                certificatePath);  
  
        byte[] decrypt = CertificateCoder.decryptByPrivateKey(encrypt,  
                keyStorePath, alias, password);  
        String outputStr = new String(decrypt);  
  
        System.err.println("加密前: " + inputStr + "\n\r" + "解密后: " + outputStr);  
  
        // 验证数据一致  
        assertArrayEquals(data, decrypt);  
  
        // 验证证书有效  
        assertTrue(CertificateCoder.verifyCertificate(certificatePath));  
  
    }  
  
    @Test  
    public void testSign() throws Exception {  
        System.err.println("私钥加密——公钥解密");  
  
        String inputStr = "sign";  
        byte[] data = inputStr.getBytes();  
  
        byte[] encodedData = CertificateCoder.encryptByPrivateKey(data,  
                keyStorePath, alias, password);  
  
        byte[] decodedData = CertificateCoder.decryptByPublicKey(encodedData,  
                certificatePath);  
  
        String outputStr = new String(decodedData);  
        System.err.println("加密前: " + inputStr + "\n\r" + "解密后: " + outputStr);  
        assertEquals(inputStr, outputStr);  
  
        System.err.println("私钥签名——公钥验证签名");  
        // 产生签名  
        String sign = CertificateCoder.sign(encodedData, keyStorePath, alias,  
                password);  
        System.err.println("签名:\r" + sign);  
  
        // 验证签名  
        boolean status = CertificateCoder.verify(encodedData, sign,  
                certificatePath);  
        System.err.println("状态:\r" + status);  
        assertTrue(status);  
  
    }  
  
    @Test  
    public void testHttps() throws Exception {  
        URL url = new URL("https://www.zlex.org/examples/");  
        HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();  
  
        conn.setDoInput(true);  
        conn.setDoOutput(true);  
  
        CertificateCoder.configSSLSocketFactory(conn, clientPassword,  
                clientKeyStorePath, clientKeyStorePath);  
  
        InputStream is = conn.getInputStream();  
  
        int length = conn.getContentLength();  
  
        DataInputStream dis = new DataInputStream(is);  
        byte[] data = new byte[length];  
        dis.readFully(data);  
  
        dis.close();  
        System.err.println(new String(data));  
        conn.disconnect();  
    }  
}  
View Code

注意testHttps方法,几乎和我们往常做HTTP访问没有差别,我们来看控制台输出:

<!--  
  Licensed to the Apache Software Foundation (ASF) under one or more  
  contributor license agreements.  See the NOTICE file distributed with  
  this work for additional information regarding copyright ownership.  
  The ASF licenses this file to You under the Apache License, Version 2.0  
  (the "License"); you may not use this file except in compliance with  
  the License.  You may obtain a copy of the License at  
  
      http://www.apache.org/licenses/LICENSE-2.0  
  
  Unless required by applicable law or agreed to in writing, software  
  distributed under the License is distributed on an "AS IS" BASIS,  
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
  See the License for the specific language governing permissions and  
  limitations under the License.  
-->  
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">  
<HTML><HEAD><TITLE>Apache Tomcat Examples</TITLE>  
<META http-equiv=Content-Type content="text/html">  
</HEAD>  
<BODY>  
<P>  
<H3>Apache Tomcat Examples</H3>  
<P></P>  
<ul>  
<li><a href="servlets">Servlets examples</a></li>  
<li><a href="jsp">JSP Examples</a></li>  
</ul>  
</BODY></HTML>  
View Code

通过浏览器直接访问https://www.lhx.org/examples/你也会获得上述内容。也就是说应用甲方作为服务器构建tomcat服务,乙方可以通过上述方式访问甲方受保护的SSL应用,并且不需要考虑具体的加密解密问题。甲乙双方可以经过相应配置,通过双方的tomcat配置有效的SSL服务,简化上述代码实现,完全通过证书配置完成SSL双向认证!

 

posted @ 2017-03-17 13:49  bjlhx15  阅读(593)  评论(0编辑  收藏  举报
Copyright ©2011~2020 JD-李宏旭