2018年3月20日 星期二

如何在JDK1.5中支援TLSv1.2

使用Java 傳 HTTPS request 時,如果接收端的server (例如別家公司開放的web api)是只使用 TLSv1.2 時,可能會因為舊版的 jdk 沒有支援,而造成連線錯誤。

如果是使用 JDK1.8,其支持 TLSv1.2,並且且預設就使用 TLSv1.2 來進行 HTTPS 的連線,
寫法可以參考之前寫的這篇文章 "以Java 由Url下載圖片" 裡面的 "getHttpURLConnectionFromHttps()" 方法來進行 HTTPS 的請求。

但是例如如果為較舊的 JDK1.5,則沒有支持 TLSv1.2,此時還用 JDK1.8的寫法去請求 TLSv1.2的server時,就會產生錯誤。

在這篇文章中,我紀錄了我找到的方法,如何讓 JDK1.5 也能進行對 TLSv1.2 server的請求。

=================================

我使用的JDK為 jdk1.5.0_22
測試的網站為 https://fancyssl.hboeck.de/
此網站聲稱只為TLSv1.2的request開放
可以使用線上SSL檢測工具來查看此網站的SSL相關設定
https://www.ssllabs.com/ssltest/analyze.html?d=fancyssl.hboeck.de

需要的 jar

  1. Bouncy Castle 的 Library : 
    1. Provider 
    2. DTLS/TLS API/JSSE Provider
  2. 解開Java密鑰長度限制的
    "Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files"
    1. JDK 1.5 可到這裡下載 "Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files 5.0"。


步驟:

  1. 導入Bouncy Castle的 jar
    Bouncy Castle 是一個提供許多密碼演法等的 Java Library,而 TLSv1.2 跟 舊版的差別其中就有密碼演算等不同,其中更深的原理我沒有鑽研,在此只展示找到的可行解決方案。

    首先先到 Bouncy Castle官網 下載 Provider 及 DTLS/TLS API/JSSE Provider 的 jar 檔,我這邊選擇的為 bcprov-jdk15on-159.jarbctls-jdk15on-159.jar。 請下載並加到 Java 專案的 Library中。
  2. 測試程式進對指定的url進行TLSv1.2 HTTPS 請求,並將回應印出來,在這裡會將整個網頁的源始碼印出來。

    測試程式碼如下:
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.net.HttpURLConnection;
    import java.net.URL;
    import java.security.SecureRandom;
    import java.security.Security;
    import java.security.cert.CertificateException;
    import java.security.cert.X509Certificate;
    
    import javax.net.ssl.HostnameVerifier;
    import javax.net.ssl.HttpsURLConnection;
    import javax.net.ssl.SSLContext;
    import javax.net.ssl.SSLSession;
    import javax.net.ssl.TrustManager;
    import javax.net.ssl.X509TrustManager;
    
    import org.bouncycastle.jce.provider.BouncyCastleProvider;
    import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
    
    public class HttpsClientRequestTest {
    
        public static void main(String[] args)
                throws Exception
            {
                Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
                Security.insertProviderAt(new BouncyCastleProvider(), 1);
    
                Security.removeProvider(BouncyCastleJsseProvider.PROVIDER_NAME);
                Security.insertProviderAt(new BouncyCastleJsseProvider(), 2);
    
                /*
                 * TEST CODE ONLY. If writing your own code based on this test case, you should configure
                 * your trust manager(s) using a proper TrustManagerFactory, or else the server will be
                 * completely unauthenticated.
                 */
                TrustManager tm = new X509TrustManager()
                {
                    public X509Certificate[] getAcceptedIssuers()
                    {
                        return new X509Certificate[0];
                    }
    
                    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException
                    {
                     //do nothing
                    }
    
                    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException
                    {
                     //do nothing
                    }
                };
                
                HostnameVerifier customHostnameVerifier = new HostnameVerifier() {
                     public boolean verify(String arg0, SSLSession arg1) {        
                          return true;
                     }
                };
    
                SSLContext sslContext = SSLContext.getInstance("TLSv1.2", BouncyCastleJsseProvider.PROVIDER_NAME);
                sslContext.init(null, new TrustManager[]{ tm }, new SecureRandom());
                HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
                HttpsURLConnection.setDefaultHostnameVerifier(customHostnameVerifier);
                
                HttpURLConnection httpUrlConnection = (HttpURLConnection) (new URL("https://fancynossl.hboeck.de")).openConnection();
                
                httpUrlConnection.setRequestProperty("User-Agent", "Mozilla/5.0 (Linux; Android 4.2.1; Nexus 7 Build/JOP40D) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166  Safari/535.19");
                httpUrlConnection.connect();
    
                InputStream is = null;
         if (httpUrlConnection.getResponseCode() >= 400) {  
             is = httpUrlConnection.getErrorStream();  
         } else {  
             is = httpUrlConnection.getInputStream();  
         }
          
                BufferedReader responseBufferedReader = new BufferedReader((new InputStreamReader(is)));
                StringBuffer responseTextStringBuffer = new StringBuffer();
                String tempString = null;
                while((tempString = responseBufferedReader.readLine()) != null) {
                    responseTextStringBuffer.append(tempString + "\n");
                }
                String responseText = responseTextStringBuffer.toString();
                httpUrlConnection.disconnect();
          
                System.out.println(responseText);
            }
    }
    
  3. 因為還沒有開放 Java 的密鑰長度限制,所以應該會出現以下錯誤java.security.InvalidKeyException: Illegal key sizeException
    此時將下載的 "Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files 5.0" 裡面的 "local_policy.jar" 和 "US_export_policy.jar" 覆蓋到JAVA_HOME\jre\lib\security\ 之下的 "local_policy.jar" 和 "US_export_policy.jar" ,再執行一次,應該就會成功了。
    下載頁面如圖所示:
    程式成功時輸出如圖,可以看到網頁的html源始碼被印了出來 (Eclipse):



參考資料:
  1. java.lang.IllegalArgumentException: TLSv1.2 on JRE 1.5
  2. The Legion of the Bouncy Castle
  3. bcgit/bc-java
  4. 解决java.io.IOException: HTTPS hostname wrong: should be
  5. AES加密时抛出java.security.InvalidKeyException: Illegal key size or default parameters

沒有留言 :

張貼留言