【问题标题】:Java JSSE TLS - Is this connection safely encrypted in both directions?Java JSSE TLS - 此连接是否在两个方向上安全加密?
【发布时间】:2012-10-15 02:32:48
【问题描述】:

在 Java 中使用 JSSE 和 TLS。我在服务器和客户端之间创建了一个安全套接字。在最终让套接字安全连接之后,我仍然对现有代码的安全性有一个基本问题。我按照教程中的说明进行操作,有时 JavaDoc 中的文档非常精确但有点模糊,除非您会说斯瓦赫里语的行话……

我用 C++ 进行网络编程已经有一段时间了。向 Java 的过渡很容易。然而,最近,我发现确保流量安全是谨慎的做法。话虽这么说:

我想以与 Web 浏览器创建安全套接字相同的方式创建安全套接字,因此双向流量均已加密。客户端可以看到从服务器发送的他们的个人帐户信息(如果被拦截则非常糟糕),并且客户端可以安全地将他们的用户名和密码发送到服务器(如果被拦截也非常糟糕)。

我完全了解公钥加密的工作原理,但仅靠公钥加密就有副作用。您将公钥发送给客户端,客户端使用公钥加密,然后将数据发送到服务器,只有服务器可以解密。现在据我了解,服务器使用私钥加密发往客户端的消息,需要添加另一层安全性以防止任何拥有公钥的人能够解密它。

  1. 我有一个公钥/私钥对存储在文件 public.key 和 private.key 中(我使用 JSSE 的 keytool 实用程序制作了这些文件
  2. 我在客户端中包含了 public.key
  3. 我在服务器中包含了 private.key

客户端类:

    KeyStore keyStore;
    TrustManagerFactory tmf;
    KeyManagerFactory kmf;
    SSLContext sslContext;
    SecureRandom secureRandom = new SecureRandom();
    secureRandom.nextInt();

        keyStore = KeyStore.getInstance("JKS");
        keyStore.load(this.getClass().getClassLoader().getResourceAsStream("server.public"),"public".toCharArray());
        tmf = TrustManagerFactory.getInstance("SunX509");
        tmf.init(keyStore);
        kmf = KeyManagerFactory.getInstance("SunX509");
        kmf.init(keyStore, "public".toCharArray());
        sslContext = SSLContext.getInstance("TLS");
        sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), secureRandom);
        SSLSocketFactory sslsocketfactory = sslContext.getSocketFactory();
        SSLSocket sslsocket = (SSLSocket)sslsocketfactory.createSocket("localhost", 9999);

服务器类:

    String passphrase = "secret"
    KeyStore keyStore;
    TrustManagerFactory tmf;
    KeyManagerFactory kmf;
    SSLContext sslContext;
    SecureRandom secureRandom = new SecureRandom();
    secureRandom.nextInt();

        keyStore = KeyStore.getInstance("JKS");
        keyStore.load(this.getClass().getClassLoader().getResourceAsStream("server.private"),passphrase.toCharArray());
        tmf = TrustManagerFactory.getInstance("SunX509");
        tmf.init(keyStore);
        kmf = KeyManagerFactory.getInstance("SunX509");
        kmf.init(keyStore, passphrase.toCharArray());
        sslContext = SSLContext.getInstance("TLS");
        sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), secureRandom);
        SSLServerSocketFactory sslserversocketfactory = sslContext.getServerSocketFactory();
        SSLServerSocket sslserversocket =
        (SSLServerSocket)sslserversocketfactory.createServerSocket(9999);

/ ******* 问题* em>*******/

一切正常!我将套接字连接到 BufferedReader 和 BufferedWriter 并在 accept(); 之后开始来回漂亮地交谈;从客户端连接并启动我的客户端和服务器发送/接收循环。

现在我知道此时客户端到服务器的通信是安全的。只有服务器密钥可以解密来自客户端的流量。但是服务器到客户端的通信呢?客户端的密钥可以解密来自服务器的消息,但在公钥加密 101 中,您了解到客户端现在应该向服务器发送公钥。这是在这段代码的幕后发生的吗? SSLContext 是否解决了这个问题?或者现在我有一个从客户端到服务器的加密连接,我现在是否应该为客户端生成一个私钥/公钥对?

让我知道上述代码中发送和接收的流量在两个方向上是否实际上都是安全的。

【问题讨论】:

  • 您是如何在客户端“包含”public.key 并在服务器上“包含”private.key 的??

标签: java network-programming ssl jsse


【解决方案1】:

SSL/TLS 中的证书(及其私钥)仅用于对 SSL/TLS 中的各方进行身份验证(通常,只有服务器使用证书)。

实际加密是使用在握手期间协商的共享/对称密钥完成的,该密钥源自使用经过身份验证的密钥交换形式交换的预主密钥(请参阅TLS Specification, Section F.1.1

如何完成经过身份验证的密钥交换取决于密码套件,但最终结果是相同的:双方之间共享的预主密钥,保证只有客户端和具有私钥的服务器知道为它的证书。

在主密钥交换之后,计算主密钥本身,从中派生出一对密钥(如Key Calculation section 中所述):一个供客户端写入(供服务器读取) ) 和一个供服务器写入(以及供客户端读取)。 (还会生成 MAC 机密,以保证连接完整性。)

原则上,并非所有密码套件都提供加密和经过身份验证的密钥交换(请参阅Cipher Suite Definitions section),但在 JSSE 中使用 SunJSSE 提供程序默认启用的所有密码套件都提供(请参阅 SunJSSE 提供程序文档中的Cipher Suite tables)。简而言之,不要启用名称中带有 anonNULL 的密码套件。

关于您的代码:

  • 有多个代码示例可以修复这样的 Key/TrustManagerFactory 算法(“SunX509”)。这通常是硬编码 Java 1.4 默认值的代码。从 Java 5 开始,默认的 TMF 算法是PKIX(请参阅Customization section of the JSSE Reference Guide)。解决此问题的最佳方法是使用 TrustManagerFactory.getDefaultAlgorithm()(KMF 也是如此),这也将允许您的代码在不支持 SunX509 的其他 JRE(例如 IBM)上运行。

  • 由于您没有使用客户端证书身份验证,因此在客户端使用 KeyManagerFactory 毫无意义。您使用可能没有私钥的密钥库对其进行初始化,这使其毫无意义。您不妨使用sslContext.init(null, tmf.getTrustManagers(), null)。 (两种情况下的安全随机数相同,让 JSSE 使用其默认值。)

【讨论】:

  • :) 谢谢你的解释。我把教程中的代码留在了那里,因为当时我还不确定在协商初始连接后是否需要添加另一个握手。事实上,我完全不清楚公钥密码学的 JSSE 实现。您链接的资源非常有用。
【解决方案2】:

您确实了解 PKI 的工作原理,但是您缺少 SSL 实现的两个关键部分。首先,大多数 PKI 算法允许双向加密流量。您可以使用公钥发送加密消息,并且只有拥有私钥的人才能读取它,这称为加密。您也可以使用私钥对消息进行加密,任何拥有公钥的人都可以解密,这称为数字签名。

另一个缺失的部分是 SSL 不使用 PKI 在客户端和服务器之间发送网络流量。它使用对称加密算法。然而,对称加密的密钥(称为会话密钥)是使用相当复杂的挑战-响应协议建立的,该协议采用 PKI 和证书。在这个阶段,服务器向客户端证明它不是中间人,客户端可以选择向服务器证明它的证书,如果它有任何更强的身份验证,并建立对称会话密钥。更多详情在这里https://www.rfc-editor.org/rfc/rfc5246

对称密钥用于使用 RC5 或 AES 等算法加密流量

【讨论】:

  • 感谢您的回复,但我想知道的是,JSEE 的 TLS API 实现是加密从服务器到客户端的流量(我发布的代码),还是任何具有公钥将能够解密从服务器到客户端的流量吗?
  • 不,TLS 和 SSL 意味着点对点隐私。握手时生成的对称会话密钥包含一段由客户端生成并使用服务器公钥加密的随机数据。所以只有那些拥有服务器私钥的人才能破译它。此随机数据用于生成会话密钥,用于对流量进行实际加密。因此,那些没有服务器私钥的人将无法破译这些发送到服务器的数据,并且随后将没有会话密钥来窥探加密的流量。
  • 这是个好消息。文档中没有真正说明“此方法生成会话密钥以保护双向通信”,或者如果它确实是微妙的。在阅读了 PKI 并找到了本教程之后,我想在设置初始套接字并为不同的客户端管理不同的密钥之后,我必须协商从客户端到服务器的另一个套接字......很高兴看到这一切都得到了处理。顺便说一句,我找到的教程是迄今为止最好的,cs.wmich.edu/~alfuqaha/Spring07/cs6030/lectures/jsse.pdf
  • @SigSeg 握手就可以了。没有特定的 Java 方法可以专门执行此操作,但这一点无关紧要。 Java 文档中没有任何内容必须这么说。 RFC 2246 说明了这一点。任何实现 TLS/SSL 的 API 都必须按照 RFC 中的规定执行,即双向对称加密。
猜你喜欢
  • 1970-01-01
  • 2023-03-09
  • 1970-01-01
  • 2012-04-23
  • 1970-01-01
  • 2022-12-17
  • 2016-12-27
  • 2021-01-25
  • 1970-01-01
相关资源
最近更新 更多