https總結(jié)及在java中的應(yīng)用

  • 概念

    http協(xié)議傳輸?shù)臄?shù)據(jù)都是未加密的,也就是明文的,因此使用http協(xié)議傳輸隱私信息非常不安全,為了保證這些隱私數(shù)據(jù)能加密傳輸,于是網(wǎng)景公司設(shè)計(jì)了ssl(Secure Sockets Layer)協(xié)議用于對http協(xié)議傳輸?shù)臄?shù)據(jù)進(jìn)行加密,從而就誕生了https。簡單來說,https協(xié)議是由http協(xié)議構(gòu)建的可進(jìn)行加密傳輸、身份認(rèn)證的網(wǎng)絡(luò)協(xié)議,要比http協(xié)議安全。詳解:https://www.cnblogs.com/wqhwe/p/5407468.html

    https是采用非對稱加密算法+對稱加密算法來保證數(shù)據(jù)的安全(c 客戶端 | s 服務(wù)端)。首先由c發(fā)起https請求s,s收到請求后返回證書(公鑰),然后c收到公鑰后隨機(jī)生成一個稱加密的密鑰,并使用s返回的公鑰加密生成的密鑰,發(fā)送至s,s使用自己的私鑰解密得到c的密鑰,后續(xù)都是用c生成的密鑰進(jìn)行通信

    • 非對稱加密兩種用法:

      • 第一種用法:公鑰加密,私鑰解密。用于加解密(https中c向s發(fā)送隨即密鑰時使用此方式)
      • 第二種用法:私鑰簽名,公鑰驗(yàn)簽。用于簽名
        詳解:https://www.kuacg.com/22672.html
  • HTTPS中間人攻擊

    https的加密傳輸彌補(bǔ)了http明文傳輸?shù)陌踩[患,保證了在傳輸過程中信息的安全,但是https本身依舊存在一個隱患,即中間人攻擊。過程大致是,當(dāng)我們向https服務(wù)器發(fā)送請求時,請求被劫持(DNS劫持,或者信任不安全證書),然后偽裝成https服務(wù)器,接收c的請求,并返回c公鑰,然后建立與c端的請求,得到c的所有請求信息,并構(gòu)造一個c1偽裝為c與真實(shí)的s建立通信,得到所有的返回信息,這個過程信息就會被泄露或篡改。因?yàn)閏無法知道自己訪問到的是真正的s,還是偽裝成的s,所以https中間人攻擊依然使得https存在一定的安全風(fēng)險。而解決這一風(fēng)險的方式就是SSL證書+CA機(jī)構(gòu)

  • CA證書與keytool自制證書

    CA證書為CA機(jī)構(gòu)頒發(fā)的SSL證書,keytool自制證書為使用java證書管理工具生成的證書。瀏覽器接受服務(wù)返回的證書后查找操作系統(tǒng)中已內(nèi)置的受信任的證書發(fā)布機(jī)構(gòu)CA,與服務(wù)器的證書中的CA匹配,查看是否為合法機(jī)構(gòu)頒發(fā),如果使用keytool自制證書則有可能被攔截,如果在keytool中將機(jī)構(gòu)寫成受信任的機(jī)構(gòu),瀏覽器從操作系統(tǒng)中獲取CA機(jī)構(gòu)的公鑰,解密證書中的簽名,同時使用相同的hash算法計(jì)算出服務(wù)器的證書的hash值與簽名做對比,對比結(jié)果即代表證書是否受信任

  • HostnameVerifier

    在使用httpclient進(jìn)行https請求時,為防止https中間人攻擊http默認(rèn)會使用HostnameVerifer對請求證書中的主機(jī)名與配置的主機(jī)名或請求的主機(jī)名進(jìn)行匹配檢驗(yàn)以判斷證書是否為偽裝

  • SSL、TLS、SSH協(xié)議

    SSH 應(yīng)用層的通信加密協(xié)議,往往用于遠(yuǎn)程登錄的會話;TLS(傳輸層安全協(xié)議)時SSL(安全套接層)標(biāo)準(zhǔn)化的結(jié)果

  • 配置

    • Spring Boot 配置
      首先生成證書:

      keytool -genkeypair -alias eairlv -keyalg RSA -keystore C:\eairlv.key
      

      然后配置:

      server:
        port: 8083
        ssl:
          key-store: eairlv.key
          key-store-type: JKS
          key-alias: eairlv
          key-store-password: eairlv.com
      http:
        port: 8084
      
    • 注入Bean:

      /**
       * server.port作為https端口
       * 如果不配置http端口的bean則默認(rèn)只開啟https服務(wù)
       */
      @Value("${server.port}")
      private Integer httpsPort;
      
      @Value("${http.port}")
      private Integer httpPort;
      
      @Bean
      public UndertowEmbeddedServletContainerFactory embeddedServletContainerFactory() {
          UndertowEmbeddedServletContainerFactory undertow = new UndertowEmbeddedServletContainerFactory();
          undertow.addBuilderCustomizers((Undertow.Builder builder) ->
              builder.addHttpListener(httpPort, "0.0.0.0")
          );
          log.info("undertow http port: {}, https port: {} ", httpPort, httpsPort);
          return undertow;
      }
      
      //spring boot 2.0 UndertowEmbeddedServletContainerFactory 被剔除
      //spring boot 2.0 正確配置:
      
      /**
       * server.port作為https端口
       */
      @Value("${server.port}")
      private Integer httpsPort;
      
      @Value("${http.port}")
      private Integer httpPort;
      
      @Bean
      public UndertowServletWebServerFactory embeddedServletContainerFactory() {
          UndertowServletWebServerFactory undertow = new UndertowServletWebServerFactory();
          undertow.addBuilderCustomizers((UndertowBuilderCustomizer) builder ->
              builder.addHttpListener(httpPort, "0.0.0.0")
          );
          return undertow;
      }
      
      //增強(qiáng)版:
      @Bean
      public UndertowServletWebServerFactory embeddedServletContainerFactory() {
          UndertowServletWebServerFactory undertow = new UndertowServletWebServerFactory();
          undertow.addBuilderCustomizers((UndertowBuilderCustomizer) builder -> {
                          // IP配置'0.0.0.0'任意方式訪問
                  builder.addHttpListener(httpPort, "0.0.0.0");
                  // 開啟HTTP2
                  builder.setServerOption(UndertowOptions.ENABLE_HTTP2, true);
          });
          undertow.addDeploymentInfoCustomizers(deploymentInfo -> {
              // 開啟HTTP自動跳轉(zhuǎn)至HTTPS
              deploymentInfo.addSecurityConstraint(new SecurityConstraint()
                      .addWebResourceCollection(new WebResourceCollection().addUrlPattern("/*"))
                      .setTransportGuaranteeType(TransportGuaranteeType.CONFIDENTIAL)
                      .setEmptyRoleSemantic(SecurityInfo.EmptyRoleSemantic.PERMIT))
                      .setConfidentialPortManager(exchange -> httpsPort);
          });
          return undertow;
      }
      
      //undertow配置http跳轉(zhuǎn)https后post接口400。
      //https://stackoverflow.com/questions/37464220/springboot-undertow-redirect-post-from-http-to-https
      
    • RestTemplate配置

      @Bean
      public RestTemplate restTemplate() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
      TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;
      
      SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom()
              .loadTrustMaterial(null, acceptingTrustStrategy)
              .build();
      
      SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
      
      CloseableHttpClient httpClient = HttpClients.custom()
              .setSSLContext(sslContext)
              .setSSLSocketFactory(csf)
              .build();
      
      HttpComponentsClientHttpRequestFactory requestFactory =
              new HttpComponentsClientHttpRequestFactory();
      
      requestFactory.setHttpClient(httpClient);
      return new RestTemplate(requestFactory);
      }
              
      //不可行方案:
      SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);
      CloseableHttpClient httpClient = HttpClients.custom()
              .setSSLSocketFactory(csf)
              .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
              .build();
      
      //可行方案1:
      SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
      CloseableHttpClient httpClient = HttpClients.custom()
              .setSSLSocketFactory(csf)
              .build();
      //可行方案2:
      CloseableHttpClient httpClient = HttpClients.custom()
              .setSSLContext(sslContext)
              .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
              .build();
      
      
      //  HttpClientBuilder 
      //  933 public CloseableHttpClient build() {...} 
      //  914 if (sslSocketFactoryCopy == null) {...} 
      //  如果設(shè)置了SSLConnectionSocketFactory,CloseableHttpClient的HostnameVerifier就會使用SSLConnectionSocketFactory中的SSLHostnameVerifier,即出現(xiàn)不可行情況
      
    • 血案

      • httpclient訪問https血案:Certificate for <localhost> doesn't match any of the subject alternative names: []
      • httpclient默認(rèn)會通過HostnameVerifier驗(yàn)證訪問主機(jī),而我們想要訪問https服務(wù)就需要對HostnameVerifier配置
      • HostnameVerifier 擁有一個DefaultHostnameVerifier,它對訪問的主機(jī)名與證書中的主機(jī)名匹配校驗(yàn),如:https://api.eairlv.com:8443/v2/api可訪問(IE直接訪問證書合法),而訪問https://115.28.100.23:8443/v2/api則訪問不通過(IE直接訪問證書不合法)
    • 信任證書

      • 信任服務(wù)器證書,如果不配置證書信任,則httpclient默認(rèn)會對證書進(jìn)行校驗(yàn),keytool自制的證書則無法通過校驗(yàn)

      • https://api.eairlv.com:8443/v2/api,IE信任而httpclient默認(rèn)不信任,猜測為證書類型為DV,即信任等級較低。DV < OV < EV。DV證書僅對傳輸信息進(jìn)行加密,并不能真正證明網(wǎng)站的真實(shí)身份,只是提高了偽造身份的復(fù)雜度,依然存在https中間人攻擊的可能性(中間人擁有可信任機(jī)構(gòu)頒發(fā)的證書,且域名一致)

        TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;
        SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom()
              .loadTrustMaterial(null, acceptingTrustStrategy)
              .build();
        或:
        SSLContext context = SSLContext.getInstance("TLS");
        context.init(null, new TrustManager[] { new SkipX509TrustManager() },
              new SecureRandom());
        private static class SkipX509TrustManager implements X509TrustManager {
            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }
        
            @Override
            public void checkClientTrusted(X509Certificate[] chain, String authType) {
            }
        
            @Override
            public void checkServerTrusted(X509Certificate[] chain, String authType) {
            }
        
        }
        
    • 信任主機(jī)名(配置的主機(jī)名或請求的主機(jī)名與證書中主機(jī)名的匹配)

      重寫HostnameVerifier類中的verify方法,可自行配置主機(jī)名的匹配規(guī)則,也可使用DefaultHostnameVerifier匹配請求的主機(jī)名與證書中的主機(jī)名,如果信任所有主機(jī)則只需使verify方法始終返回true

      SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
      // 據(jù)說這種方式在google play上會報不合法,需要簡單的在verify中添加邏輯
      SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext, (s, sslSession) -> true);
      
  • 總結(jié)

    要想防止中間人攻擊證書的信任與主機(jī)名的信任需要同時開啟,我之前一直也覺得只需要信任證書就行了,為什么還需要信任主機(jī)名?而如果中間人偽裝的服務(wù)器也擁有CA信任證書,主機(jī)名則能夠幫助客戶端區(qū)分證書是否為真正受信任的CA證書。同一個域名可能對應(yīng)多個可信任證書,即依然存在安全隱患,而如果希望安全性更高的話,則可采取客戶端預(yù)埋證書的方式鎖死證書,當(dāng)客戶端證書與服務(wù)端證書完全一致才能進(jìn)行通信,對于證書過期問題則需要要求用戶在官網(wǎng)(軟件更新本身也會有可能被中間調(diào)包)下載證書來解決。在一般的Android程序中,連接https請求時為保證安全性可自定義規(guī)則,也可使用默認(rèn)(默認(rèn)更嚴(yán)格,自定義規(guī)則用于子域名的情況,CA通配符證書較貴),首先檢驗(yàn)證書是否被信任與主機(jī)名是否被信任,而在瀏覽器中則使用默認(rèn)的匹配規(guī)則以達(dá)到防止https中間人的攻擊

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容