Java SSL 代码抛出 NoSuchAlgorithmException 异常

7

我正在开发一个项目,想要添加SSL,因此我创建了一个简单的客户/服务器测试实现来检查它是否有效,但是出现了NoSuchAlgorithmException异常。以下是我抛出异常的服务器代码:

import java.io.*;
import java.net.*;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;

import javax.net.ssl.*;

public class SslServer
{
    private static final int PORT = 5555;

    public static void main(String[] args)
    {
        SecureRandom sr = new SecureRandom();
        sr.nextInt();

        try {
            //client.public is the keystore file that holds the client's public key (created with keytool)
            KeyStore clientKeyStore = KeyStore.getInstance("JKS");
            clientKeyStore.load(new FileInputStream("client.public"), "clientpublicpw".toCharArray());

            //server.private is the key pair for the server (created with keytool)
            KeyStore serverKeyStore = KeyStore.getInstance("JKS");
            clientKeyStore.load(new FileInputStream("server.private"), "serverprivatepw".toCharArray());

            TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
            tmf.init(clientKeyStore);

            //This next line is where the exception occurs
            KeyManagerFactory kmf = KeyManagerFactory.getInstance("TLS");
            kmf.init(serverKeyStore, "serverprivatepw".toCharArray());

            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), sr);

            SSLServerSocketFactory sf = sslContext.getServerSocketFactory();
            SSLServerSocket ss = (SSLServerSocket)sf.createServerSocket(SslServer.PORT);
            ss.setNeedClientAuth(true);

            BufferedReader in = new BufferedReader(new InputStreamReader(ss.accept().getInputStream()));

            String line = null;
            while((line = in.readLine()) != null)
            {
                System.out.println(line);
            }
            in.close();
            ss.close();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (KeyStoreException e) {
            e.printStackTrace();
        } catch (UnrecoverableKeyException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        }
    }

}

我得到的堆栈跟踪如下:
java.security.NoSuchAlgorithmException: TLS KeyManagerFactory not available
    at sun.security.jca.GetInstance.getInstance(Unknown Source)
    at javax.net.ssl.KeyManagerFactory.getInstance(Unknown Source)
    at SslServer.main(SslServer.java:32)

我尝试将“TLS”替换为“SSL”,但我仍然得到了相同的异常。这对我来说没有意义。TLS和SSL怎么可能不被支持呢?这是我第一次尝试实现SSL,似乎很难找到好的资源,其中包括良好解释的代码示例。有人能告诉我为什么会出现这种异常或指出我的代码有什么问题吗?
3个回答

16

有几个问题:

  • 它叫做 TLS (传输层安全协议),而不是 TSL(在 SSLContext 中)。
  • 我建议在这里使用默认值:TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())(Oracle JRE 上的默认值将是 PKIX)。
  • 编辑:)默认的 KeyManagerFactorySunX509(这里不存在 TLS)。同样,使用 getDefaultAlgorithm()
  • 读取文件之后,应该关闭 FileInputStream
  • 不清楚为什么你在同一个地方都有客户端和服务器的密钥库。这应该是两个程序:一个用于客户端,一个用于服务器(setNeedClientAuth(true) 只在服务器端有用)。如果这实际上是您的密钥库,则将其称为其他名称会更清晰。(此外,由于似乎正在学习如何使其工作,建议先尝试没有客户端证书身份验证的情况,在这种情况下,服务器不需要信任商店:使用 SSLContext.init(...) 的第二个参数传递 null 以使用默认值。)
  • 请勿将服务器密钥库提供给客户端。只需将其证书导出到新的密钥库中,您将使用该密钥库作为信任库。每个实体(客户端和服务器)都应保留自己的私钥。
  • 你需要在信任商店中导入的不仅是远程方的公钥(仅仅是公钥),而是他的整个证书,以确保安全。
  • 为了清晰起见,请保留文件的适当扩展名:对于您的 JKS 密钥库使用 .jks,这样以后就不会出现麻烦。
  • 可以在 SSLContext.init(...) 中使用 null 作为 SecureRandom 的值:这将根据安全提供程序使用默认值。

像这样的代码应该更好用:

KeyStore trustStore = KeyStore.getInstance("JKS");
InputStream tsis = new FileInputStream("trustedcerts.jks");
trustStore.load(tsis, "clientpublicpw".toCharArray());
tsis.close();

KeyStore serverKeyStore = KeyStore.getInstance("JKS");
InputStream ksis = new FileInputStream("server.jks");
clientKeyStore.load(ksis.close(), "serverprivatepw".toCharArray());
ksis.close();

TrustManagerFactory tmf = 
    TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);

KeyManagerFactory kmf = 
    KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(serverKeyStore, "serverprivatepw".toCharArray());

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

SSLServerSocketFactory sf = sslContext.getServerSocketFactory();
SSLServerSocket ss = (SSLServerSocket)sf.createServerSocket(SslServer.PORT);
ss.setNeedClientAuth(true);

如果你真的担心密码安全,那么你应该直接使用char[]或密码回调来处理密码,在真正的应用中永远不要硬编码你的密码(参见https://dev59.com/rGox5IYBdhLWcg3w74q2#8889285)。 - Bruno
顺便提一下,等待readLine返回null并不是停止读取(从任何类型的套接字)的好方法,请参见此处的讨论:https://dev59.com/1VjUa4cB1Zd3GeqPRneb#6425509 - Bruno
1
我修改了我的代码,使用TrustManagerFactory.getDefaultAlgorithm()和KeyManagerFactory.getDefaultAlgorithm(),这样就可以工作了。我在这里有两个密钥库的原因是因为服务器有server.private和client.public,而客户端将有client.private和server.public。顺便说一句,我用这个网站作为我的代码参考,不确定它有多可靠,但这是我能找到的最好的:www.ibm.com/developerworks/java/tutorials/j-jsse/index.html - MattL922
它确实在KMF中使用了SunX509,而不是TLS。它还使用了一个相当旧的Java版本,TMF的新默认值是PKIX。(我仍然不确定本文中密钥库文件命名约定的正确性。) - Bruno
不要检查读取器的结尾。它可能大部分时间都能正常工作,所以你看不到问题,但是要研究其他协议。HTTP、POP、IMAP、SMTP都有某种形式的分隔符来指示远程方何时完成(并等待另一条消息)。每当您在聊天中发送新消息(可能或可能不在应用程序协议级别上表示为“消息”),请使用某个指示器来说明何时结束。 - Bruno
显示剩余4条评论

3
请看这里的示例,以及支持算法的名称。KeyManagerFactory 支持的算法有 "SunX509" 和 "NewSunX509"。协议名为 TLS,不是 TSL。请注意,保留了 HTML 标签。

再次强调,这只是一个打错字,已经被修正了。我仍然遇到了“TLS”异常。 - MattL922
你看过我的回答吗?似乎"SunX509"和"NewSunX509"是KeyManagerFactory支持的算法。 - JB Nizet
+1(因为我在最初的回答中打错了X509 -> SunX509)。@MattL922:getDefaultAlgorithm()通常是最安全的选择。 - Bruno

1

正确的SSLContext名称是“TLS”。可以在这里找到标准算法名称列表。


这是一个打错字,已经被修正了。我仍然遇到了“TLS”异常。 - MattL922
请查看Bruno在他的答案中关于KeyManagerFactory的编辑,那将是你的另一个问题。 - Joachim Isaksson

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