Java和SSL证书

18

我正在尝试使用安全套接字层(HTTPS)在Java中与我的PHP脚本建立连接,但我发现为确保最大的安全性/有效性,我必须将我的网站使用的SSL证书导入到我的应用程序中...这是我不知道如何做的事情。

如果可以的话,我的SSL证书不是自签名的,而是由StartSSL提供的,并且我正在使用Eclipse IDE。

有人能指点我正确的方向吗?例如,我需要哪些文件,应该在哪里导入它们,以及我需要在Java中编写什么代码等等?


1
如果它可以在不导入任何证书的情况下正常工作,我不明白通过导入网站证书如何确保/增强安全性。如果必须导入任何证书,那么应该是StartSSL的根证书,而不是您的网站证书,因为Java无法将StartSSL识别为有效的认证机构。 - JB Nizet
@JB哦。只是因为在我之前的问题中,Chubbard说我应该确保在我的客户端中验证服务器的证书,以确保我正在与一个假服务对话,并提到了keytool。所以如果你说导入网站证书不影响安全性,那么为什么会有人导入SSL证书呢? - Andy
2
是Java的SSL堆栈将验证网站证书,就像浏览器一样。您在访问SSL网站之前是否导入所有SSL网站的证书?不需要。浏览器会验证其证书,因为它由浏览器知道的知名证书颁发机构认证。在您的情况下,StartSSL是证书颁发机构。Java也是如此。您可能需要导入StartSSL的证书,但不需要导入网站证书。 - JB Nizet
另外,我的SSL证书实际上是由StartCom Ltd.验证/提供的,尽管使用了StartSLL。因此,如果有人获取了StartCom的根证书,Java是否会将其识别为有效的认证机构,并在连接到我的PHP脚本时知道它是否正在与虚假服务通信? - Andy
阅读Vineet的回答。它很完美。 - JB Nizet
是的,Vineet的回答确实帮了很多忙,但还是谢谢你的意见! - Andy
6个回答

32
我发现为了确保最大的安全性/有效性,我需要将我的网站使用的SSL证书导入到我的应用程序中。你部分正确,当你说出那句话时。你不需要导入你的SSL证书。导入StartSSL CA证书就足够了。
此外,在Java应用程序中不存在导入证书的概念。Java中的SSL支持依赖于密钥库和信任库的概念,而不是一些在你的应用程序中打包的证书。如果你要发布你的应用程序供最终用户下载和执行,那么没有必要在你的应用程序中发布你的证书或私钥。私钥和相关的证书将存储在只有你可以访问的密钥库中。
你的应用程序的最终用户将依赖于Java运行时中的SSL支持,该支持将使应用程序能够在验证服务器证书后建立与站点的SSL连接。Java运行时在信任库中提供了一组默认的CA证书,仅要求成功建立SSL连接的先决条件是服务器的SSL证书由信任库中的某个CA发行。至少在版本6中,Java运行时的信任库中没有StartSSL的证书,因此:
  • You could instruct your end users to perform the activity of importing the StartSSL CA certificate into the Java truststore. Links that may help include this StartSSL forum thread (only the first 4 steps are needed to import the CA certs into a truststore), a GitHub project, and this blog post; a disclaimer - I haven't attempted using any of those and you ought to be using it at your own risk.
  • Or, you could initialize your application with your own truststore using the -Djavax.net.ssl.trustStore=<path_to_truststore> -Djavax.net.ssl.trustStorePassword=<truststore_password> JVM startup flags, or execute the following code before initializing SSL connections:

    System.setProperty("javax.net.ssl.trustStore","<path_to_truststore>");
    System.setProperty("javax.net.ssl.trustStorePassword","<truststore_password>");
    

    This is a viable approach only if your application is a Java SE application that does not happen to be an applet (or an application with similar restrictions on how the truststore is specified).


阅读Java keytool文档也会有所帮助。


谢谢您提供的信息丰富而又有见地的答案!基本上,您的意思是我只需要导入StartSSL的CA证书,然后我就可以安全地进行了?如果我没理解错的话,您提供的链接仅适用于最终用户,证书不会随我的软件分发,但是您第二个要点中的方法意味着StartSSL的CA证书将始终存在,我的最终用户不需要做任何事情?因为我真的不希望我的客户必须导入StartSSL的CA证书... - Andy
CA证书具有多种用途;对于像Tomcat这样使用StartSSL颁发的站点证书通过HTTPS提供站点服务的Java服务器,您需要拥有一个包含站点证书的密钥库,以及一个包含StartSSL CA证书的信任库。 SSL客户端(如浏览器或Java应用程序)只需要在信任库中拥有StartSSL CA证书(或用于验证信任的任何内容)。因此,您的最终用户必须将CA证书导入cacerts文件中,应用程序所建立的连接将是安全的。 - Vineet Reynolds
我不确定这是否有所不同,但我计划使用launch4j来分发JRE与我的应用程序,以便我不必依赖客户安装Java。那么,这是否意味着StartSSL的CA证书也随JRE一起打包? - Andy
当您使用launch4j时,它不需要指向任何位置(除非您的代码依赖于该环境变量)。但是,如果您尝试查找keytool可执行文件,则它在JRE和JDK中都可用,在JAVA_HOME/bin目录中。因此,如果您已将Java安装在C:\ Java \ jdk1.6.0_26中,则该目录将是JAVA_HOME,其中keytool位于bin子目录中。 - Vineet Reynolds
只要您遵循StartSSL论坛帖子,就足够了。您需要确保导入信任存储库的StartSSL根CA和中间CA证书。Github项目除了提供一个Windows批处理脚本来导入证书(随项目一起提供,但实际上,您可以下载这些证书)之外,什么也不做。关于测试信任存储库的正确性,您可以使用一个Java程序,该程序使用StartSSL证书连接到网站进行HTTPS连接。 - Vineet Reynolds
显示剩余6条评论

3
下面的方法将加载默认(cacerts)密钥库,检查是否安装了证书,如果没有则安装证书。它消除了在任何服务器上手动运行keystore命令的必要性。
它假定默认密钥库密码(changeit)未更改,如果已更改,请更新CACERTS_PASSWORD。请注意,该方法在添加证书后保存密钥库,因此运行一次后,证书将永久保存在密钥库中。
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;

/**
 * Add a certificate to the cacerts keystore if it's not already included
 */
public class SslUtil {
    private static final String CACERTS_PATH = "/lib/security/cacerts";

    // NOTE: DO NOT STORE PASSWORDS IN PLAIN TEXT CODE, LOAD AT RUNTIME FROM A SECURE CONFIG
    // DEFAULT CACERTS PASSWORD IS PROVIDED HERE AS A QUICK, NOT-FOR-PRODUCTION WORKING EXAMPLE
    // ALSO, CHANGE THE DEFAULT CACERTS PASSWORD, AS IT IMPLORES YOU TO!
    private static final String CACERTS_PASSWORD = "changeit";

    /**
     * Add a certificate to the cacerts keystore if it's not already included
     * 
     * @param alias The alias for the certificate, if added
     * @param certInputStream The certificate input stream
     * @throws KeyStoreException
     * @throws NoSuchAlgorithmException
     * @throws CertificateException
     * @throws IOException
     */
    public static void ensureSslCertIsInKeystore(String alias, InputStream certInputStream)
            throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException{
        //get default cacerts file
        final File cacertsFile = new File(System.getProperty("java.home") + CACERTS_PATH);
        if (!cacertsFile.exists()) {
            throw new FileNotFoundException(cacertsFile.getAbsolutePath());
        }

        //load cacerts keystore
        FileInputStream cacertsIs = new FileInputStream(cacertsFile);
        final KeyStore cacerts = KeyStore.getInstance(KeyStore.getDefaultType());
        cacerts.load(cacertsIs, CACERTS_PASSWORD.toCharArray());
        cacertsIs.close();

        //load certificate from input stream
        final CertificateFactory cf = CertificateFactory.getInstance("X.509");
        final Certificate cert = cf.generateCertificate(certInputStream);
        certInputStream.close();

        //check if cacerts contains the certificate
        if (cacerts.getCertificateAlias(cert) == null) {
            //cacerts doesn't contain the certificate, add it
            cacerts.setCertificateEntry(alias, cert);
            //write the updated cacerts keystore
            FileOutputStream cacertsOs = new FileOutputStream(cacertsFile);
            cacerts.store(cacertsOs, CACERTS_PASSWORD.toCharArray());
            cacertsOs.close();
        }
    }
}

使用方法如下:

SslUtil.ensureSslCertIsInKeystore("startssl", new FileInputStream("/path/to/cert.crt"));

在我的代码中,这会导致一个java.io.FileNotFoundException: C:\Program Files (x86)\Java\jre1.8.0_45\lib\security\cacerts(访问被拒绝)。我将证书下载到临时文件夹,然后使用唯一别名(不是starttls)和fileInputStream导入它。我的证书来自HTTPS服务器(www.gmx.at)。 - lumo
问题:在代码中以明文形式存储密码不是一个相当大的安全漏洞吗?或者在SSL连接方面这并不重要吗? - Flaom
@Flaom 是的,绝对不应该在代码中存储明文密码。上面的密码(changeit)是默认的 cacerts 密码,应该进行更改。我会在代码片段中添加注释来指出这一点。 - Shane

2

显然,由于某些原因,mailgun工程师不想给我们清晰的解决方案。这是我所做的。

我们运行tomcat8并通过jersey web服务连接到mailgun API。我按照这位用户的指示操作,效果很好。希望能帮到有需要的人。

在1月22日,由于Symantec的PKI基础设施被设置为不受信任,我们更新了SSL证书。一些旧版本的Java没有“DigiCert Global Root G2” CA。

有几个选项:

将“DigiCert Global Root G2” CA导入您的“cacerts”文件。

升级您的JRE到8u91(或更高版本),其中包括此根证书。

要导入“DigiCert Global Root G2”,您可以从https://www.digicert.com/digicert-root-certificates.htm下载根证书。请确保下载正确的根证书。

下载证书后,您需要使用以下命令将其导入:

keytool -import -trustcacerts -keystore /path/to/cacerts -storepass changeit -noprompt -alias digicert-global-root-g2 -file /path/to/digicert.crt 您需要设置Java Keystore的路径和您下载的根证书的位置。


所以: 1. /path/to/digicert.crt是您刚下载的文件。 2. /path/to/cacerts - 这在您的JRE路径中。我使用“find / -name cacerts -print”命令快速查找了所有Java cacerts在我的文件系统上的位置。对于我来说,它在/usr/lib/jvm/java-7-openjdk-amd64/jre/lib/security/cacerts中。


0
请参考以下文章: http://stilius.net/java/java_ssl.php。它包含一些代码示例,这将有助于在您尝试从代码中访问脚本的情况下解决问题。
请注意,你应该使用系统属性。
javax.net.ssl.keyStore
javax.net.ssl.keyStorePassword

使用keytool工具将SSL证书传递给JVM或导入到JRE密钥库中


感谢您的答复@ Sergey。不幸的是,我已经看了您建议的文章。如果我再多研究一下,它可能会有所帮助,但这对我来说完全是一个新课题,而且我有点时间紧迫,所以很抱歉,但我在这里寻找最简单的方法。幸运的是,在与Vineet的讨论中,我发现了最适合我的方法,但还是谢谢! - Andy

0
我发现为了确保最大的安全性/有效性,我必须导入SSL证书。
不,你不需要这一步。只有在客户端没有信任服务器证书的签名者时才需要这一步骤,这种情况只会出现在服务器证书是自签名或由内部CA签名的情况下。

谢谢您的回答。不过很遗憾,我已经导入了一个证书——但现在我并不认为这是必要的,问题是如果我的客户不信任服务器证书的签发人,我怎么知道呢?当使用“Htttpsurlconnection”时,我的应用程序会收到异常提示吗? - Andy
@Andy,你会得到与你第一次遇到的相同的异常。 - user207421

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接