【问题标题】:Secret Key SSL Socket connections in JavaJava 中的密钥 SSL 套接字连接
【发布时间】:2014-05-09 09:34:15
【问题描述】:

我正在加密服务器和客户端之间的 tcp 连接。在研究和测试过程中,我倾向于使用密钥加密。我的问题是我找不到任何有关如何实现此功能的教程。我发现的教程都是围绕一次性 https 请求展开的,我只需要一个 SSL 套接字。

到目前为止我编写的代码如下。我几乎可以肯定它需要扩展,我只是不知道如何。任何帮助表示赞赏。

private ServerSocketFactory factory;
private SSLServerSocket serverSocket;

factory = SSLServerSocketFactory.getDefault();
serverSocket = (SSLServerSocket) factory.createServerSocket( <portNum> );

接受客户端连接的服务器代码

SSLSocket socket = (SSLSocket) serverSocket.accept();
socket.startHandshake();

我只是不知道如何实际握手。

参考:http://docs.oracle.com/javase/1.5.0/docs/guide/security/jsse/JSSERefGuide.html

【问题讨论】:

  • 对这里的答案感兴趣,希望有一个例子。我以前想使用 SSL 套接字,但有点像你,从来没有完全理解密钥是如何交换的,但是我从来没有付出太多努力,只是阅读文档,就像,嗯,那不听起来很简单,大声笑。
  • 我在下面的回答中提供了我希望相当于教程的内容。在您的情况下,您通常只需将 socket.startHandshake 调用移动到客户端,适当地获取客户端的 SSLSocket - 您需要确保您的证书是正确的。
  • @WarrenDew,太糟糕了,我们不能对 cme​​ts 投反对票,但你上面所说的确实没有意义。 socket.startHandshake 是一个 Java API 调用,它与谁在协议级别实际开始握手没有太大关系。它可以在客户端或服务器端调用(尽管作为重新协商的一部分,它在服务器上最有意义)。它通常是可选的。将其“移动”到客户端并不重要。

标签: java sockets ssl sslhandshakeexception sslsocketfactory


【解决方案1】:

SSL 套接字连接在 Java 中得到很好的支持,可能是您的不错选择。需要提前了解的一件事是 SSL 同时提供加密和服务器身份验证;你不能轻易地得到加密。作为参考,加密可防止网络窃听,而服务器身份验证可防止“中间人”攻击,其中攻击者充当客户端和服务器之间的代理。

由于身份验证是 SSL 的组成部分,因此服务器需要提供 SSL 证书,而客户端需要能够对证书进行身份验证。服务器将需要一个“密钥库”文件来存储其证书。客户端将需要一个“信任库”文件来存储它所信任的证书,其中一个必须是服务器的证书,或者是一个证书,从该证书可以将“信任链”追踪到服务器的证书。

请注意,您无需了解 SSL 的来龙去脉即可使用 Java SSL 套接字。我确实认为阅读有关 SSL 如何工作的信息很有趣,例如关于 TLS 的 Wikipedia 文章,但复杂的多步握手和实际连接加密的设置都在 SSLServerSocket 和 SSLSocket 类的幕后处理。

代码

以上所有内容只是解释以下部分代码的背景信息。该代码假定您熟悉常规的未加密套接字。在服务器上,您将需要这样的代码:

/**
 * Returns an SSLServerSocket that uses the specified key store file 
 * with the specified password, and listens on the specified port.
 */
ServerSocket getSSLServerSocket(
    File keyStoreFile, 
    char[] keyStoreFilePassword,
    int port
) throws GeneralSecurityException, IOException {
    SSLContext sslContext 
        = SSLConnections.getSSLContext(keyStoreFile, keyStoreFilePassword);
    SSLServerSocketFactory sslServerSocketFactory 
        = sslContext.getServerSocketFactory();
    SSLServerSocket sslServerSocket
        = (SSLServerSocket) sslServerSocketFactory.createServerSocket(port);
    return sslServerSocket;
}

然后可以像使用任何其他 ServerSocket 一样使用 SSLServerSocket;认证、加密和解密将对调用代码完全透明。事实上,我自己代码中的同源函数声明了一个只是普通ServerSocket的返回类型,所以调用代码不会混淆。

注意:如果您想使用 JRE 的默认 cacerts 文件作为您的密钥存储文件,您可以跳过创建 SSLContext 的行,并使用 ServerSocketFactory.getDefault() 获取 ServerSocketFactory。您仍然需要将服务器的公钥/私钥对安装到密钥存储文件中,在本例中为 cacerts 文件。

在客户端,你需要这样的代码:

SSLSocket getSSLSocket(
    File trustStoreFile,
    char[] trustStoreFilePassword,
    InetAddress serverAddress,
    port serverPort
) throws GeneralSecurityException, IOException {
    SSLContext sslContext 
        = SSLConnections.getSSLContext(trustStoreFile, trustStoreFilePassword);
    SSLSocket sslSocket 
        = (SSLSocket) sslContext.getSocketFactory().createSocket
            (serverAddress, serverPort);
    sslSocket.startHandshake();
    return sslSocket;
}

和服务端代码中的 SSLServerSocket 一样,这里返回的 SSLSocket 和普通的 Socket 一样使用;进出 SSLSocket 的 I/O 是通过未加密的数据完成的,所有加密的东西都在里面完成。

与服务器代码一样,如果您想使用默认的 JRE cacerts 文件作为您的信任存储,您可以跳过 SSLContext 的创建并使用SSLSocketFactory.getDefault() 而不是sslContext.getSocketFactory()。在这种情况下,如果服务器的证书是自签名的或不是由主要认证机构之一颁发的,您只需要安装服务器的证书。此外,为确保您不信任在您信任的证书链中合法颁发的证书,但与您尝试访问的域完全不同,您应该在该行之后添加以下(未经测试的)代码在哪里创建 SSLSocket:

    sslSocket.getSSLParameters().setEndpointIdentificationAlgorithm("HTTPS");

如果您使用自己的信任存储文件,但信任该文件中的一个或多个证书颁发机构颁发的所有证书,或者信任该文件中不同服务器的多个证书,这也适用。

证书、密钥库和信任库

现在是最困难的部分,或者至少是稍微困难的部分:生成和安装证书。我建议使用 Java keytool,最好是 1.7 或更高版本来完成这项工作。

如果您要创建自签名证书,请首先使用如下命令从命令行生成服务器的密钥对:keytool -genkey -alias server -keyalg rsa -dname "cn=server, ou=unit, o=org, l=City, s=ST, c=US" -validity 365242 -keystore server_key_store_file -ext san=ip:192.168.1.129 -v。替换您自己的名称和值。特别是,此命令为 IP 地址为 192.168.1.129 的服务器创建一个在 365242 天(1000 年)后到期的密钥对。如果客户端将通过域名系统查找服务器,请使用 san=dns:server.example.com 之类的名称,而不是 san=ip:192.168.1.129。有关 keytool 选项的更多信息,请使用man keytool

系统将提示您输入密钥库的密码 - 或设置密钥库的密码(如果这是一个新的密钥库文件)并为新的密钥对设置密码。

现在,使用keytool -export -alias server -file server.cer -keystore server_key_store_file -rfc -v 导出服务器的证书。这将创建一个 server.cer 文件,其中包含带有服务器公钥的证书。

最后,将 server.cer 文件移动到客户端计算机并将证书安装到客户端的信任库中,使用类似 keytool -import -alias server -file server.cer -keystore client_trust_store_file -v 的东西。系统将提示您输入信任库文件的密码;提示将显示“输入密钥库密码”,因为 Java 密钥工具适用于密钥库文件和信任库文件。请注意,如果您使用默认的 JRE cacerts 文件,我相信初始密码是 changeit

如果您使用从公认的认证机构购买的证书,并且您在客户端使用默认的 JRE cacerts 文件,您只需将证书安装在服务器的密钥库文件中;您不必弄乱客户的文件。服务器安装说明应由认证机构提供,或者您可以再次查看man keytool 获取说明。

关于套接字,尤其是 SSL 套接字,有很多神秘之处,但它们实际上很容易使用。在许多情况下,十行代码将避免对复杂和脆弱的消息传递或消息队列基础设施的需求。适合您考虑此选项。

【讨论】:

  • 您能解释一下如何生成证书吗?最好也解释一下如何将证书提供给客户,但我认为这可能不是主题。
  • 我添加了如何生成密钥对和相关证书的描述,如果是自生成证书,如何在客户端安装。
  • 很好的答案,谢谢。我将在此页面添加书签以供进一步参考。
  • @WarrenDew,这是迄今为止我见过的最简洁的教程。我试试看,谢谢!
  • @Jared,您应该阅读 Security.SE 上的 this question。服务器根本不加密证书。客户端使用服务器的 pubkey(或者对于 DHE 交换更复杂的东西,服务器签署新生成的密钥)来加密预主密钥(RSA 密钥交换)。从那时起,它都是对称加密,没有公钥/私钥。
【解决方案2】:

您已开始握手。这就是你所要做的:事实上你甚至不必这样做,因为它会自动发生。您现在所要做的就是正常的输入和输出,就像使用纯文本套接字一样。

【讨论】:

  • 我会注意到握手通常是由客户端启动的。
  • @WarrenDew 我会注意到,就应用程序而言,它是自动的,正如我所说的,而且任何一方,甚至双方都可以调用 startHandshake()。通过网络,它总是由客户端启动(以 SSL 术语),即使用 ClientHello 消息,但这不是 OP 需要关心的事情。
  • 就应用程序代码而言,确实如此。但是,从配置的角度来看,不开始握手的一方需要有一个带有公钥/私钥对的密钥库。开始握手的一方只需要一个带有适当证书的信任库。
  • @WarrenDew,我认为 EJP 指的是“任何在此套接字上读取或写入应用程序数据的尝试都会导致隐式握手”(参见SSLSocket Javadoc)。服务器和客户端都不需要在 Java 中显式地执行此操作。根据定义,初始握手总是由 SSL/TLS 客户端启动(请参阅glossary),并且您或多或少总是希望在任何 SSL/TLS 连接中验证服务器。跨度>
  • @Bruno 我不同意这一切。但是,在一般情况下,不能假设希望成为客户端的应用程序的一部分一定是第一个在连接上读取或写入数据的部分。因此,在某些情况下,您可能确实希望从客户端显式启动握手。绝对没有您想从服务器显式启动握手的情况,因为问题的原始代码有它。我并不反对 EJP。我只是添加了可能与某些读者相关的其他相关信息。
猜你喜欢
  • 1970-01-01
  • 2015-11-12
  • 2012-02-29
  • 2018-10-11
  • 2020-01-02
  • 2015-11-29
  • 2013-09-18
  • 2011-04-17
  • 1970-01-01
相关资源
最近更新 更多