mitmproxy支持https抓包

 开发  mitmproxy支持https抓包已关闭评论
11月 292019
 

mitmproxy支持https抓包,需要下载下对应文件

  1. 先运行mitmproxy或mitmdump或mitmweb启动代理服务器后(默认8080端口), 在手机里设置好代理服务器。
  2. 打开浏览器,并浏览地址: http://mitm.it, 可以看到下面的画面:选择对应系统的证书文件下载。
  3. 如果是ios用户,记得在描述文件下载后安装。

 

done!

HTTPS中间人攻击与证书校验

 ssl  HTTPS中间人攻击与证书校验已关闭评论
11月 022016
 

网上看到的一篇个人感觉目前最好的《关于HTTPS中间人攻击与证书校验》的文章, 让我豁然开朗, 分享下:http://www.2cto.com/article/201607/523509.html

引言

随着安全的普及,https通信应用越发广泛,但是由于对https不熟悉导致开发人员频繁错误的使用https,例如最常见的是未校验https证书从而导致“中间人攻击”,并且由于修复方案也一直是个坑,导致修复这个问题时踩各种坑,故谨以此文简单的介绍相关问题。

本文第一节主要讲述https的握手过程,第二节主要讲述常见的“https中间人攻击”场景,第三节主要介绍证书校验修复方案,各位看官可根据自己口味浏览。

0x01 Https原理

 

浅析HTTPS中间人攻击与证书校验

 

首先来看下https的工作原理,上图大致介绍了https的握手流程,后续我们通过抓包看下每个握手包到底干了些什么神奇的事。

注:本文所有内容以TLS_RSA_WITH_AES_128_CBC_SHA加密组件作为基础进行说明,其他加密组件以及TLS版本会存在一定差异,例如TLS1.3针对移动客户端有了很大的改动,现在的ECDHE等密钥交换算法与RSA作为密钥交换算法也完全不一样,所以有些地方和大家实际操作会存在一定出入。

1.1 TCP

TCP的三次握手,这是必须的。

 

浅析HTTPS中间人攻击与证书校验

 

1.2 Client Hello

 

浅析HTTPS中间人攻击与证书校验

 

TLS的版本号 随机数random_c:这个是用来生成最后加密密钥的因子之一,它包含两部分,时间戳和随机数 session-id:用来标识会话,第一次握手时为空,如果以前建立过,可以直接带过去从而避免完全握手 Cipher Suites加密组件列表:浏览器所支持的加密算法的清单客户端支持的加密签名算法的列表,让服务器进行选择 扩展字段:比如密码交换算法的参数、请求主机的名字,用于单ip多域名的情况指定域名。

 

浅析HTTPS中间人攻击与证书校验

 

1.3 Sever Hello

 

浅析HTTPS中间人攻击与证书校验

 

随机数rando_s,这个是用来生成最后加密密钥的因子之一,包含两部分,时间戳和随机数 32字节的SID,在我们想要重新连接到该站点的时候可以避免一整套握手过程。 在客户端提供的加密组件中,服务器选择了TLS_RSA_WITH_AES_128_CBC_SHA组件。1.4 Certificate

 

浅析HTTPS中间人攻击与证书校验

 

证书是https里非常重要的主体,可用来识别对方是否可信,以及用其公钥做密钥交换。可以看见证书里面包含证书的颁发者,证书的使用者,证书的公钥,颁发者的签名等信息。其中Issuer Name是签发此证书的CA名称,用来指定签发证书的CA的可识别的唯一名称(DN, Distinguished Name),用于证书链的认证,这样通过各级实体证书的验证,逐渐上溯到链的终止点,即可信任的根CA,如果到达终点在自己的信任列表内未发现可信任的CA则认为此证书不可信。

验证证书链的时候,用上一级的公钥对证书里的签名进行解密,还原对应的摘要值,再使用证书信息计算证书的摘要值,最后通过对比两个摘要值是否相等,如果不相等则认为该证书不可信,如果相等则认为该级证书链正确,以此类推对整个证书链进行校验,引用高性能网络中的证书链校验图。

 

浅析HTTPS中间人攻击与证书校验

 

 

浅析HTTPS中间人攻击与证书校验

 

二级机构的公钥

 

浅析HTTPS中间人攻击与证书校验

 

网站证书的签名

不仅仅进行证书链的校验,此时还会进行另一个协议即Online Certificate Status Protocol, 该协议为证书状态在线查询协议,一个实时查询证书是否吊销的方式,客户端发送证书的信息并请求查询,服务器返回正常、吊销或未知中的任何一个状态,这个查询地址会附在证书中供客户端使用。

1.5 Server Hello Done

 

浅析HTTPS中间人攻击与证书校验

 

这是一个零字节信息,用于告诉客户端整个server hello过程已经结束。

1.6 ClientKeyExchange

 

浅析HTTPS中间人攻击与证书校验

 

客户端在验证证书有效之后发送ClientKeyExchange消息,ClientKeyExchange消息中,会设置48字节的premaster secret,通过密钥交换算法加密发送premaster secret的值,例如通过 RSA公钥加密premaster secret的得到Encrypted PreMaster传给服务端。PreMaster前两个字节是TLS的版本号,该版本号字段是用来防止版本回退攻击的。

从握手包到目前为止,已经出现了三个随机数(客户端的random_c,服务端的random_s,premaster secret),使用这三个随机数以及一定的算法即可获得对称加密AES的加密主密钥Master-key,主密钥的生成非常的精妙,通过阅读RFC2246文档以及openssl的函数int master_secret(unsigned char *dest,int len,unsigned char *pre_master_secret,int pms_len,unsigned char *label,unsigned char *seed,int seed_len)可得到主密钥的生成过程如下:

1 2 PRF(secret, label, seed) = P_MD5(S1, label + seed) XOR P_SHA-1(S2, label + seed);

函数中的参数定义如下:

secret即为pre secret ,label是一个字符串,seed为random_c+random_s,S1为前半部分的pre secret,S2为后半部分的pre secret。分别使用P_MD5()和P_SHA-1进行hash计算得到两个hash,再使用这两个hash进行异或得到最终的主密钥master key,这两个hash由下面的运算得来:

1 2 3 P_hash(1/2secret, label,seed) = HMAC_hash(1/2secret, A(1) + seed) + HMAC_hash(1/2secret, A(2) + seed) + HMAC_hash(1/2secret, A(3) + seed) + …

P_hash()是P_MD5()和P_SHA-1()的统称。

A()的赋值相对比较复杂,变形后如下:

1 2 A(0)=HMAC(md5,1/2secret,strlen(1/2secret), actual_seed(0), strlen(label)+strlen(seed),temp_md5,NULL); //temp_md5数组用于接收最后生成的hash,attual_seed为label和seed结合 A(0) = HMAC(sha,1/2secret,strlen(1/2secret) , actual_seed(0), strlen(label)+strlen(seed),temp_sha,NULL);// temp_sha数组用于接收最后生成的hash,attual_seed为label和seed结合

简化表达如下:

1 A(i)=HMAC_hash(1/2secret,A(i-1)+seed)

由于只进行一次SHA-1或者MD5产生的hash字节数是不够的,所以使用迭代的方式产生足够多的hash,多出来的hash则直接抛弃。例如,使用P_SHA-1()产生64字节的数据,就需要迭代4次(通过A(4)),产生80字节的输出数据,最后迭代产生的16字节将会被抛弃,只留下64字节的数据,如果使用P_MD5()则需要迭代5次。

1.7 Change Cipher Spec

 

浅析HTTPS中间人攻击与证书校验

 

发送一个不加密的信息,浏览器使用该信息通知服务器后续的通信都采用协商的通信密钥和加密算法进行加密通信。

1.8 Encrypted Handshake Message

 

浅析HTTPS中间人攻击与证书校验

 

验证加密算法的有效性,结合之前所有通信参数的 hash 值与其它相关信息生成一段数据,采用协商密钥 session secret 与算法进行加密,然后发送给服务器用于数据与握手验证,通过验证说明加密算法有效。

1.9 Change_cipher_spec

 

浅析HTTPS中间人攻击与证书校验

 

Encrypted Handshake Message通过验证之后,服务器同样发送 change_cipher_spec 以通知客户端后续的通信都采用协商的密钥与算法进行加密通信。这里还有一个New Session Ticket并不是必须的,这是服务器做的优化,后续我们再讲解该协议的作用。

1.10 Encrypted Handshake Message

 

浅析HTTPS中间人攻击与证书校验

 

同样的,服务端也会发送一个Encrypted Handshake Message供客户端验证加密算法有效性。

1.11 Application Data

 

浅析HTTPS中间人攻击与证书校验

 

经过一大串的的计算之后,终于一切就绪,后续传输的数据可通过主密钥master key进行加密传输,加密数据查看图中的Encrypted Apploication Data字段数据,至此https的一次完整握手以及数据加密传输终于完成。

https里还有很多可优化并且很多精妙的设计,例如为了防止经常进行完整的https握手影响性能,于是通过sessionid来避免同一个客户端重复完成握手,但是又由于sessionid消耗的内存性能比较大,于是又出现了new session ticket,如果客户端表明它支持Session Ticket并且服务端也支持,那么在TLS握手的最后一步服务器将包含一个“New Session Ticket”信息,其中包含了一个加密通信所需要的信息,这些数据采用一个只有服务器知道的密钥进行加密。这个Session Ticket由客户端进行存储,并可以在随后的一次会话中添加到 ClientHello消息的SessionTicket扩展中。虽然所有的会话信息都只存储在客户端上,但是由于密钥只有服务器知道,所以Session Ticket仍然是安全的,因此这不仅避免了性能消耗还保证了会话的安全性。

最后我们可以使用openssl命令来直观的看下https握手的流程:

 

浅析HTTPS中间人攻击与证书校验

 

0x02 中间人攻击

https握手过程的证书校验环节就是为了识别证书的有效性唯一性等等,所以严格意义上来说https下不存在中间人攻击,存在中间人攻击的前提条件是没有严格的对证书进行校验,或者人为的信任伪造证书,下面一起看下几种常见的https“中间人攻击”场景。

2.1 证书未校验

由于客户端没有做任何的证书校验,所以此时随意一张证书都可以进行中间人攻击,可以使用burp里的这个模块进行中间人攻击。

 

浅析HTTPS中间人攻击与证书校验

 

通过浏览器查看实际的https证书,是一个自签名的伪造证书。

 

浅析HTTPS中间人攻击与证书校验

 

2.2 部分校验

做了部分校验,例如在证书校验过程中只做了证书域名是否匹配的校验,可以使用burp的如下模块生成任意域名的伪造证书进行中间人攻击。

 

浅析HTTPS中间人攻击与证书校验

 

实际生成的证书效果,如果只做了域名、证书是否过期等校验可轻松进行中间人攻击(由于chrome是做了证书校验的所以会提示证书不可信任)。

 

浅析HTTPS中间人攻击与证书校验

 

2.3 证书链校验

如果客户端对证书链做了校验,那么攻击难度就会上升一个层次,此时需要人为的信任伪造的证书或者安装伪造的CA公钥证书从而间接信任伪造的证书,可以使用burp的如下模块进行中间人攻击。

 

浅析HTTPS中间人攻击与证书校验

 

 

浅析HTTPS中间人攻击与证书校验

 

可以看见浏览器是会报警告的,因为burp的根证书PortSwigger CA并不在浏览器可信任列表内,所以由它作为根证书签发的证书都是不能通过浏览器的证书校验的,如果将PortSwigger CA导入系统设置为可信任证书,那么浏览器将不会有任何警告。

2.4 手机客户端Https数据包抓取

上述第一、二种情况不多加赘述,第三种情况就是我们经常使用的抓手机应用https数据包的方法,即导入代理工具的公钥证书到手机里,再进行https数据包的抓取。导入手机的公钥证书在android平台上称之为受信任的凭据,在ios平台上称之为描述文件,可以通过openssl的命令直接查看我们导入到手机客户端里的这个PortSwiggerCA.crt

 

浅析HTTPS中间人攻击与证书校验

 

可以看见是Issuer和Subject一样的自签名CA公钥证书,另外我们也可以通过证书类型就可以知道此为公钥证书,crt、der格式的证书不支持存储私钥或证书路径(有兴趣的同学可查找证书相关信息)。导入CA公钥证书之后,参考上文的证书校验过程不难发现通过此方式能通过证书链校验,从而形成中间人攻击,客户端使用代理工具的公钥证书加密随机数,代理工具使用私钥解密并计算得到对称加密密钥,再对数据包进行解密即可抓取明文数据包。

2.5 中间人攻击原理

一直在说中间人攻击,那么中间人攻击到底是怎么进行的呢,下面我们通过一个流行的MITM开源库mitmproxy来分析中间人攻击的原理。中间人攻击的关键在于https握手过程的ClientKeyExchange,由于pre key交换的时候是使用服务器证书里的公钥进行加密,如果用的伪造证书的公钥,那么中间人就可以解开该密文得到pre_master_secret计算出用于对称加密算法的master_key,从而获取到客户端发送的数据;然后中间人代理工具再使用其和服务端的master_key加密传输给服务端;同样的服务器返回给客户端的数据也是经过中间人解密再加密,于是完整的https中间人攻击过程就形成了,一图胜千言,来吧。

 

浅析HTTPS中间人攻击与证书校验

 

通过读Mitmproxy的源码发现mitmproxy生成伪造证书的函数如下:

 

浅析HTTPS中间人攻击与证书校验

 

通过上述函数一张完美伪造的证书就出现了,使用浏览器通过mitmproxy做代理看下实际伪造出来的证书。

 

浅析HTTPS中间人攻击与证书校验

 

可以看到实际的证书是由mimtproxy颁发的,其中的公钥就是mimtproxy自己的公钥,后续的加密数据就可以使用mimtproxy的私钥进行解密了。如果导入了mitmproxy的公钥证书到客户端,那么该伪造的证书就可以完美的通过客户端的证书校验了。这就是平时为什么导入代理的CA证书到手机客户端能抓取https的原因。

0x03 App证书校验

通过上文第一和第二部分的说明,相信大家已经对https有个大概的了解了,那么问题来了,怎样才能防止这些“中间人攻击”呢?

app证书校验已经是一个老生常谈的问题了,但是市场上还是有很多的app未做好证书校验,有些只做了部分校验,例如检查证书域名是否匹配证书是否过期,更多数的是根本就不做校验,于是就造成了中间人攻击。做证书校验需要做完全,只做一部分都会导致中间人攻击,对于安全要求并不是特别高的app可使用如下校验方式:

查看证书是否过期 服务器证书上的域名是否和服务器的实际域名相匹配 校验证书链

可参考http://drops.wooyun.org/tips/3296,此类校验方式虽然在导入CA公钥证书到客户端之后会造成中间人攻击,但是攻击门槛已相对较高,所以对于安全要求不是特别高的app可采用此方法进行防御。对于安全有较高要求一些app(例如金融)上述方法或许还未达到要求,那么此时可以使用如下更安全的校验方式,将服务端证书打包放到app里,再建立https链接时使用本地证书和网络下发证书进行一致性校验,可参考安卓官方提供的https连接demo:https://developer.android.com/training/articles/security-ssl.html

#!java
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.URL;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;

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

import android.content.Context;
import android.util.Log;

public class TestHttpsConnect {
    public static void test(Context mcontext, String name, String weburl) throws Exception {

        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        InputStream caInput = new BufferedInputStream(new FileInputStream("baidu.cer"));
        Certificate ca;
        try {
            ca = cf.generateCertificate(caInput);
            System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
        } finally {
            caInput.close();
        }

        String keyStoreType = KeyStore.getDefaultType();
        KeyStore keyStore = KeyStore.getInstance(keyStoreType);
        keyStore.load(null, null);
        keyStore.setCertificateEntry("ca", ca);

        String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
        tmf.init(keyStore);

        SSLContext context = SSLContext.getInstance("TLS");
        context.init(null, tmf.getTrustManagers(), null);

        URL url = new URL(weburl);
        HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
        urlConnection.setSSLSocketFactory(context.getSocketFactory());
        InputStream in = urlConnection.getInputStream();
        ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();

        byte[] buffer = new byte[1024];
        int len = -1;
        while ((len = in.read(buffer)) >= 0) {
            arrayOutputStream.write(buffer, 0, len);
        }

        Log.e("", arrayOutputStream.toString());

    }

}

此类校验即便导入CA公钥证书也无法进行中间人攻击,但是相应的维护成本会相对升高,例如服务器证书过期,证书更换时如果app不升级就无法使用,那么可以改一下:

生成一对RSA的公私钥,公钥可硬编码在app,私钥放服务器。 https握手前可通过服务器下发证书信息,例如公钥、办法机构、签名等,该下发的信息使用服务器里的私钥进行签名; 通过app里预置的公钥验签得到证书信息并存在内容中供后续使用; 发起https连接获取服务器的证书,通过对比两个证书信息是否一致进行证书校验。

这样即可避免强升的问题,但是问题又来了,这样效率是不是低太多了?答案是肯定的,所以对于安全要求一般的应用使用第一种方法即可,对于一些安全要求较高的例如金融企业可选择第二种方法。

说了挺多,但是该来的问题还是会来啊!现在的app一般采用混合开发,会使用很多webveiw直接加载html5页面,上面的方法只解决了java层证书校验的问题,并没有涉及到webview里面的证书校验,对于这种情况怎么办呢?既然问题来了那么就一起说说解决方案,对于webview加载html5进行证书校验的方法如下:

webview创建实例加载网页时通过onPageStart方法返回url地址; 将返回的地址转发到java层使用上述的证书校验代码进行进行校验; 如果证书校验出错则使用stoploading()方法停止网页加载,证书校验通过则正常加载。

提供参考代码如下:
 

#!java
package com.example.testhttps;

import java.io.InputStream;
import java.net.URL;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;

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

import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.http.SslError;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.webkit.SslErrorHandler;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;

public class TestWebViewActivity extends Activity {

    static final String TAB = "MainActivity";
    WebView mWebView;

    SSLContext mSslContext;
    KeyStore keyStore;
    TrustManagerFactory tmf;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);

        setContentView(R.layout.test_webview_acitity);

        iniData();

        mWebView = (WebView) findViewById(R.id.webView1);
        mWebView.setWebViewClient(new MyWebViewClient());
        mWebView.getSettings().setJavaScriptEnabled(true);

        Intent i = getIntent();
        String url = i.getStringExtra("url");
        mWebView.loadUrl(url);
    }

    /**
     * 初始化证书
     */
    void iniData() {
        try {

            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            InputStream caInput = getAssets().open("baidu.cer");
            Certificate ca;
            try {
                ca = cf.generateCertificate(caInput);
                System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
            } finally {
                caInput.close();
            }

            String keyStoreType = KeyStore.getDefaultType();
            keyStore = KeyStore.getInstance(keyStoreType);
            keyStore.load(null, null);
            keyStore.setCertificateEntry("ca", ca);

            String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
            tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
            tmf.init(keyStore);

            mSslContext = SSLContext.getInstance("TLS");
            mSslContext.init(null, tmf.getTrustManagers(), null);
        } catch (Exception e) {
            Log.e("", "iniData error");
            e.printStackTrace();
        }
    }

    class MyWebViewClient extends WebViewClient {

        @Override
        public void onPageStarted(WebView view, String url, Bitmap favicon) {
            // TODO Auto-generated method stub
            super.onPageStarted(view, url, favicon);

            // 如果不是https,不用校验
            if (!url.startsWith("https://")) {
                return;
            }

            final WebView tempView = view;
            final String tempurl = url;

            /**
             * 测试url校验,如果不通过,就不加载
             */
            new AsyncTask() {

                @Override
                protected Boolean doInBackground(String... params) {
                    // TODO Auto-generated method stub
                    try {
                        // 检验证书是否正确
                        URL url = new URL(tempurl);
                        HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
                        urlConnection.setSSLSocketFactory(mSslContext.getSocketFactory());
                        InputStream in = urlConnection.getInputStream();
                        in.close();
                        // TestHttpsConnect.test(TestWebViewActivity.this,
                        // "baidu.cer", tempurl);
                    } catch (Exception e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                        return false;
                    }
                    return true;
                }

                protected void onPostExecute(Boolean result) {
                    if (!result) {
                        Toast.makeText(TestWebViewActivity.this, "证书校验错误", Toast.LENGTH_SHORT).show();
                        tempView.stopLoading();
                    }
                };

            }.execute(url);
        }

        @Override
        public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
            // TODO Auto-generated method stub
            super.onReceivedSslError(view, handler, error);
            // Log.e(TAB, "onReceivedSslError");
        }
    }

}

浅析HTTPS中间人攻击与证书校验(三)


tornado平台中使用微信证书退款(https证书退款)( [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed解决)

 python  tornado平台中使用微信证书退款(https证书退款)( [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed解决)已关闭评论
9月 272016
 

使用tornado的httpclient做微信支付开发时,如果需要调用退款等需要证书验证的api时,可以使用下面的函数做证书验证:
(微信提供下载的证书文件有: apiclient_cert.p12, apiclient_cert.pem,apiclient_key.pem,rootca.pem)

from tornado import httpclient 
def get_response_httpclient_withcert(url,body,method, header = None, client_key=None, client_cert=None, ca_certs=None):
    http_client = httpclient.HTTPClient()
    request = httpclient.HTTPRequest(url, headers = header, body=body, method=method, client_key=client_key, client_cert=client_cert, connect_timeout=20.0, request_timeout=20.0)

    #如果出现类似“xxxxxx/tornado/iostream.py[line:1276] WARNING :SSL Error on 14 (‘x.x.x.x.’, 443): [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:590)”这样的错误,可以将ca_certs的参数也带上(即带上rootca.pem),使用下面的语句: 
    #request = httpclient.HTTPRequest(url, headers = header, body=body, method=method, client_key=client_key,     client_cert=client_cert, ca_certs=ca_certs, connect_timeout=20.0, request_timeout=20.0) 
    response = http_client.fetch(request)
    return response.body

调用举例:
module_path = os.path.dirname(__file__) 
client_cert_path = os.path.abspath(os.path.join(module_path, “../keys/apiclient_cert.pem”))
client_key_path = os.path.abspath(os.path.join(module_path, “../keys/apiclient_key.pem”))
 ca_certs_path = os.path.abspath(os.path.join(module_path, “../keys/rootca_wx.pem”))
resp = webRequrestUtil.get_response_httpclient_withcert(‘https://api.mch.weixin.qq.com/secapi/pay/refund’, “<xml> xxxxxxxxx</xml>”, “POST”, client_key=client_key_path, client_cert=client_cert_path, ca_certs=ca_certs_path)

 

TLS,SSL,HTTPS理解

 python  TLS,SSL,HTTPS理解已关闭评论
9月 102016
 

需要了解的背景知识:

  • 术语 HTTPS,SSL,TLS
  • 长连接与短连接的关系
  • 了解 CA 证书
  • 基本流程

一.术语扫盲

1.什么是SSL?

SSL(Secure Sockets Layer, 安全套接字),因为原先互联网上使用的 HTTP 协议是明文的,存在很多缺点——比如传输内容会被偷窥(嗅探)和篡改。发明 SSL 协议,就是为了解决这些问题。

2.那么什么是TLS呢?

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

很多相关的文章都把这两者并列称呼(SSL/TLS),因为这两者可以视作同一个东西的不同阶段。

3.那么什么是HTTPS呢?

HTTPS = HTTP + SSL/TLS, 也就是 HTTP over SSL 或 HTTP over TLS.这是后面加S的由来

Untitled Image

相对于HTTP:

  • http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
  • http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

二.长连接VS短连接

HTTP对TCP的连接使用分为:

  • 短连接
  • 长连接(又称“持久连接”,或“Keep-Alive”或“Persistent Connection”)

如果是短连接的话,针对每个HTML资源,就会针对每一个外部资源,分别发起一个个 TCP 连接。相反,如果是“长连接”的方式,浏览器也会先发起一个 TCP 连接去抓取页面。但是抓取页面之后,该 TCP 连接并不会立即关闭,而是暂时先保持着(所谓的“Keep-Alive”)。然后浏览器分析 HTML 源码之后,发现有很多外部资源,就用刚才那个 TCP 连接去抓取此页面的外部资源。

注意:

  • 在 HTTP 1.0 版本,【默认】使用的是“短连接”(那时候是 Web 诞生初期,网页相对简单,“短连接”的问题不大)
  • 在 HTTP 1.1 中,【默认】采用的是“Keep-Alive”的方式。

三.HTTPS的设计

HTTPS的设计要兼容HTTP

  • HTTPS 还是要基于 TCP 来传输
  • 单独使用一个新的协议,把 HTTP 协议包裹起来(所谓的“HTTP over SSL”,实际上是在原有的 HTTP 数据外面加了一层 SSL 的封装。HTTP 协议原有的 GET、POST 之类的机制,基本上原封不动)

关于HTTPS的性能,为了确保性能,SSL 的设计者至少要考虑如下几点:

  • 如何选择加密算法(“对称”or“非对称”)?
  • 如何兼顾 HTTP 采用的“短连接”TCP 方式?

四.简单运行过程

SSL/TLS协议的基本思路是采用公钥加密法,也就是说,客户端先向服务器端索要公钥,然后用公钥加密信息,服务器收到密文后,用自己的私钥解密。

问题:

  • 如何保证公钥不被篡改?:解决方法:将公钥放在数字证书中。只要证书是可信的,公钥就是可信的。
  • 公钥加密计算量太大,如何减少耗用的时间?解决方法:每一次对话(session),客户端和服务器端都生成一个”对话密钥”(session key),用它来加密信息。由于”对话密钥”是对称加密,所以运算速度非常快,而服务器公钥只用于加密”对话密钥”本身,这样就减少了加密运算的消耗时间。

因此,SSL/TLS协议的基本过程是这样的:

  • 客户端向服务器端索要并验证公钥。
  • 双方协商生成”对话密钥”。
  • 双方采用”对话密钥”进行加密通信。

如下图解:

Untitled Image

五.详解运行过程

如下图示:

Untitled Image

注意的是,”握手阶段”的所有通信都是明文的

1.客户端发出请求(ClientHello)

C向S提供信息如下:

  • 支持的协议版本,比如TLS 1.0版。
  • 一个客户端生成的随机数,稍后用于生成”对话密钥”。
  • 支持的加密方法,比如RSA公钥加密。
  • 支持的压缩方法。

2.服务器回应(SeverHello)

服务器收到客户端请求后,向客户端发出回应,这叫做SeverHello。服务器的回应包含以下内容。

  • 确认使用的加密通信协议版本,比如TLS 1.0版本。如果浏览器与服务器支持的版本不一致,服务器关闭加密通信。
  • 一个服务器生成的随机数,稍后用于生成”对话密钥”。
  • 确认使用的加密方法,比如RSA公钥加密。
  • 服务器证书。

除了上面这些信息,如果服务器需要确认客户端的身份,就会再包含一项请求,要求客户端提供”客户端证书”。

3.客户端回应

客户端收到服务器回应以后,首先验证服务器证书。如果证书不是可信机构颁布、或者证书中的域名与实际域名不一致、或者证书已经过期,就会向访问者显示一个警告,由其选择是否还要继续通信。

如果证书没有问题,客户端就会从证书中取出服务器的公钥。然后,向服务器发送下面三项信息。

  • 一个随机数。该随机数用服务器公钥加密,防止被窃听。
  • 编码改变通知,表示随后的信息都将用双方商定的加密方法和密钥发送。
  • 客户端握手结束通知,表示客户端的握手阶段已经结束。这一项同时也是前面发送的所有内容的hash值,用来供服务器校验。

现在总共有3个随机数,第三个又称”pre-master key”,有了它以后,客户端和服务器就同时有了三个随机数,接着双方就用事先商定的加密方法,各自 生成 本次会话 所用的 同一把 “会话密钥”。

4.服务器的最后回应

服务器收到客户端的第三个随机数pre-master key之后,计算生成本次会话所用的”会话密钥”。然后,向客户端最后发送下面信息。

  • 编码改变通知,表示随后的信息都将用双方商定的加密方法和密钥发送。
  • 服务器握手结束通知,表示服务器的握手阶段已经结束。这一项同时也是前面发送的所有内容的hash值,用来供客户端校验。

至此,整个握手阶段全部结束。接下来,客户端与服务器进入加密通信,就完全是使用普通的HTTP协议,只不过用”会话密钥”加密内容。

Untitled Image

六.Https的劣势

不完整总结如下:

  • 对数据进行加解密决定了它比http慢
  • https协议需要到CA申请证书。

转自:http://beginman.cn/python/2016/05/27/ssh-with-python/?utm_source=tuicool&utm_medium=referral

HTTPS 协议和原理

 加解密  HTTPS 协议和原理已关闭评论
9月 162015
 

一篇关于https原理的分析文章,虽然自己不能完全理解,但有收获,分享下

1 前言

百度已经于近日上线了全站 HTTPS 的安全搜索,默认会将 HTTP 请求跳转成 HTTPS。本文重点介绍 HTTPS 协议, 并简单介绍部署全站 HTTPS 的意义。

 

2 HTTPS 协议概述

HTTPS 可以认为是 HTTP + TLS。HTTP 协议大家耳熟能详了,目前大部分 WEB 应用和网站都是使用 HTTP 协议传输的。

TLS 是传输层加密协议,它的前身是 SSL 协议,最早由 netscape 公司于 1995 年发布,1999 年经过 IETF 讨论和规范后,改名为 TLS。如果没有特别说明,SSL 和 TLS 说的都是同一个协议。

HTTP 和 TLS 在协议层的位置以及 TLS 协议的组成如下图:

pic1

图 1 TLS 协议格式

TLS 协议主要有五部分:应用数据层协议,握手协议,报警协议,加密消息确认协议,心跳协议。

TLS 协议本身又是由 record 协议传输的,record 协议的格式如上图最右所示。

 

目前常用的 HTTP 协议是 HTTP1.1,常用的 TLS 协议版本有如下几个:TLS1.2, TLS1.1, TLS1.0 和 SSL3.0。其中 SSL3.0 由于 POODLE 攻击已经被证明不安全,但统计发现依然有不到 1% 的浏览器使用 SSL3.0。TLS1.0 也存在部分安全漏洞,比如 RC4 和 BEAST 攻击。

TLS1.2 和 TLS1.1 暂时没有已知的安全漏洞,比较安全,同时有大量扩展提升速度和性能,推荐大家使用。

需要关注一点的就是 TLS1.3 将会是 TLS 协议一个非常重大的改革。不管是安全性还是用户访问速度都会有质的提升。不过目前没有明确的发布时间。

同时 HTTP2 也已经正式定稿,这个由 SPDY 协议演化而来的协议相比 HTTP1.1 又是一个非常重大的变动,能够明显提升应用层数据的传输效率。

 

3 HTTPS 功能介绍

百度使用 HTTPS 协议主要是为了保护用户隐私,防止流量劫持。

HTTP 本身是明文传输的,没有经过任何安全处理。例如用户在百度搜索了一个关键字,比如“苹果手机”,中间者完全能够查看到这个信息,并且有可能打电话过来骚扰用户。也有一些用户投诉使用百度时,发现首页或者结果页面浮了一个很长很大的广告,这也肯定是中间者往页面插的广告内容。如果劫持技术比较低劣的话,用户甚至无法访问百度。

这里提到的中间者主要指一些网络节点,是用户数据在浏览器和百度服务器中间传输必须要经过的节点。比如 WIFI 热点,路由器,防火墙,反向代理,缓存服务器等。

在 HTTP 协议下,中间者可以随意嗅探用户搜索内容,窃取隐私甚至篡改网页。不过 HTTPS 是这些劫持行为的克星,能够完全有效地防御。

总体来说,HTTPS 协议提供了三个强大的功能来对抗上述的劫持行为:

1,  内容加密。浏览器到百度服务器的内容都是以加密形式传输,中间者无法直接查看原始内容。

2,  身份认证。保证用户访问的是百度服务,即使被 DNS 劫持到了第三方站点,也会提醒用户没有访问百度服务,有可能被劫持

3,  数据完整性。防止内容被第三方冒充或者篡改。

那 HTTPS 是如何做到上述三点的呢?下面从原理角度介绍一下。

 

4 HTTPS 原理介绍

4.1 内容加密

加密算法一般分为两种,对称加密和非对称加密。所谓对称加密(也叫密钥加密)就是指加密和解密使用的是相同的密钥。而非对称加密(也叫公钥加密)就是指加密和解密使用了不同的密钥。

pic2

图 2 对称加密

pic3

图 3 非对称加密

对称内容加密强度非常高,一般破解不了。但存在一个很大的问题就是无法安全地生成和保管密钥。假如客户端软件和服务器之间每次会话都使用固定的,相同的密钥加密和解密,肯定存在很大的安全隐患。如果有人从客户端端获取到了对称密钥,整个内容就不存在安全性了,而且管理海量的客户端密钥也是一件很复杂的事情。

非对称加密主要用于密钥交换(也叫密钥协商),能够很好地解决这个问题。浏览器和服务器每次新建会话时都使用非对称密钥交换算法协商出对称密钥,使用这些对称密钥完成应用数据的加解密和验证,整个会话过程中的密钥只在内存中生成和保存,而且每个会话的对称密钥都不相同(除非会话复用),中间者无法窃取。

非对称密钥交换很安全,但同时也是 HTTPS 性能和速度严重降低的“罪魁祸首”。想要知道 HTTPS 为什么影响速度,为什么消耗资源,就一定要理解非对称密钥交换的整个过程。

下面重点介绍一下非对称密钥交换的数学原理及在 TLS 握手过程中的应用。

4.1.1 非对称密钥交换

在非对称密钥交换算法出现以前,对称加密一个很大的问题就是不知道如何安全生成和保管密钥。非对称密钥交换过程主要就是为了解决这个问题,使得对称密钥的生成和使用更加安全。

密钥交换算法本身非常复杂,密钥交换过程涉及到随机数生成,模指数运算,空白补齐,加密,签名等操作。

常见的密钥交换算法有 RSA,ECDHE,DH,DHE 等算法。它们的特性如下:

  •  RSA:算法实现简单,诞生于 1977 年,历史悠久,经过了长时间的破解测试,安全性高。缺点就是需要比较大的素数(目前常用的是 2048 位)来保证安全强度,很消耗 CPU 运算资源。RSA 是目前唯一一个既能用于密钥交换又能用于证书签名的算法。
  • DH:diffie-hellman 密钥交换算法,诞生时间比较早(1977 年),但是 1999 年才公开。缺点是比较消耗 CPU 性能。
  • ECDHE:使用椭圆曲线(ECC)的 DH 算法,优点是能用较小的素数(256 位)实现 RSA 相同的安全等级。缺点是算法实现复杂,用于密钥交换的历史不长,没有经过长时间的安全攻击测试。
  •  ECDH:不支持 PFS,安全性低,同时无法实现 false start。
  •  DHE:不支持 ECC。非常消耗 CPU 资源。

建议优先支持 RSA 和 ECDH_RSA 密钥交换算法。原因是:

1,  ECDHE 支持 ECC 加速,计算速度更快。支持 PFS,更加安全。支持 false start,用户访问速度更快。

2,  目前还有至少 20% 以上的客户端不支持 ECDHE,我们推荐使用 RSA 而不是 DH 或者 DHE,因为 DH 系列算法非常消耗 CPU(相当于要做两次 RSA 计算)。

pic4

需要注意通常所说的 ECDHE 密钥交换默认都是指 ECDHE_RSA,使用 ECDHE 生成 DH 算法所需的公私钥,然后使用 RSA 算法进行签名最后再计算得出对称密钥。

非对称加密相比对称加密更加安全,但也存在两个明显缺点:

1,  CPU 计算资源消耗非常大。一次完全 TLS 握手,密钥交换时的非对称解密计算量占整个握手过程的 90% 以上。而对称加密的计算量只相当于非对称加密的 0.1%,如果应用层数据也使用非对称加解密,性能开销太大,无法承受。

2,  非对称加密算法对加密内容的长度有限制,不能超过公钥长度。比如现在常用的公钥长度是 2048 位,意味着待加密内容不能超过 256 个字节。

所以公钥加密目前只能用来作密钥交换或者内容签名,不适合用来做应用层传输内容的加解密。

非对称密钥交换算法是整个 HTTPS 得以安全的基石,充分理解非对称密钥交换算法是理解 HTTPS 协议和功能的关键。

下面分别通俗地介绍一下 RSA 和 ECDHE 在密钥交换过程中的应用。

4.1.1.1 RSA 密钥协商

4.1.1.1.1 RSA 算法介绍

RSA 算法的安全性是建立在乘法不可逆或者大数因子很难分解的基础上。RSA 的推导和实现涉及到了欧拉函数和费马定理及模反元素的概念,有兴趣的读者可以自行百度。

RSA 算法是统治世界的最重要算法之一,而且从目前来看,RSA 也是 HTTPS 体系中最重要的算法,没有之一。

RSA 的计算步骤如下:

1,  随机挑选两个质数 p, q,假设 p = 13, q = 19。 n = p * q = 13 * 19 = 247;

2,  ∅(n) 表示与整数 n 互质数的个数。如果 n 等于两个质数的积,则∅(n)=(p-1)(q-1) 挑选一个数 e,满足 1< e <∅(n) 并且 e 与互质,假设 e = 17;

3,  计算 e 关于 n 的模反元素, ed=1 mod ∅(n) , 由 e = 17 ,∅(n) =216  可得 d = 89;

4,  求出了 e,和 d,假设明文 m = 135,密文用 c 表示。那么加解密计算如下:

scr1

实际应用中,(n,e) 组成了公钥对,(n,d)组成了私钥对,其中 n 和 d 都是一个接近 22048的大数。即使现在性能很强的 CPU,想要计算 m≡c^d mod(n),也需要消耗比较大的计算资源和时间。

公钥对 (n, e) 一般都注册到了证书里,任何人都能直接查看,比如百度证书的公钥对如下图,其中最末 6 个数字(010001)换算成 10 进制就是 65537,也就是公钥对中的 e。e 取值比较小的好处有两个:

1,  由 c=m^e mod(n) 可知,e 较小,客户端 CPU 计算消耗的资源较少。

2,  加大 server 端的破解难度。e 比较小,私钥对中的 d 必然会非常大。所以 d 的取值空间也就非常大,增加了破解难度。

那为什么 (n,e) 能做为公钥公开,甚至大家都能直接从证书中查看到,这样安全吗?分析如下:

由于 ed≡1 mod ∅(n),知道了 e 和 n,想要求出私钥 d,就必须知道∅(n)。而∅(n)=(p-1)*(q-1),必须计算出 p 和 q 才能确定私钥 d。但是当 n 大到一定程度时(比如接近 2^2048),即使现在最快的 CPU 也无法进行这个因式分解,即无法知道 n 是由哪个数 p 和 q 乘出来的。所以就算知道了公钥,整个加解密过程还是非常安全的。

pic5

图 5 百度 HTTPS 证书公钥

4.1.1.1.2 握手过程中的 RSA 密钥协商

介绍完了 RSA 的原理,那最终会话所需要的对称密钥是如何生成的呢?跟 RSA 有什么关系?

以 TLS1.2 为例简单描述一下,省略跟密钥交换无关的握手消息。过程如下:

1,  浏览器发送 client_hello,包含一个随机数 random1。

2,  服务端回复 server_hello,包含一个随机数 random2,同时回复 certificate,携带了证书公钥 P。

3,  浏览器接收到 random2 之后就能够生成 premaster_secrect 以及 master_secrect。其中 premaster_secret 长度为 48 个字节,前 2 个字节是协议版本号,剩下的 46 个字节填充一个随机数。结构如下:

Struct {byte Version[2];bute random[46];}

master secrect 的生成算法简述如下:

Master_key = PRF(premaster_secret, “master secrect”, 随机数1+随机数2)其中 PRF 是一个随机函数,定义如下:PRF(secret, label, seed) = P_MD5(S1, label + seed)  XOR  P_SHA-1(S2, label + seed)

从上式可以看出,把 premaster_key 赋值给 secret,”master key”赋值给 label,浏览器和服务器端的两个随机数做种子就能确定地求出一个 48 位长的随机数。

而 master secrect 包含了六部分内容,分别是用于校验内容一致性的密钥,用于对称内容加解密的密钥,以及初始化向量(用于 CBC 模式),客户端和服务端各一份。

至此,浏览器侧的密钥已经完成协商。

4,  浏览器使用证书公钥 P 将 premaster_secrect 加密后发送给服务器。

5,  服务端使用私钥解密得到 premaster_secrect。又由于服务端之前就收到了随机数 1,所以服务端根据相同的生成算法,在相同的输入参数下,求出了相同的 master secrect。

RSA 密钥协商握手过程图示如下:

PIC2

图 6 RSA 密钥协商过程

可以看出,密钥协商过程需要 2 个 RTT,这也是 HTTPS 慢的一个重要原因。而 RSA 发挥的关键作用就是对 premaster_secrect 进行了加密和解密。中间者不可能破解 RSA 算法,也就不可能知道 premaster_secrect,从而保证了密钥协商过程的安全性。

4.1.1.2 ECDHE 密钥协商

4.1.1.2.1 DH 与 ECC 算法原理

ECDHE 算法实现要复杂很多,主要分为两部分:diffie-hellman 算法(简称为 DH)及 ECC(椭圆曲线算术)。他们的安全性都是建立在离散对数计算很困难的基础上。

简单介绍一下 dh 算法的实现,先介绍两个基本概念:

  • 本原根:如果整数 a 是素数 p 的本原根,则 a, a^2, …, a^(p-1) 在 mod p 下都不相同。
  •  离散对数:对任意整数 b 和素数 p 的本原根 a,存在唯一的指数 i 满足:

b ≡ a^i mod p (0≤i≤p-1)

则称 i 是 b 的以 a 为底的模 p 的离散对数。

理解这两个概念,dh 算法就非常简单了,示例如下:

假设 client 和 server 需要协商密钥,p=2579,则本原根 a = 2。

1,  Client 选择随机数 Kc = 123 做为自己的私钥,计算 Yc = a^Kc  mod p = 2^123 mod 2579 = 2400,把 Yc 作为公钥发送给 server。

2,  Server 选择随机数 Ks = 293 作为私钥,计算 Ys = a^Ks  mod p = s^293 mod 2579 = 968,把 Ys 作为公钥发送给 client。

3,  Client 计算共享密钥:secrect =  Ys^Kc mod (p) = 968^123  mod(2579) = 434

4,  Server 计算共享密钥:secrect = Yc^Ks mod(p) =2400^293 mod(2579) =434

上述公式中的 Ys,Yc,P, a, 都是公开信息,可以被中间者查看,只有 Ks,Kc 作为私钥没有公开,当私钥较小时,通过穷举攻击能够计算出共享密钥,但是当私钥非常大时,穷举攻击肯定是不可行的。

DH 算法有一个比较大的缺陷就是需要提供足够大的私钥来保证安全性,所以比较消耗 CPU 计算资源。ECC 椭圆曲线算术能够很好的解决这个问题,224 位的密钥长度就能达到 RSA2048 位的安全强度。

ECC 的曲线公式描述的其实不是椭圆,只是跟椭圆曲线周长公式形似才叫椭圆曲线加密算术。ECC 涉及到了有限域、群等近世代数的多个概念,就不做详细介绍了。

ECC 安全性依赖于这样一个事实:

P = kQ, 已知 k, Q 求出 P 相对简单,但是已知 P 和 Q 求出 k 却非常困难。

上式看起来非常简单,但有如下约束条件:

1,  Q 是一个非常大的质数,p, k, q 都是椭圆曲线有限域上的离散点。

2,  有限域定义了自己的加法和乘法法则,即使 kQ 的运算也非常复杂。

ECC 应用于 Diffie-Hellman 密钥交换过程如下:

1,  定义一个满足椭圆方程的有限域,即挑选 p, a, b 满足如下方程:

y^2 mod p = (x^3+ax +b) mod p

2,  挑选基点 G = (x, y),G 的阶为 n。n 为满足 nG = 0 的最小正整数。

3,  Client 选择私钥 Kc (0 <Kc<n ),产生公钥 Yc =Kc *G

4,  server 选择私钥 Ks 并产生公钥 Ys =Ks*G

5,  client 计算共享密钥 K = Kc*Ys   ,server 端计算共享密钥 Ks*Yc ,这两者的结果是一样的,因为:

 Kc*Ys = Kc*(Ks*G) = Ks*(Kc*G) = Ks*Yc

由上面描述可知,只要确定 p, a, b 就能确定一条有限域上的椭圆曲线,由于不是所有的椭圆曲线都能够用于加密,所以 p, a, b 的选取非常讲究,直接关系曲线的安全性和计算速度。

Openssl 实现的,也是 FIPS 推荐的 256 位素数域上的椭圆曲线参数定义如下:

质数 p = 115792089210356248762697446949407573530086143415290314195533631308867097853951 阶 n = 115792089210356248762697446949407573529996955224135760342422259061068512044369SEED = c49d3608 86e70493 6a6678e1 139d26b7 819f7e90c = 7efba166 2985be94 03cb055c 75d4f7e0 ce8d84a9 c5114abcaf317768 0104fa0d 椭圆曲线的系数 a = 0 椭圆曲线的系统 b = 5ac635d8 aa3a93e7 b3ebbd55 769886bc 651d06b0 cc53b0f63bce3c3e 27d2604b 基点 G x = 6b17d1f2 e12c4247 f8bce6e5 63a440f2 77037d81 2deb33a0f4a13945 d898c296 基点 G y = 4fe342e2 fe1a7f9b 8ee7eb4a 7c0f9e16 2bce3357 6b315ececbb64068 37bf51f5

 

4.1.1.2.2 握手过程中的 ECDHE 密钥协商

简单介绍了 ECC 和 DH 算法的数学原理,我们看下 ECDHE 在 TLS 握手过程中的应用。

相比 RSA,ECDHE 需要多发送一个 server_key_exchange 的握手消息才能完成密钥协商。

同样以 TLS1.2 为例,简单描述一下过程:

1,  浏览器发送 client_hello,包含一个随机数 random1,同时需要有 2 个扩展:

a)         Elliptic_curves:客户端支持的曲线类型和有限域参数。现在使用最多的是 256 位的素数域,参数定义如上节所述。

b)        Ec_point_formats:支持的曲线点格式,默认都是 uncompressed。

2,  服务端回复 server_hello,包含一个随机数 random2 及 ECC 扩展。

3,  服务端回复 certificate,携带了证书公钥。

4,  服务端生成 ECDH 临时公钥,同时回复 server_key_exchange,包含三部分重要内容:

a)         ECC 相关的参数。

b)        ECDH 临时公钥。

c)         ECC 参数和公钥生成的签名值,用于客户端校验。

5,  浏览器接收 server_key_exchange 之后,使用证书公钥进行签名解密和校验,获取服务器端的 ECDH 临时公钥,生成会话所需要的共享密钥。

至此,浏览器端完成了密钥协商。

6,  浏览器生成 ECDH 临时公钥和 client_key_exchange 消息,跟 RSA 密钥协商不同的是,这个消息不需要加密了。

7,  服务器处理 client_key_exchang 消息,获取客户端 ECDH 临时公钥。

8,  服务器生成会话所需要的共享密钥。

9,  Server 端密钥协商过程结束。

 

图示如下:

PIC1

图 7 ECDHE 密钥协商过程

4.1.2 对称内容加密

非对称密钥交换过程结束之后就得出了本次会话需要使用的对称密钥。对称加密又分为两种模式:流式加密和分组加密。流式加密现在常用的就是 RC4,不过 RC4 已经不再安全,微软也建议网站尽量不要使用 RC4 流式加密

一种新的替代 RC4 的流式加密算法叫 ChaCha20,它是 google 推出的速度更快,更安全的加密算法。目前已经被 android 和 chrome 采用,也编译进了 google 的开源 openssl 分支 —boring ssl,并且nginx 1.7.4 也支持编译 boringssl

分组加密以前常用的模式是 AES-CBC,但是 CBC 已经被证明容易遭受BEASTLUCKY13 攻击。目前建议使用的分组加密模式是 AES-GCM,不过它的缺点是计算量大,性能和电量消耗都比较高,不适用于移动电话和平板电脑。

4.2 身份认证

身份认证主要涉及到 PKI 和数字证书。通常来讲 PKI(公钥基础设施)包含如下部分:

  • End entity:终端实体,可以是一个终端硬件或者网站。
  • CA:证书签发机构。
  • RA:证书注册及审核机构。比如审查申请网站或者公司的真实性。
  • CRL issuer:负责证书撤销列表的发布和维护。
  • Repository:负责数字证书及 CRL 内容存储和分发。

申请一个受信任的数字证书通常有如下流程:

1,  终端实体生成公私钥和证书请求。

2,  RA 检查实体的合法性。如果个人或者小网站,这一步不是必须的。

3,  CA 签发证书,发送给申请者。

4,  证书更新到 repository,终端后续从 repository 更新证书,查询证书状态等。

目前百度使用的证书是 X509v3 格式,由如下三个部分组成:

1,  tbsCertificate(to be signed certificate 待签名证书内容),这部分包含了 10 个要素,分别是版本号,序列号,签名算法标识,发行者名称,有效期,证书主体名,证书主体公钥信息,发行商唯一标识,主体唯一标识,扩展等。

2,  signatureAlgorithm,签名算法标识,指定对 tbsCertificate 进行签名的算法。

3,  signaturValue(签名值),使用 signatureAlgorithm 对 tbsCertificate 进行计算得到签名值。

数字证书有两个作用:

1,  身份授权。确保浏览器访问的网站是经过 CA 验证的可信任的网站。

2,  分发公钥。每个数字证书都包含了注册者生成的公钥。在 SSL 握手时会通过 certificate 消息传输给客户端。比如前文提到的 RSA 证书公钥加密及 ECDHE 的签名都是使用的这个公钥。

申请者拿到 CA 的证书并部署在网站服务器端,那浏览器发起握手接收到证书后,如何确认这个证书就是 CA 签发的呢?怎样避免第三方伪造这个证书?

答案就是数字签名(digital signature)。数字签名是证书的防伪标签,目前使用最广泛的 SHA-RSA 数字签名的制作和验证过程如下:

1,  数字签名的签发。首先是使用哈希函数对待签名内容进行安全哈希,生成消息摘要,然后使用 CA 自己的私钥对消息摘要进行加密。

2,  数字签名的校验。使用 CA 的公钥解密签名,然后使用相同的签名函数对待签名证书内容进行签名并和服务端数字签名里的签名内容进行比较,如果相同就认为校验成功。

pic8

图 8 数字签名生成及校验

这里有几点需要说明:

  1. 数字签名签发和校验使用的密钥对是 CA 自己的公私密钥,跟证书申请者提交的公钥没有关系。
  2. 数字签名的签发过程跟公钥加密的过程刚好相反,即是用私钥加密,公钥解密。
  3. 现在大的 CA 都会有证书链,证书链的好处一是安全,保持根 CA 的私钥离线使用。第二个好处是方便部署和撤销,即如果证书出现问题,只需要撤销相应级别的证书,根证书依然安全。
  4. 根 CA 证书都是自签名,即用自己的公钥和私钥完成了签名的制作和验证。而证书链上的证书签名都是使用上一级证书的密钥对完成签名和验证的。
  5. 怎样获取根 CA 和多级 CA 的密钥对?它们是否可信?当然可信,因为这些厂商跟浏览器和操作系统都有合作,它们的公钥都默认装到了浏览器或者操作系统环境里。比如firefox 就自己维护了一个可信任的 CA 列表,而chrome 和 IE 使用的是操作系统的 CA 列表

4.3 数据完整性

这部分内容比较好理解,跟平时的 md5 签名类似,只不过安全要求要高很多。openssl 现在使用的完整性校验算法有两种:MD5 或者 SHA。由于 MD5 在实际应用中存在冲突的可能性比较大,所以尽量别采用 MD5 来验证内容一致性。SHA 也不能使用 SHA0 和 SHA1,中国山东大学的王小云教授在 2005 年就宣布破解了 SHA-1 完整版算法

微软和 google 都已经宣布 16 年及 17 年之后不再支持 sha1 签名证书。

 

5 HTTPS 使用成本

HTTPS 目前唯一的问题就是它还没有得到大规模应用,受到的关注和研究都比较少。至于使用成本和额外开销,完全不用太过担心。

一般来讲,使用 HTTPS 前大家可能会非常关注如下问题:

  1. 证书费用以及更新维护。大家觉得申请证书很麻烦,证书也很贵,可是证书其实一点都不贵,便宜的一年几十块钱,最多也就几百。而且现在也有了免费的证书机构,比如著名的 mozilla 发起的免费证书项目:let’s encrypt(https://letsencrypt.org/)就支持免费证书安装和自动更新。这个项目将于今年中旬投入正式使用。

数字证书的费用其实也不高,对于中小网站可以使用便宜甚至免费的数字证书服务(可能存在安全隐患),像著名的 verisign 公司的证书一般也就几千到几万块一年不等。当然如果公司对证书的需求比较大,定制性要求高,可以建立自己的 CA 站点,比如 google,能够随意签发 google 相关证书。

  1. HTTPS 降低用户访问速度。HTTPS 对速度会有一定程度的降低,但是只要经过合理优化和部署,HTTPS 对速度的影响完全可以接受。在很多场景下,HTTPS 速度完全不逊于 HTTP,如果使用 SPDY,HTTPS 的速度甚至还要比 HTTP 快。

大家现在使用百度 HTTPS 安全搜索,有感觉到慢吗?

  1. HTTPS 消耗 CPU 资源,需要增加大量机器。前面介绍过非对称密钥交换,这是消耗 CPU 计算资源的大户,此外,对称加解密,也需要 CPU 的计算。

同样地,只要合理优化,HTTPS 的机器成本也不会明显增加。对于中小网站,完全不需要增加机器也能满足性能需求。

6 后记

国外的大型互联网公司很多已经启用了全站 HTTPS,这也是未来互联网的趋势。国内的大型互联网并没有全站部署 HTTPS,只是在一些涉及账户或者交易的子页面 / 子请求上启用了 HTTPS。百度搜索首次全站部署 HTTPS,对国内互联网的全站 HTTPS 进程必将有着巨大的推动作用。

目前互联网上关于 HTTPS 的中文资料比较少,本文就着重介绍了 HTTPS 协议涉及到的重要知识点和平时不太容易理解的盲区,希望能对大家理解 HTTPS 协议有帮助。百度 HTTPS 性能优化涉及到大量内容,从前端页面、后端架构、协议特性、加密算法、流量调度、架构和运维、安全等方面都做了大量工作。本系列的文章将一一进行介绍。

转自:http://op.baidu.com/2015/04/https-s01a01/

使用 mitmproxy 代理获取 HTTPS 请求

 java  使用 mitmproxy 代理获取 HTTPS 请求已关闭评论
6月 262015
 

mitmproxy抓https请求的文章,分享下,https不自定义加密都不安全了。

一、前言

http proxy在web渗透上占据着非常重要的地位,这方面的工具也非常多,像burp suite, Fiddler,Charles简直每个搞web的必备神器,还有历史比较久远的paros,webscarab等,实际上每个web漏洞扫描器都有http代理的功能。

而今天要介绍的mitmproxy代理工具,非常棒,特别是对https数据的截获,及可扩展性,bra bra……

之所以选择这款工具,是为了做移动APP的通信分析。我们知道使用http代理的基本条件就是网络要互通,而很多情况下,公司的移动网络与办公网络是隔离的,当然你可以用网络共享(或者买个小度wifi或360wifi)的方法来解决隔离。(记得几年前做移动通信分析时就遇到了网络隔离且没有无线网卡的尴尬情况。)

我的选择是在vps上搭建一个http代理(刚买了vps,正可着劲的折腾呢),这时候就需要命令行的工具了,于是选择了mitmproxy(其实我也用nginx搭建了代理,目前以查看通信为主,待熟练了,或许能用nginx-lua模块写个截获重写的模块,悲催的是nginx不支持https,扯远了)。

二、mitmproxy原理

mitmproxy就是代理,有篡改功能的代理。它支持两种模式,正向代理,与透明代理,支持http通信与https通信。

(正向代理,反向代理,透明代理的科普可以看http://z00w00.blog.51cto.com/515114/1031287这篇文章,很容易懂)

mitmproxy官网上也有介绍原理,这里附上好心人的翻译http://www.oschina.net/translate/how-mitmproxy-works

对代理很了解的同学,看看下面四幅图,就能知道是咋回事了。

1. http正向代理

mitmproxy——中间人攻击的神器 - 碳基体 - 碳基体

2. https正向代理

mitmproxy——中间人攻击的神器 - 碳基体 - 碳基体

3. http透明代理

mitmproxy——中间人攻击的神器 - 碳基体 - 碳基体

4. https透明代理

mitmproxy——中间人攻击的神器 - 碳基体 - 碳基体

三、mitmproxy安装

接下来我要在vps上安装mitmproxy了,该工具使用python编写,现在非常多的工具都是python编写的,都有专门的python黑客培训机构了,话说不会python的程序员不是好程序员。

python是跨平台的,因此mitmproxy也是跨平台的。

接下来以linux(debian 7)上安装为例,其他平台非常类似(mac上安装包用homebrew就好)

1. 首先安装python环境,了解python的可以不用看

#python及python依赖包apt-get install build-essential python-dev python-setuptools#安装pip,很好的python包管理器,类似于aptitude,apt-getwget https://pypi.python.org/packages/source/p/pip/pip-1.4.1.tar.gzpython setup.py install

2. 安装mitmproxy依赖包

sudo pip install netlib pyopenssl pyasn1 urwid pil lxml flask#下面是可选(为了解码用)sudo pip install pyamf protobuf#下面是可选(为了测试用)sudo pip install nose pathod countershape

3. 安装mitmproxy

sudo pip install mitmproxy

安装成功后会在生成两个工具/usr/local/bin/mitmproxy与/usr/local/bin/mitmdump

本人是个开源工具杀手,总会遇到问题

安装问题解决:

如果使用pip安装时,出现pkg_resources.DistributionNotFound:(刚升级了os x Mavericks版本就出现了这个问题),可以先更新pip

sudo easy_install –upgrade distributesudo easy_install –upgrade pip

四、CA证书的安装

要捕获https证书,就得解决证书认证的问题,因此需要在通信发生的客户端安装证书,并且设置为受信任的根证书颁布机构。下面介绍6种客户端的安装方法。

当我们初次运行mitmproxy或mitmdump时,

会在当前目录下生成 ~/.mitmproxy文件夹,其中该文件下包含4个文件,这就是我们要的证书了。

mitmproxy-ca.pem 私钥

mitmproxy-ca-cert.pem 非windows平台使用

mitmproxy-ca-cert.p12 windows上使用

mitmproxy-ca-cert.cer 与mitmproxy-ca-cert.pem相同,android上使用

1. Firefox上安装

preferences-Advanced-Encryption-View Certificates-Import (mitmproxy-ca-cert.pem)-trust this CA to identify web sites

2. chrome上安装

设置-高级设置-HTTPS/SSL-管理证书-受信任的根证书颁发机构-导入mitmproxy-ca-cert.pem

2. osx上安装

双击mitmproxy-ca-cert.pem – always trust

3.windows7上安装

双击mitmproxy-ca-cert.p12-next-next-将所有的证书放入下列存储-受信任的根证书发布机构

4.iOS上安装

将mitmproxy-ca-cert.pem发送到iphone邮箱里,通过浏览器访问/邮件附件

我将证书放在了vps上以供下载

http://tanjiti.com/crt/mitmproxy-ca-cert.pem mitmproxy iOS

http://tanjiti.com/crt/mitmproxy-ca-cert.cer mitmproxy android

http://tanjiti.com/crt/mitmproxy-ca-cert.p12 windows

http://tanjiti.com/crt/PortSwigger.cer BurpSuite (burpsuite的证书,随便附上)

5.iOS模拟器上安装

git clone https://github.com/ADVTOOLS/ADVTrustStore.gitcd ADVTrustStore/

DANI-LEE-2:ADVTrustStore danqingdani$ python iosCertTrustManager.py -a ~/iostools/mitmproxy-ca-cert.pem

subject= CN = mitmproxy, O = mitmproxyImport certificate to iPhone/iPad simulator v5.1 [y/N] yImporting to /Users/danqingdani/Library/Application Support/iPhone Simulator/5.1/Library/Keychains/TrustStore.sqlite3 Certificate added

实际上上面的操作就是给 ~/Library/Application Support/iPhone Simulator/5.1/Library/Keychains/TrustStore.sqlite3 数据库中表tsettings表中插入证书数据

6.Android上安装

将mitmproxy-ca-cert.cer 放到sdcard根目录下

选择设置-安全和隐私-从存储设备安装证书

五、工具使用

在vps上装好了mitmproxy代理,在客户端也装好了CA证书,接下来就可以使用了。

第一步:在vps上启动mitmproxy

mitmproxy -b xxx.xxx.xxx(指定监听的接口) -p xxx(指定端口)

果然我是开源工具杀手,运行时又报错了。

运行错误问题解决:

当运行mitmproxy报错:

Error: mitmproxy requires a UTF console environment.

Set your LANG enviroment variable to something like en_US.UTF-8

你可以先运行locale查看当前的语言环境,我的vps就是POSIX环境

[email protected]:/# locale

LANG=

LANGUAGE=

LC_CTYPE=”POSIX”

LC_NUMERIC=”POSIX”

LC_TIME=”POSIX”

LC_COLLATE=”POSIX”

LC_MONETARY=”POSIX”

LC_MESSAGES=”POSIX”

LC_PAPER=”POSIX”

LC_NAME=”POSIX”

LC_ADDRESS=”POSIX”

LC_TELEPHONE=”POSIX”

LC_MEASUREMENT=”POSIX”

LC_IDENTIFICATION=”POSIX”

LC_ALL=

现在我们需要的是把其修改为en_US.UTF-8

方法参考http://jrs-s.net/2010/11/18/setting-locale-to-utf-8-in-debian/

vim /etc/default/localeLANG=en_US.UTF-8. locale-gen#编辑/etc/profile,与/etc/bash.bashrc,增加 export LANG=en_US.UTF-8echo “export LANG=en_US.UTF-8” > /etc/profileecho “export LANG=en_US.UTF-8” > /etc/bash.bashrc source /etc/profilesource /etc/bash.bashrc

现在再运行locale,可以看到语言修改过来了

[email protected]:/# locale

LANG=en_US.UTF-8

LANGUAGE=

LC_CTYPE=”en_US.UTF-8″

LC_NUMERIC=”en_US.UTF-8″

LC_TIME=”en_US.UTF-8″

LC_COLLATE=”en_US.UTF-8″

LC_MONETARY=”en_US.UTF-8″

LC_MESSAGES=”en_US.UTF-8″

LC_PAPER=”en_US.UTF-8″

LC_NAME=”en_US.UTF-8″

LC_ADDRESS=”en_US.UTF-8″

LC_TELEPHONE=”en_US.UTF-8″

LC_MEASUREMENT=”en_US.UTF-8″

LC_IDENTIFICATION=”en_US.UTF-8″

LC_ALL=

然后就可以正常的运行了!

第二步:在手机或PC或浏览器上选择使用该http代理

第三步:使用客户端访问,现在就可以操作通信数据了

六、常见操作

1. mitmproxy

就介绍最常用到的修改请求,并回放请求的方法吧

(1)方向键定位请求

mitmproxy——中间人攻击的神器 - 碳基体 - 碳基体

(2)当黄色箭头>>定位到指定请求时,按回车enter进入请求中

mitmproxy——中间人攻击的神器 - 碳基体 - 碳基体

(3)按e进入编辑状态,然后按对应的蓝色字体来选择修改的部分

mitmproxy——中间人攻击的神器 - 碳基体 - 碳基体

可以修改query,查询字符串;path,路径;url ;header 请求头;form 表单;raw body 请求正文;method 请求方法。

(4)a 增加一行,tab键切换编辑字段,回车enter开始编辑,esc保存,q返回上一级

(5)修改完后,按r就可以重放请求,然后查看修改结果了

2. mitmdump

别忘了,mitmproxy还有个内向的双胞胎叫mitmdump(很像tcpdump),它是不交互版的mitmproxy。可以非实时的处理通信包。

我们可以在mitmproxy中按w,将通信数据保存到指定文件中后,然后用mitmdump来操作。接下来简单介绍一个例子,从mitmproxy中捕获到的数据包中,筛选出来自微博的数据包,然后重放这个数据包(其实也可以修改后再重放)

-n 表示不启用代理, -r表示从文件中读取数据包, -w表示将数据包存储到文件中,-c表示重放客户端请求包

mitmdump -nr all.data -w weibo.data “~u weibo”

mitmdump -nc weibo.data[replay] POST http://api.weibo.cn/2/client/addlog_batch?s=2edc0cfa7&gsid=4ubed3V0QehBa8KoNp4AA75J&c=android&wm=20005_0002&ua=Xiaomi-MI+2S__weibo__4.0.1__android__android4.1.1&oldwm=9975_0001&from=1040195010&skin=default&i=8764056d2&isgzip=&lang=zh_CN<< 200 OK 32B

3. mitmproxy API

开源精神最赞的是,可以像小时候玩积木一样,用大牛们提供的各种精巧工具,搭建自己合适的武器。

mitmproxy提供了libmproxy以供调用扩展。

我们可以查看一下libmproxy的详细说明,了解主要的API接口调用

pydoc libmproxy

官网给了一个自己编写脚本,来操纵数据包的例子,很简单,人人都能看懂

如下所示,就是在响应包中增加一个自定义头

def response(context, flow): flow.response.headers[“newheader”] = [“foo”]

我们可以在mitmdump 中使用这个脚本

-s表示从读取自定义脚本来处理数据包

mitmdump -ns examples/add_header.py -r infile -w outfile

好了,就介绍到这了。

转自:http://segmentfault.com/a/1190000002524455

Java Tomcat SSL 服务端/客户端双向认证のApache HttpClient(二)

 tomcat  Java Tomcat SSL 服务端/客户端双向认证のApache HttpClient(二)已关闭评论
5月 212013
 

转自:http://www.blogjava.net/icewee/archive/2012/06/05/379983.html

本演示例程是继Java Tomcat SSL 服务端/客户端双向认证(一),密钥库可证书的生成脚本不再重复黏贴,仅仅是用程序来代替浏览器访问服务端。

例程中使用到了Apache HttpClient库,版本为4.1.3

全部依赖库:

commons-logging-1.1.1.jar

httpclient-4.1.3.jar

httpcore-4.1.4.jar

httpmime-4.1.3.jar(上传文件使用)


在(一)中的程序包中创建一个客户端类:HttpsClient

HttpsClient.java

 

package com.icesoft.client;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.KeyStore;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;

public class HttpsClient {
    
    private static final String KEY_STORE_TYPE_JKS = "jks";
    private static final String KEY_STORE_TYPE_P12 = "PKCS12";
    private static final String SCHEME_HTTPS = "https";
    private static final int HTTPS_PORT = 8443;
    private static final String HTTPS_URL = "https://127.0.0.1:8443/HttpClientSSL/sslServlet";
    private static final String KEY_STORE_CLIENT_PATH = "E:/ssl/client.p12";
    private static final String KEY_STORE_TRUST_PATH = "E:/ssl/client.truststore";
    private static final String KEY_STORE_PASSWORD = "123456";
    private static final String KEY_STORE_TRUST_PASSWORD = "123456";

    public static void main(String[] args) throws Exception {
        ssl();
    }

    
    private static void ssl() throws Exception {
        HttpClient httpClient = new DefaultHttpClient();
        try {
            KeyStore keyStore  = KeyStore.getInstance(KEY_STORE_TYPE_P12);
            KeyStore trustStore  = KeyStore.getInstance(KEY_STORE_TYPE_JKS);
            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(); } catch (Exception ignore) {}
                try { tsIn.close(); } catch (Exception ignore) {}
            }

            SSLSocketFactory socketFactory = new SSLSocketFactory(keyStore, KEY_STORE_PASSWORD, trustStore);
            Scheme sch = new Scheme(SCHEME_HTTPS, HTTPS_PORT, socketFactory);
            httpClient.getConnectionManager().getSchemeRegistry().register(sch);
            HttpGet httpget = new HttpGet(HTTPS_URL);
            System.out.println("executing request" + httpget.getRequestLine());
            HttpResponse response = httpClient.execute(httpget);
            HttpEntity entity = response.getEntity();
            System.out.println("—————————————-");
            System.out.println(response.getStatusLine());
            if (entity != null{
                System.out.println("Response content length: " + entity.getContentLength());
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(entity.getContent()));
                String text;
                while ((text = bufferedReader.readLine()) != null{
                    System.out.println(text);
                }

                bufferedReader.close();
            }

            EntityUtils.consume(entity);
        }
 finally {
            httpClient.getConnectionManager().shutdown();
        }

    }


}



启动Tomcat,运行HttpsClient,控制台返回:



OK,和使用浏览器访问得到的结果一模一样!


全文完!

Java Tomcat SSL 服务端/客户端双向认证(一)

 tomcat  Java Tomcat SSL 服务端/客户端双向认证(一)已关闭评论
5月 212013
 

转自:http://www.blogjava.net/icewee/archive/2012/06/04/379947.html

SSL——Secure Sockets Layer



双向认证(个人理解):

客户端认证:

客户端通过浏览器访问某一网站时,如果该网站为HTTPS网站,浏览器会自动检测系统中是否存在该网站的信任证书,如果没有信任证书,浏览器一般会拒绝访问,IE会有一个继续访问的链接,但地址栏是红色,给予用户警示作用,即客户端验证服务端并不是强制性的,可以没有服务端的信任证书,当然是否继续访问完全取决于用户自己。如何去除地址栏的红色警告呢?后续会介绍导入服务端证书到浏览器的方法。



服务端认证:

服务端需要获取到客户端通过浏览器发送过来的认证证书,该证书在服务端的证书库中已存在,仅仅是个匹配过程,匹配成功即通过认证,可继续访问网站资源,反之则无法显示网页,后续有截图。



基本逻辑:

1、生成服务端密钥库并导出证书;

2、生成客户端密钥库并导出证书;

3、根据服务端密钥库生成客户端信任的证书;

4、将客户端证书导入服务端密钥库;

5、将服务端证书导入浏览器。



构建演示系统

演示环境:

JDK:1.6.0_32

Tomcat:apache-tomcat-7.0.27

开发工具:MyEclipse 10

浏览器:Internet Explorer 9



一、生成密钥库和证书

可参考以下密钥生成脚本,根据实际情况做必要的修改,其中需要注意的是:服务端的密钥库参数“CN”必须与服务端的IP地址相同,否则会报错,客户端的任意。

key.script

 

1、生成服务器证书库

keytool -validity 365 -genkey -v -alias server -keyalg RSA -keystore E:sslserver.keystore -dname "CN=127.0.0.1,OU=icesoft,O=icesoft,L=Haidian,ST=Beijing,c=cn" -storepass 123456 -keypass 123456


2、生成客户端证书库

keytool -validity 365 -genkeypair -v -alias client -keyalg RSA -storetype PKCS12 -keystore E:sslclient.p12 -dname "CN=client,OU=icesoft,O=icesoft,L=Haidian,ST=Beijing,c=cn" -storepass 123456 -keypass 123456


3、从客户端证书库中导出客户端证书

keytool -export -v -alias client -keystore E:sslclient.p12 -storetype PKCS12 -storepass 123456 -rfc -file E:sslclient.cer


4、从服务器证书库中导出服务器证书

keytool -export -v -alias server -keystore E:sslserver.keystore -storepass 123456 -rfc -file E:sslserver.cer


5、生成客户端信任证书库(由服务端证书生成的证书库)

keytool –import -v -alias server -file E:sslserver.cer -keystore E:sslclient.truststore -storepass 123456


6、将客户端证书导入到服务器证书库(使得服务器信任客户端证书)

keytool –import -v -alias client -file E:sslclient.cer -keystore E:sslserver.keystore -storepass 123456


7、查看证书库中的全部证书

keytool -list -keystore E:sslserver.keystore -storepass 123456





二、Tomat配置

使用文本编辑器编辑${catalina.base}/conf/server.xml

找到Connector port="8443"的标签,取消注释,并修改成如下:

<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol" SSLEnabled="true"
               maxThreads="150" scheme="https" secure="true"
               clientAuth="true" sslProtocol="TLS"
               keystoreFile="${catalina.base}/key/server.keystore" keystorePass="123456"
               truststoreFile="${catalina.base}/key/server.keystore" truststorePass="123456"/>



备注:

keystoreFile:指定服务器密钥库,可以配置成绝对路径,如“D:/key/server.keystore”,本例中是在Tomcat目录中创建了一个名称为key的文件夹,仅供参考。

keystorePass:密钥库生成时的密码

truststoreFile:受信任密钥库,和密钥库相同即可

truststorePass:受信任密钥库密码



三、建立演示项目

项目结构图:

项目名称:SSL(随意)





SSLServlet.java

package com.icesoft.servlet;

import java.io.IOException;
import java.io.PrintWriter;
import java.security.cert.X509Certificate;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * <p>
 * SSL Servlet
 * </p>
 * 
 * 
@author IceWee
 * @date 2012-6-4
 * 
@version 1.0
 
*/

public class SSLServlet extends HttpServlet {

    private static final long serialVersionUID = 1601507150278487538L;
    private static final String ATTR_CER = "javax.servlet.request.X509Certificate";
    private static final String CONTENT_TYPE = "text/plain;charset=UTF-8";
    private static final String DEFAULT_ENCODING = "UTF-8";
    private static final String SCHEME_HTTPS = "https";

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType(CONTENT_TYPE);
        response.setCharacterEncoding(DEFAULT_ENCODING);
        PrintWriter out = response.getWriter();
        X509Certificate[] certs = (X509Certificate[]) request.getAttribute(ATTR_CER);
        if (certs != null{
            int count = certs.length;
            out.println("共检测到[” + count + “]个客户端证书");
            for (int i = 0; i < count; i++) {
                out.println("客户端证书 [” + (++i) + “]: ");
                out.println("校验结果:" + verifyCertificate(certs[–i]));
                out.println("证书详细:r" + certs[i].toString());
            }

        }
 else {
            if (SCHEME_HTTPS.equalsIgnoreCase(request.getScheme())) {
                out.println("这是一个HTTPS请求,但是没有可用的客户端证书");
            }
 else {
                out.println("这不是一个HTTPS请求,因此无法获得客户端证书列表 ");
            }

        }

        out.close();
    }


    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }

    
    /**
     * <p>
     * 校验证书是否过期
     * </p>
     * 
     * 
@param certificate
     * 
@return
     */

    private boolean verifyCertificate(X509Certificate certificate) {
        boolean valid = true;
        try {
            certificate.checkValidity();
        }
 catch (Exception e) {
            e.printStackTrace();
            valid = false;
        }

        return valid;
    }


}



web.xml

说明:该演示项目强制使用了SSL,即普通的HTTP请求也会强制重定向为HTTPS请求,配置在最下面,可以去除,这样HTTP和HTTPS都可以访问。

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" 
    xmlns
="http://java.sun.com/xml/ns/javaee" 
    xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation
="http://java.sun.com/xml/ns/javaee 
    http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
>
      <display-name>Secure Sockets Layer</display-name>    
    
    <servlet>
        <servlet-name>SSLServlet</servlet-name>
        <servlet-class>com.icesoft.servlet.SSLServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>SSLServlet</servlet-name>
        <url-pattern>/sslServlet</url-pattern>
    </servlet-mapping>
    
    <welcome-file-list>
      <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>

    <!– 强制SSL配置,即普通的请求也会重定向为SSL请求 –>  
    <security-constraint>
        <web-resource-collection>
            <web-resource-name>SSL</web-resource-name>
            <url-pattern>/*</url-pattern><!– 全站使用SSL –>
        </web-resource-collection>
        <user-data-constraint>
            <description>SSL required</description>
            <!– CONFIDENTIAL: 要保证服务器和客户端之间传输的数据不能够被修改,且不能被第三方查看到 –>
            <!– INTEGRAL: 要保证服务器和client之间传输的数据不能够被修改 –>
            <!– NONE: 指示容器必须能够在任一的连接上提供数据。(即用HTTP或HTTPS,由客户端来决定)–>
            <transport-guarantee>CONFIDENTIAL</transport-guarantee>
        </user-data-constraint>
    </security-constraint>
</web-app>





index.jsp

<%@ page language="java" pageEncoding="UTF-8"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>客户端证书上传</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">    
</head>
<body>
<form action="${pageContext.request.contextPath}/sslServlet" method="post">
    <input type="submit"  value="提交证书"/>
</form>
</body>
</html>





四、演示及配置

发布演示项目,通过浏览器访问:http://127.0.0.1:8080/SSLhttps://127.0.0.1:8443/SSL,得到相同的结果,如图:













得到如上结果的原始是因为客户端没有通过服务端的安全认证,接下来将服务端给客户端颁发的证书导入到浏览器中:

双击“client.p12”







弹出窗口,下一步







默认,下一步







输入生成密钥时的密码“123456”,下一步







下一步







完成







成功







再次访问http://127.0.0.1:8080/SSLhttps://127.0.0.1:8443/SSL,弹出提示框:







点击确定后,IE浏览器自动阻止了继续访问,并给予警告提示,原因是浏览器中未导入该网站的可信证书











点击“继续浏览此网站”,弹出提示,点击确定







哇!鲜红的地址栏,够醒目吧!你访问的网站不安全那,亲!







点击“提交证书”按钮,返回正确结果!







可以看出,客户端并没有服务端那么严格,只要未通过验证就甭想访问,下面将服务端生成的信任证书导入到浏览器的根证书中,这样红色的地址栏就会消失了!

开始导入服务端信任证书,不能双击“server.cer”,需要手动导入到受信任的根证书机构中去。







浏览器Internet选项-内容-证书







点击“受信任的根证书颁发机构”







点击“导入”







下一步







手动选择“server.cer”,下一步













下一步







完成







点“是”









成功











可以看到我们刚刚导入的根证书







把所有浏览器窗口都关掉,再次访问网站,发现鲜红色已经逝去







点击“提交证书”按钮,一切正常了,双向认证的DEMO结束了!