【问题标题】:Java and HTTPS url connection without downloading certificate无需下载证书的 Java 和 HTTPS url 连接
【发布时间】:2012-10-12 22:17:45
【问题描述】:

此代码连接到 HTTPS 站点,我假设我没有验证证书。但是为什么我不必为该站点在本地安装证书呢?我不应该在本地安装证书并为该程序加载它还是在幕后下载?客户端到远程站点之间的流量是否仍在加密传输中?

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.net.URLConnection;
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;

public class TestSSL {

    public static void main(String[] args) throws Exception {
        // Create a trust manager that does not validate certificate chains
        TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                return null;
            }
            public void checkClientTrusted(X509Certificate[] certs, String authType) {
            }
            public void checkServerTrusted(X509Certificate[] certs, String authType) {
            }
        } };
        // Install the all-trusting trust manager
        final SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, trustAllCerts, new java.security.SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
        // Create all-trusting host name verifier
        HostnameVerifier allHostsValid = new HostnameVerifier() {
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        };

        // Install the all-trusting host verifier
        HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);

        URL url = new URL("https://www.google.com");
        URLConnection con = url.openConnection();
        final Reader reader = new InputStreamReader(con.getInputStream());
        final BufferedReader br = new BufferedReader(reader);        
        String line = "";
        while ((line = br.readLine()) != null) {
            System.out.println(line);
        }        
        br.close();
    } // End of main 
} // End of the class //

【问题讨论】:

  • 这种完全不安全的TrustManager 实现甚至不符合它自己的规范。检查 Javadoc。
  • @EliteOctagon 不要在生产代码中使用它!这个 TrustManager 会简单地忽略无效的证书并使 MITM 攻击变得非常容易。
  • 谢谢@binwiederhier,我会记住这一点。这甚至适用于没有人会看到的 URL,因为它们是只有我知道的?你能详细说明一下这种情况下的中间人攻击吗?
  • @EliteOctagon 这取决于数据包的传输位置,而不是 URL 是否公开。关于 MITM,我写了两篇博文:blog.philippheckel.com/2013/07/01/…blog.philippheckel.com/2013/08/04/…
  • 在帖子中,您必须安装受信任的 CA 才能使其工作。使用上面的信任管理器,这是没有必要的;所以这个信任经理真的很危险!

标签: java ssl https


【解决方案1】:

如果您使用任何支付网关点击任何 url 只是为了发送消息,那么我使用 webview 并遵循它: How can load https url without use of ssl in android webview

并在可见性消失的情况下在您的活动中创建网络视图。你需要做的:只需加载那个 webview .. 像这样:

 webViewForSms.setWebViewClient(new SSLTolerentWebViewClient());
                webViewForSms.loadUrl(" https://bulksms.com/" +
                        "?username=test&password=test@123&messageType=text&mobile="+
                        mobileEditText.getText().toString()+"&senderId=ATZEHC&message=Your%20OTP%20for%20A2Z%20registration%20is%20124");

简单。

您将从以下链接获得:SSLTolerentWebViewClient: How can load https url without use of ssl in android webview

【讨论】:

    【解决方案2】:

    按照此处的建议使用最新的 X509ExtendedTrustManager 而不是 X509Certificate:java.security.cert.CertificateException: Certificates does not conform to algorithm constraints

        package javaapplication8;
    
        import java.io.InputStream;
        import java.net.Socket;
        import java.net.URL;
        import java.net.URLConnection;
        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.SSLEngine;
        import javax.net.ssl.SSLSession;
        import javax.net.ssl.TrustManager;
        import javax.net.ssl.X509ExtendedTrustManager;
    
        /**
         *
         * @author hoshantm
         */
        public class JavaApplication8 {
    
            /**
             * @param args the command line arguments
             * @throws java.lang.Exception
             */
            public static void main(String[] args) throws Exception {
                /*
                 *  fix for
                 *    Exception in thread "main" javax.net.ssl.SSLHandshakeException:
                 *       sun.security.validator.ValidatorException:
                 *           PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException:
                 *               unable to find valid certification path to requested target
                 */
                TrustManager[] trustAllCerts = new TrustManager[]{
                    new X509ExtendedTrustManager() {
                        @Override
                        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                            return null;
                        }
    
                        @Override
                        public void checkClientTrusted(X509Certificate[] certs, String authType) {
                        }
    
                        @Override
                        public void checkServerTrusted(X509Certificate[] certs, String authType) {
                        }
    
                        @Override
                        public void checkClientTrusted(X509Certificate[] xcs, String string, Socket socket) throws CertificateException {
    
                        }
    
                        @Override
                        public void checkServerTrusted(X509Certificate[] xcs, String string, Socket socket) throws CertificateException {
    
                        }
    
                        @Override
                        public void checkClientTrusted(X509Certificate[] xcs, String string, SSLEngine ssle) throws CertificateException {
    
                        }
    
                        @Override
                        public void checkServerTrusted(X509Certificate[] xcs, String string, SSLEngine ssle) throws CertificateException {
    
                        }
    
                    }
                };
    
                SSLContext sc = SSLContext.getInstance("SSL");
                sc.init(null, trustAllCerts, new java.security.SecureRandom());
                HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
    
                // Create all-trusting host name verifier
                HostnameVerifier allHostsValid = new HostnameVerifier() {
                    @Override
                    public boolean verify(String hostname, SSLSession session) {
                        return true;
                    }
                };
                // Install the all-trusting host verifier
                HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
                /*
                 * end of the fix
                 */
    
                URL url = new URL("https://10.52.182.224/cgi-bin/dynamic/config/panel.bmp");
                URLConnection con = url.openConnection();
                //Reader reader = new ImageStreamReader(con.getInputStream());
    
                InputStream is = new URL(url.toString()).openStream();
    
                // Whatever you may want to do next
    
            }
    
        }
    

    【讨论】:

    • 核弹。谢谢。这里的代码更简洁:newfivefour.com/android-trust-all-ssl-certificates.html
    • @GayanWeerakutti 你能详细说明为什么代码更干净吗?请注意,我使用的是 X509ExtendedTrustManager 而不是 X509TrustManager。看看我的答案的开头,了解使用 TrustManager 的扩展风格的原因。
    • 我指的是它的类,作为它的复制粘贴问题。使用X509ExtendedTrustManager 可能是对的。链接上的 BTW 课程也对我有用。
    【解决方案3】:

    一个简单但不是纯 java 的解决方案是从 java 中转至 curl,这使您可以完全控制请求的完成方式。如果您只是为了简单的事情而这样做,则可以使用此方法有时忽略证书错误。此示例说明如何使用有效或无效证书向安全服务器发出请求,传入 cookie,并使用来自 java 的 curl 获取输出。

    import java.io.BufferedReader;
    import java.io.File;
    import java.io.IOException;
    import java.io.InputStreamReader;
    
    public class MyTestClass
    {
      public static void main(String[] args) 
      {
        String url = "https://www.google.com";
        String sessionId = "faf419e0-45a5-47b3-96d1-8c62b2a3b558";
    
        // Curl options are:
        // -k: ignore certificate errors
        // -L: follow redirects
        // -s: non verbose
        // -H: add a http header
    
        String[] command = { "curl", "-k", "-L", "-s", "-H", "Cookie: MYSESSIONCOOKIENAME=" + sessionId + ";", "-H", "Accept:*/*", url };
        String output = executeShellCmd(command, "/tmp", true, true);
        System.out.println(output);
      }
    
      public String executeShellCmd(String[] command, String workingFolder, boolean wantsOutput, boolean wantsErrors)
      {
        try
        {
          ProcessBuilder pb = new ProcessBuilder(command);
          File wf = new File(workingFolder);
          pb.directory(wf);
    
          Process proc = pb.start();
          BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream()));
          BufferedReader stdError = new BufferedReader(new InputStreamReader(proc.getErrorStream()));
    
          StringBuffer sb = new StringBuffer();
          String newLine = System.getProperty("line.separator");
          String s;
    
          // read stdout from the command
          if (wantsOutput)
          {
            while ((s = stdInput.readLine()) != null)
            {
              sb.append(s);
              sb.append(newLine);
            }
          }
    
          // read any errors from the attempted command
          if (wantsErrors)
          {
            while ((s = stdError.readLine()) != null)
            {
              sb.append(s);
              sb.append(newLine);
            }
          }
    
          String result = sb.toString();
    
          return result;
        }
        catch (IOException e)
        {
          throw new RuntimeException("Problem occurred:", e);
        }
      }
    
    
    }
    

    【讨论】:

      【解决方案4】:

      Java 和 HTTPS url 连接,无需下载证书

      如果您真的想避免下载服务器的证书,请使用匿名协议,例如 Anonymous Diffie-Hellman (ADH)。服务器的证书不与 ADH 和好友一起发送。

      您使用setEnabledCipherSuites 选择匿名协议。您可以通过getEnabledCipherSuites 查看可用的密码套件列表。

      相关:这就是为什么你必须在 OpenSSL 中调用 SSL_get_peer_certificate。你会得到一个带有匿名协议的X509_V_OK,这就是你检查交换中是否使用证书的方式。

      但正如 Bruno 和 Stephed C 所说,避免检查或使用匿名协议是个坏主意。


      另一种选择是使用 TLS-PSK 或 TLS-SRP。他们也不需要服务器证书。 (但我认为你不能使用它们)。

      问题是,您需要在系统中预先配置,因为 TLS-PSK 是 Pres-shared Secret 而 TLS-SRP 是 Secure Remote Password。身份验证是相互的,而不仅仅是服务器。

      在这种情况下,相互认证是由双方都知道共享秘密并到达相同的预主秘密的属性提供的;或其中一个(或两者)没有,并且通道设置失败。每一方都证明知道秘密是“相互”的部分。

      最后,TLS-PSK 或 TLS-SRP 不会做愚蠢的事情,例如在使用 HTTP(或通过 HTTPS)的网络应用程序中咳出用户密码。这就是为什么我说每一方都证明知道这个秘密......

      【讨论】:

        【解决方案5】:

        但是为什么我不必在本地为站点安装证书?

        您使用的代码明确设计为无需进行任何检查即可接受证书。这不是一个好的做法......但如果这是您想要做的,那么(显然)不需要安装您的代码明确忽略的证书。

        我是否应该在本地安装证书并为该程序加载它,或者它是在幕后下载的?

        没有,也没有。见上文。

        客户端到远程站点之间的流量是否仍在加密传输中?

        是的。但是,问题在于,由于您已告诉它信任服务器的证书而不进行任何检查,因此您不知道您是在与真正的服务器交谈,还是与其他一些假装的网站交谈成为真正的服务器。这是否是一个问题取决于具体情况。


        如果我们以浏览器为例,通常浏览器不会要求用户为访问的每个 ssl 站点显式安装证书。

        浏览器预装了一组受信任的根证书。大多数情况下,当您访问“https”站点时,浏览器可以验证该站点的证书(最终通过证书链)是否受到这些受信任证书之一的保护。如果浏览器无法将链开头的证书识别为受信任的证书(或者如果证书已过期或其他无效/不适当),则会显示警告。

        Java 的工作方式相同。 JVM 的 keystore 有一组可信证书,同样的过程用于检查证书是否被可信证书保护。

        java https客户端api是否支持某种机制自动下载证书信息?

        没有。允许应用程序从随机位置下载证书并将它们(以受信任的方式)安装在系统密钥库中将是一个安全漏洞。

        【讨论】:

          【解决方案6】:

          您不必在本地加载证书的原因是您已明确选择不使用信任所有证书的信任管理器验证证书。

          流量仍将被加密,但您打开了与中间人攻击的连接:您正在与某人秘密通信,您只是不确定它是您期望的服务器,还是可能的攻击者。

          如果您的服务器证书来自知名 CA,是与 JRE 捆绑的默认 CA 证书捆绑包的一部分(通常是 cacerts 文件,请参阅 JSSE 参考指南),您可以只使用默认信任管理器,您不必在这里设置任何东西。

          如果您有一个特定的证书(自签名或来自您自己的 CA),您可以使用默认的信任管理器或使用特定信任库初始化的信任管理器,但您必须在您的信任库中显式导入证书(经过独立验证),如this answer 中所述。你可能也对this answer感兴趣。

          【讨论】:

          • 如果我们以浏览器为例,通常浏览器不会要求用户为访问的每个 ssl 站点显式安装证书。 java https客户端api是否支持某种机制自动下载证书信息?
          • @BerlinBrown 因为浏览器有一个可信 CA 列表,就像 Java 一样。
          • 我刚刚得到了这个答案。我不知道这是否已经过时了,但你说:The traffic will still be encrypted, but you're opening the connection to Man-In-The-Middle attacks: 但如果你检查这个答案:stackoverflow.com/a/3075498/1194779 它会说不然。我有点迷路了。
          • @Lambasoft,这是“您明确选择不使用信任所有证书的信任管理器验证证书的结果”(checkServerTrusted 什么都不做,并且从不抛出异常),这有效地禁用了服务器的身份验证。您链接到的另一个答案假设这没有被禁用。
          • @Bruno 哦,我明白了!非常感谢您解决这个问题。最后一个大忙,你能指出我可以学习正确编写 HTTPS 安全连接的链接吗?如果没有问题当然可以。再次感谢布鲁诺!
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2017-03-28
          • 2018-05-07
          • 2021-04-04
          • 2011-08-19
          • 2016-11-19
          • 2013-03-12
          • 1970-01-01
          相关资源
          最近更新 更多