当前位置:   article > 正文

网络编程——HTTPS和SSL握手小结及使用原生Socket 完成HTTPS简单通信_ssl使用绑定得socket端口吗

ssl使用绑定得socket端口吗

引言

前一篇文章 网络编程——使用原生Socket 完成HTTP简单通信简单实现了使用原生Socket的方式进行了HTTP通信,不过呢,HTTP协议以明文方式发送内容,不提供任何方式的数据加密,通过抓包工具就可以捕获报文的信息,因此HTTPS 应运而生。

一、HTTPS概述

超文本传输安全协议HTTPS(Hyper Text Transfer Protocol over Secure Socket Layer 或 Hypertext Transfer Protocol Secure)是HTTP的安全版,即HTTPS=HTTP+SSL,SSL是HTTPS的安全保障,HTTPS是在HTTP的基础上加上了SSL安全加密层,把原来的明文数据进行加密之后再进行传输,如下图:
在这里插入图片描述
为了数据传输的安全,HTTPS在HTTP的基础上加入了SSL\TSL协议,SSL\TSL依靠CA证书来验证服务器的身份,并为Client和Server此次通信进行加密,如下图示是我抓的对比包:
在这里插入图片描述

二、SSL\TSL 安全协议

1、SSL\TSL 概述

安全套接层SSL(Secure Sockets Layer )或传输层安全TLS(Transport Layer Security)是为网络通信提供安全及数据完整性的一种安全协议,用于在传输层对此次网络连接采用对称算法或非对称算法进行加密,保证信息安全,SSL协议位于TCP/IP协议与各种应用层协议之间,为数据通讯提供安全支持,SSL协议可分为两层:

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

采用HTTPS进行的网络通信大致流程:Client在向服务器发送数据前,先使用与服务器协商好的加密算法进行加密处理,然后才向服务器传输,Server端在接到数据之后,也会先进行解密再作响应,则如下图所示:
在这里插入图片描述
SSL最初为网景Netscape所研发,用以保障在Internet上数据传输之安全,利用数据加密(Encryption)技术,可确保数据在网络上的传输过程中不会被截取及窃听。

2、SSL 协议提供的服务

  • 认证用户和服务器,确保数据发送到正确的客户机和服务器
  • 加密数据以防止数据中途被窃取
  • 维护数据的完整性,确保数据在传输过程中不被改变。

3、SSL协议的工作流程

3.1、服务器认证阶段:

  • 客户端向服务器发送一个开始信息“Hello”以便开始一个新的会话连接;
  • 服务器根据客户的信息确定是否需要生成新的主密钥,如需要则服务器在响应客户的“Hello”信息时将包含生成主密钥所需的信息;
  • 客户根据收到的服务器响应信息,产生一个主密钥,并用服务器的公开密钥加密后传给服务器;
  • 服务器恢复该主密钥,并返回给客户一个用主密钥认证的信息,以此让客户认证服务器。

3.2、用户认证阶段

在此之前,服务器已经通过了客户认证,这一阶段主要完成对客户的认证。经认证的服务器发送一个提问给客户,客户则返回(数字)签名后的提问和其公开密钥,从而向服务器提供认证。从SSL 协议所提供的服务及其工作流程可以看出,SSL协议运行的基础是商家对消费者信息保密的承诺,这就有利于商家而不利于消费者。在电子商务初级阶段,由于运作电子商务的企业大多是信誉较高的大公司,因此这问题还没有充分暴露出来。但随着电子商务的发展,各中小型公司也参与进来,这样在电子支付过程中的单一认证问题就越来越突出。虽然在SSL3.0中通过数字签名和数字证书可实现浏览器和Web服务器双方的身份验证,但是SSL协议仍存在一些问题,比如,只能提供交易中客户与服务器间的双方认证,在涉及多方的电子交易中,SSL协议并不能协调各方间的安全传输和信任关系。

3、握手流程

SSL 协议既用到了公钥加密技术又用到了对称加密技术,对称加密技术虽然比公钥加密技术的速度快,可是公钥加密技术提供了更好的身份认证技术。SSL 的握手协议非常有效的让客户和服务器之间完成相互之间的身份认证,其主要过程如下:

  • 客户端向服务器传送客户端SSL 协议的版本号,支持的加密算法种类,产生的随机数

  • 服务器向客户端传送SSL 协议的版本号、选择的加密算法,随机数以及其他相关信息,同时服务器还将向客户端传送自己的证书。

  • 客户端利用服务器传过来的信息验证服务器的合法性,服务器的合法性(包括证书是否过期,发行服务器证书的CA 是否可靠,发行者证书的公钥能否正确解开服务器证书的“发行者的数字签名”,服务器证书上的域名是否和服务器的实际域名相匹配)。如果合法性验证没有通过,通讯将断开;如果合法性验证通过,将继续进行第四步。

  • 客户端随机产生一个用于加密通讯的“对称密码”并用服务器的公钥(服务器的公钥从第二步中的服务器的证书中获得)对其加密再将加密后的“预主密码”传给服务器

  • 如果服务器要求客户端的进行身份认证(在握手过程中为可选),用户可以建立一个随机数然后对其进行数据签名,将这个含有签名的随机数和客户自己的证书以及加密过的“预主密码”一起传给服务器

  • 如果服务器要求客户的身份认证,服务器必须检验客户证书和签名随机数的合法性包括(客户的证书使用日期是否有效,为客户提供证书的CA 是否可靠,发行CA 的公钥能否正确解开客户证书的发行CA 的数字签名,检查客户的证书是否在证书废止列表CRL中)检验如果没有通过,通讯立刻中断;如果验证通过,服务器将用自己的私钥解开加密的“预主密码”,然后执行一系列步骤来产生主通讯密码(客户端也将通过同样的方法产生相同的主通讯密码)。

  • 服务器和客户端用相同的主密码即“通话密码”,一个对称密钥用于SSL 协议的安全数据通讯的加解密通讯。同时在SSL 通讯过程中还要完成数据通讯的完整性,防止数据通讯中的任何变化。

  • 客户端向服务器端发出信息,指明后面的数据通讯将使用的第七步中的主密码为对称密钥,同时通知服务器客户端的握手过程结束

  • 服务器向客户端发出信息,指明后面的数据通讯将使用的第七步中的主密码为对称密钥,同时通知客户端服务器端的握手过程结束

  • SSL 的握手部分结束,SSL 安全通道的数据通讯开始,客户和服务器开始使用相同的对称密钥进行数据通讯,同时进行通讯完整性的检验

以下是我抓一个api的握手大致流程:
在这里插入图片描述
完整的SSL一次握手抓包如下:
在这里插入图片描述

4、CA 数字证书

采用HTTPS的服务器必须从CA (Certificate Authority)申请一个用于证明服务器用途类型的证书(相当于是服务器的"身份证"一样,后缀通常是.crt 或者.cer),客户端进行通信之前需要首先确认是否信任这个"身份证"对应的服务器,该证书只有用于对应的服务器的时候,客户端才信任此主机,只有彼此信任才能进行通信。

三、HTTP 和HTTPS小结

1、HTTP和HTTPS的异同

  • HTTPS协议需要到CA机构申请证书。

  • HTTPS是超文本传输协议,信息是明文传输,HTTPS则是具有安全性的SSL加密传输协议。

  • HTTP和HTTPS通信流程及使用的端口也不一样,前者是80,后者是443。

  • HTTP的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比HTTP协议安全。

2、HTTPS的优点和不足

虽然掌握根证书的机构、掌握加密算法的组织都可以进行中间人形式的攻击,但HTTPS仍是现行架构下最安全的解决方案,主要有以下几个优点:

  • 使用HTTPS协议可认证用户和服务器,确保数据安全发送到正确的客户机和服务器

  • HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比HTTP协议安全,可防止数据在传输过程中不被窃取、改变,确保数据的完整性。

  • HTTPS它大幅增加了中间人攻击的成本,客户端与服务器的加密连接中能保护此次传入的数据信息部分,只有客户端到服务器之间的连接是安全的,但并不能绝对确保服务器自己是安全的。

虽然说HTTPS有很大的优势,但也还是存在不足之处:

  • HTTPS协议握手阶段(除了TCP握手还需要进行SSL握手)比较费时,会使页面的加载时间延长,增加耗电;

  • HTTPS连接缓存不如HTTP高效,会增加数据开销和功耗,甚至已有的安全措施也会因此而受到影响;

  • SSL证书需要钱,功能越强大的证书费用越高,个人网站、小网站没有必要一般不会用。

  • SSL证书通常需要绑定IP,不能在同一IP上绑定多个域名,IPv4资源不可能支撑这个消耗。

  • HTTPS协议的加密范围也比较有限,在黑客攻击、拒绝服务攻击、服务器劫持等方面几乎起不到什么作用。最关键的,SSL证书的信用链体系并不安全,特别是在某些国家可以控制CA根证书的情况下,中间人攻击一样可行。

四、Socket实现HTTPS通信

HTTPS通信本质也是HTTP 通信,所以绝大部分的核心功能(包括拼接请求报文、发送报文、解析报文)的实现和流程都是一样的,由于篇幅问题不再列出,请参见上一篇 网络编程——使用原生Socket 完成HTTP简单通信,最大的区别在于HTTPS通信是需要依赖CA证书的,所以呢在创建Socket 建立连接这一部分有所不同,需要给“Socket 添加上对应的信任证书“”(这说话或许不太严谨)。

1、仅信任本机预置的所有CA证书

如果你不需要支持自定义的证书或者是非权威CA 机构颁发的数字证书,可以使用这种方式。

    @NonNull
    private Socket createHttpsConnection(HttpUrl url) throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException {
        //获取SSLContext 对象,将来用于获取创建支持SSL 协议的Socket对象
        SSLContext sslContext=SSLContext.getInstance("TLS");
        //服务器要使用HTTPS 必须要有CA证书
        //数字证书,相当于是服务器的"身份证"一样,客户端进行通信之前需要首先确认是否信任这个"身份证"对应的服务器,后缀通常是.crt 或者.cer
        TrustManagerFactory trustManagerFactory=TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        //传入null,表示信任本机(此处我是在Java 本地单元测试则代表PC 机本身,如果在手机中则代表手机中的所有CA证书)系统下预置所有的CA证书(权威CA 机构发布的证书)
        trustManagerFactory.init((KeyStore)null);
        TrustManager[] trustManagers=trustManagerFactory.getTrustManagers();
        //通过信任证书管理器对SSLContext对象进行初始化配置,之后才能用于获取SSLSocketFactory
        sslContext.init(null,trustManagers,null);
        SSLSocketFactory socketFactory=sslContext.getSocketFactory();
        //创建SSLSocket 默认端口443
        Socket socket=socketFactory.createSocket();
        socket.connect(new InetSocketAddress(url.getHost(),url.getPort()));
        return socket;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

2、信任所有的CA证书,未校验证书的合法性

    /**
     * 信任所有的证书,包括自定义的还有预置,未校验证书的合法性,容易受到中间人攻击
     */
    @NonNull
    private Socket createHttpsConnection(HttpUrl url) throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException {
        //获取SSLContext 对象,将来用于获取创建支持SSL 协议的Socket对象
        SSLContext sslContext=SSLContext.getInstance("TLS");
        //这会信任所有的证书,包括自定义的还有预置,未校验证书的合法性,容易受到中间人攻击
        sslContext.init(null,new TrustManager[]{
                new X509TrustManager() {
                    @Override
                    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                    }

                    @Override
                    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                    }

                    @Override
                    public X509Certificate[] getAcceptedIssuers() {
                        return new X509Certificate[0];
                    }
                }
        },null);
        SSLSocketFactory socketFactory=sslContext.getSocketFactory();
        创建SSLSocket 默认端口443
        Socket socket=socketFactory.createSocket();
        socket.connect(new InetSocketAddress(url.getHost(),url.getPort()));
        return socket;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

3、仅信任设置的自定义证书

    /**
     * 仅使用自定义的证书进行Https 通信,系统的预置证书不信任
     */
    @NonNull
    private Socket createHttpsConnection3(HttpUrl url) throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException, CertificateException {
        //使用Java提供的KeyStore 证书库api 对自定义证书进行设置
        KeyStore keyStore=KeyStore.getInstance(KeyStore.getDefaultType());
        CertificateFactory certificateFactory=CertificateFactory.getInstance("X.509");
        //通过输入流生成对应的CA证书
        Certificate certificate=certificateFactory.generateCertificate(new FileInputStream("c:\\xxx\\Xxx"+"\\crazy.cer"));
        keyStore.load(null);
        //第一个参数加密的参数
        keyStore.setCertificateEntry("cmo",certificate);
        //数字证书,相当于是服务器的"身份证"一样,客户端进行通信之前需要首先确认是否信任这个"身份证"对应的服务器,后缀通常是.crt 或者.cer
        TrustManagerFactory trustManagerFactory=TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        //传入的自定义证书,在进行通信时会自动地进行校验
        trustManagerFactory.init(keyStore);
        TrustManager[] trustManagers=trustManagerFactory.getTrustManagers();
        //获取SSLContext 对象,将来用于获取创建支持SSL 协议的Socket对象
        SSLContext sslContext=SSLContext.getInstance("TLS");
        //通过信任证书管理器对SSLContext对象进行初始化配置,之后才能用于获取SSLSocketFactory
        sslContext.init(null,trustManagers,null);
        SSLSocketFactory socketFactory=sslContext.getSocketFactory();
        创建SSLSocket 默认端口443
        Socket socket=socketFactory.createSocket();
        socket.connect(new InetSocketAddress(url.getHost(),url.getPort()));
        return socket;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

4、既信任设置的证书也支持系统预置的证书

想要同时支持自定义证书和系统预置的证书需要自己实现校验流程,实现起来也很简单——实现X509TrustManager 接口并重写对应的方法即可。

	
	 public class MyX509TrustManager implements X509TrustManager {
        X509Certificate mCertificate;
        public MyX509TrustManager(X509Certificate certificate) {
            this.mCertificate=certificate;
        }

        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {

        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            try{
                //先去校验系统预置的CA证书
                TrustManagerFactory factory=TrustManagerFactory.getInstance("X.509");
                factory.init((KeyStore)null);
                X509TrustManager trustManager= (X509TrustManager) factory.getTrustManagers()[0];
                //若没有报异常则说明访问的服务器拥有的预置的CA机构证书
                trustManager.checkServerTrusted(chain,authType);
            }catch (Exception e){
                try{
                    //只校验了证书是否与服务器的公钥一致
                    chain[0].verify(mCertificate.getPublicKey());
                    System.out.println("校验成功");
                } catch (NoSuchAlgorithmException e1) {
                    e1.printStackTrace();
                } catch (SignatureException e1) {
                    e1.printStackTrace();
                } catch (NoSuchProviderException e1) {
                    e1.printStackTrace();
                } catch (InvalidKeyException e1) {
                    e1.printStackTrace();
                }
            }
        }

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

    /**
     * 不仅使用自定义的证书进行Https 通信,还支持系统的预置证书
     */
    @NonNull
    private Socket createHttpsConnection(HttpUrl url) throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException, CertificateException {
        //使用Java提供的KeyStore 证书库api 对自定义证书进行设置
        KeyStore keyStore=KeyStore.getInstance(KeyStore.getDefaultType());
        CertificateFactory certificateFactory=CertificateFactory.getInstance("X.509");
        //通过输入流生成对应的CA证书
        Certificate certificate=certificateFactory.generateCertificate(new FileInputStream("c:\\xxx\\Xxx"+"\\crazy.cer"));
        keyStore.load(null);
        keyStore.setCertificateEntry("cmo",certificate);

        //自定义证书认证流程得到信任管理器
        X509TrustManager trustManager=new MyX509TrustManager((X509Certificate)certificate);
        //获取SSLContext 对象,将来用于获取创建支持SSL 协议的Socket对象
        SSLContext sslContext=SSLContext.getInstance("TLS");
        //通过信任证书管理器对SSLContext对象进行初始化配置,之后才能用于获取SSLSocketFactory
        //TODO init方法第一个参数是自己的证书(对应服务端中的第二个参数),第二个是信任的服务器证书(对应服务端中的第一个参数),双向认证很简单,只需要在init的时候传入客户端自己的证书,然后服务端再需要把客户端的参数作为第二个参数传入而把服务端自己的参数作为第一个参数传入即可
        sslContext.init(null,new TrustManager[]{trustManager},null);
        SSLSocketFactory socketFactory=sslContext.getSocketFactory();
        创建SSLSocket 默认端口443
        Socket socket=socketFactory.createSocket();
        socket.connect(new InetSocketAddress(url.getHost(),url.getPort()));
        return socket;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70

测试代码:

void httpsTest() throws MalformedURLException {
        //响应体使用Content-Length
        final HttpUrl url=new HttpUrl("https://restapi.amap.com/v3/weather/weatherInfo?city=上海&key=ccbf1d251595efa936df0ba784346902");
        System.out.println("host:"+url.host);
        System.out.println("protocol:"+url.getProtocol());
        System.out.println("port:"+url.getPort());
        System.out.println("path:"+url.getPath());

        StringBuffer buffer=createRequestPacket(url);
        //Socket socket=new Socket();
        try {
            //通过端口号与指定Host的主机建立了连接
            //socket.connect(new InetSocketAddress(url.getHost(),url.getPort()),5000);
            Socket socket = createHttpsConnection(url);

            //建立了Sockect 连接之后就可以通过对应的方法获取输入输出流,其中输入流是用于读取服务器的响应数据;而输出流则是客服端发送数据给服务器的
            final OutputStream outputStream=socket.getOutputStream();
            final InputStream inputStream=socket.getInputStream();
            System.out.println("开始发送报文... \n"+buffer);
            sendRequest(buffer, outputStream);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    HttpCodec httpCodec=new HttpCodec();
                    try{
                        //解析响应行
                        String responseLine=httpCodec.readLine(inputStream);
                        System.out.println("响应行:"+responseLine);
                        System.out.println("响应头:");
                        //解析响应头
                        Map<String,String> headers=httpCodec.readHeaders(inputStream);
                        for (Map.Entry<String,String> entry :headers.entrySet()){
                            System.out.println(entry.getKey() +":"+entry.getValue());
                        }
                        //解析Content-Length响应体
                        if(headers.containsKey("Content-Length")){
                            int length=Integer.valueOf(headers.get("Content-Length"));
                            byte[] bytes=httpCodec.readBytes(inputStream,length);
                            System.out.println("\n响应体:"+new String(bytes));
                        }else{
                            //分块编码
                            String response=httpCodec.readChunked(inputStream);
                            System.out.println("分块响应体:"+response);
                        }
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }).start();

            while (true){
                Thread.sleep(1000*10);
            }
        } catch (IOException | InterruptedException | NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) {
            e.printStackTrace();
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57

结果:
在这里插入图片描述

源码传送门

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/208167
推荐阅读
相关标签
  

闽ICP备14008679号