5

在 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);

/ * ** * ** *问题* ** * ** * * /

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

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

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

4

2 回答 2

6

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

实际加密是使用在握手期间协商的共享/对称密钥完成的,从使用经过身份验证的密钥交换形式交换的预主密钥派生(参见TLS 规范,第 F.1.1 节

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

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

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

关于您的代码:

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

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

于 2012-10-15T08:42:32.680 回答
1

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

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

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

于 2012-10-15T03:28:37.937 回答