Я написал программу на Java клиент / сервер, используя объекты ServerSocket и Socket. Затем я изменил код, чтобы использовать SSLServerSocket и SSLSocket, однако получаю разные исключения, в том числе:
javax.net.ssl.SSLHandshakeException: no cipher suites in common
Я надеюсь сделать как можно больше программно. Я также не против самоподписанных сертификатов.
В одном учебном пособии, которому я следовал, предлагалось создать сертификат с помощью Java-приложения keytool, а затем переместить этот файл в свой Java-проект. Я сделал это с помощью команды терминала keytool -genkey -alias zastore -keyalg RSA -keystore za.store. Я назначил пароль password.
Затем я вызываю функцию System.setProperty в надежде, что SSLSockets заработает, но это все еще не работает.
Вот мой серверный код
public class Server implements Runnable
{
private SSLServerSocket serverSocket;
private int portNumber;
private Thread acceptThread;
private LinkedList<Connection> connections;
private ConnectionListener connectionListener;
public Server(int port, ConnectionListener connectionListener)
{
this.connectionListener = connectionListener;
portNumber = port;
connections = new LinkedList<Connection>();
try
{
System.setProperty("javax.net.ssl.trustStore", "za.store");
System.setProperty("javax.net.ssl.keyStorePassword", "password");
SSLServerSocketFactory sslssf = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
serverSocket = (SSLServerSocket) sslssf.createServerSocket(portNumber,15);
}
catch (IOException e)
{
e.printStackTrace();
}
}
public void startListening()
{
acceptThread = new Thread(this);
acceptThread.start();
}
public void stopListening()
{
for(Connection c:connections)
{
c.stopListeningAndDisconnect();
}
try
{
serverSocket.close();
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public void run()
{
try
{
while(true)
{
SSLSocket s = (SSLSocket) serverSocket.accept();
Connection c = new Connection(s,connectionListener);
connections.add(c);
System.out.println("New Connection Established From"+s.getInetAddress().toString());
}
}
catch(java.net.SocketException e)
{
System.out.println("Listening thread terminated with exception.");
}
catch(IOException e)
{
e.printStackTrace();
}
}
public void removeConnection(Connection c)
{
connections.remove(c);
}
public void printConnections()
{
System.out.println("Number of connections "+connections.toString());
for(int i=0; i<connections.size(); i++)
{
System.out.println(connections.toString());
}
}
}
А затем фрагмент моего клиентского кода, который подключается при нажатии кнопки:
@Override
public void actionPerformed(ActionEvent e)
{
if (e.getSource() == connect)
{
try
{
System.setProperty("javax.net.ssl.trustStore", "za.store");
System.setProperty("javax.net.ssl.keyStorePassword", "password");
SSLSocketFactory sslsf = (SSLSocketFactory)SSLSocketFactory.getDefault();
SSLSocket s = (SSLSocket)sslsf.createSocket(ipBox.getText(), Integer.parseInt(portBox.getText()));
Connection c = new Connection(s,parent);
parent.connectionSuccessful(c);
}
catch (NumberFormatException e1)
{
JOptionPane.showMessageDialog(this, "Error! Port number must be a number", "Error", JOptionPane.ERROR_MESSAGE);
}
catch (UnknownHostException e1)
{
JOptionPane.showMessageDialog(this, "Error! Unable to find that host", "Error", JOptionPane.ERROR_MESSAGE);
}
catch (IOException e1)
{
e1.printStackTrace();
}
}
}
В одной статье Stackoverflow говорилось, что у моего сервера «нет сертификата». Я не знаю, что это значит и как получить его и разместить в нужном месте.




Следующая ошибка может возникнуть по разным причинам:
javax.net.ssl.SSLHandshakeException: no cipher suites in common
Что нужно проверить при отладке:
Например, в следующем примере я использую Java 8 с набором шифров по умолчанию. Сгенерированный мной сертификат использует ECDSA и SHA384, и, следовательно, когда TLS-соединение устанавливается между сервером и клиентом, я вижу, что согласованный набор шифров - это TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, включив отладку (System.setProperty("javax.net.debug", "ssl");).
Ниже приведен рабочий пример:
В качестве первого шага необходимо создать пару ключей и сертификат. В целях тестирования давайте создадим самозаверяющий сертификат и будем использовать один и тот же сертификат как для сервера, так и для клиента:
keytool -genkeypair -alias server -keyalg EC \
-sigalg SHA384withECDSA -keysize 256 -keystore servercert.p12 \
-storetype pkcs12 -v -storepass abc123 -validity 10000 -ext san=ip:127.0.0.1
Теперь создадим сервер:
package com.sapbasu.javastudy;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.Objects;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.TrustManagerFactory;
/*
* keytool -genkeypair -alias server -keyalg EC \
* -sigalg SHA384withECDSA -keysize 256 -keystore servercert.p12 \
* -storetype pkcs12 -v -storepass abc123 -validity 10000 -ext san=ip:127.0.0.1
*/
public class TLSServer {
public void serve(int port, String tlsVersion, String trustStoreName,
char[] trustStorePassword, String keyStoreName, char[] keyStorePassword)
throws Exception {
Objects.requireNonNull(tlsVersion, "TLS version is mandatory");
if (port <= 0) {
throw new IllegalArgumentException(
"Port number cannot be less than or equal to 0");
}
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
InputStream tstore = TLSServer.class
.getResourceAsStream("/" + trustStoreName);
trustStore.load(tstore, trustStorePassword);
tstore.close();
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
InputStream kstore = TLSServer.class
.getResourceAsStream("/" + keyStoreName);
keyStore.load(kstore, keyStorePassword);
KeyManagerFactory kmf = KeyManagerFactory
.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, keyStorePassword);
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(),
SecureRandom.getInstanceStrong());
SSLServerSocketFactory factory = ctx.getServerSocketFactory();
try (ServerSocket listener = factory.createServerSocket(port)) {
SSLServerSocket sslListener = (SSLServerSocket) listener;
sslListener.setNeedClientAuth(true);
sslListener.setEnabledProtocols(new String[] {tlsVersion});
// NIO to be implemented
while (true) {
try (Socket socket = sslListener.accept()) {
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
out.println("Hello World!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
Теперь создайте клиента:
package com.sapbasu.javastudy;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.Socket;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.Objects;
import javax.net.SocketFactory;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManagerFactory;
public class TLSClient {
public String request(InetAddress serverHost, int serverPort,
String tlsVersion, String trustStoreName, char[] trustStorePassword,
String keyStoreName, char[] keyStorePassword) throws Exception {
Objects.requireNonNull(tlsVersion, "TLS version is mandatory");
Objects.requireNonNull(serverHost, "Server host cannot be null");
if (serverPort <= 0) {
throw new IllegalArgumentException(
"Server port cannot be lesss than or equal to 0");
}
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
InputStream tstore = TLSClient.class
.getResourceAsStream("/" + trustStoreName);
trustStore.load(tstore, trustStorePassword);
tstore.close();
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
InputStream kstore = TLSClient.class
.getResourceAsStream("/" + keyStoreName);
keyStore.load(kstore, keyStorePassword);
KeyManagerFactory kmf = KeyManagerFactory
.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, keyStorePassword);
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(),
SecureRandom.getInstanceStrong());
SocketFactory factory = ctx.getSocketFactory();
try (Socket connection = factory.createSocket(serverHost, serverPort)) {
((SSLSocket) connection).setEnabledProtocols(new String[] {tlsVersion});
SSLParameters sslParams = new SSLParameters();
sslParams.setEndpointIdentificationAlgorithm("HTTPS");
((SSLSocket) connection).setSSLParameters(sslParams);
BufferedReader input = new BufferedReader(
new InputStreamReader(connection.getInputStream()));
return input.readLine();
}
}
}
Наконец, вот тест JUnit для проверки соединения:
package com.sapbasu.javastudy;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.net.InetAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.junit.jupiter.api.Test;
public class TLSServerClientTest {
private static final int SERVER_PORT = 8444;
private static final String TLS_VERSION = "TLSv1.2";
private static final int SERVER_COUNT = 1;
private static final String SERVER_HOST_NAME = "127.0.0.1";
private static final String TRUST_STORE_NAME = "servercert.p12";
private static final char[] TRUST_STORE_PWD = new char[] {'a', 'b', 'c', '1',
'2', '3'};
private static final String KEY_STORE_NAME = "servercert.p12";
private static final char[] KEY_STORE_PWD = new char[] {'a', 'b', 'c', '1',
'2', '3'};
@Test
public void whenClientSendsServerRequest_givenServerIsUp_returnsHelloWorld()
throws Exception {
TLSServer server = new TLSServer();
TLSClient client = new TLSClient();
System.setProperty("javax.net.debug", "ssl");
ExecutorService serverExecutor = Executors.newFixedThreadPool(SERVER_COUNT);
serverExecutor.submit(() -> {
try {
server.serve(SERVER_PORT, TLS_VERSION, TRUST_STORE_NAME,
TRUST_STORE_PWD, KEY_STORE_NAME, KEY_STORE_PWD);
} catch (Exception e) {
e.printStackTrace();
}
});
try {
String returnedValue = client.request(
InetAddress.getByName(SERVER_HOST_NAME), SERVER_PORT, TLS_VERSION,
TRUST_STORE_NAME, TRUST_STORE_PWD, KEY_STORE_NAME, KEY_STORE_PWD);
assertEquals("Hello World!", returnedValue);
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
}
Примечание. Сертификат (в данном примере servercert.p12) должен находиться в пути к классам. В этом примере я сохранил его в папке test / resources структуры папок Maven, чтобы тест JUnit мог получить его в пути к классам.
При использовании TLS / SSL используемые криптографические алгоритмы определяются наборами шифров. Сервер поддерживает набор наборов шифров (вы можете включить или отключить определенные наборы в соответствии с вашими потребностями и уровнем безопасности). Клиент также поддерживает набор наборов шифров. Во время установки соединения между клиентом и сервером согласовывается набор шифров, который будет использоваться. Предпочтение клиента будет соблюдаться при условии, что сервер поддерживает этот конкретный набор шифров.
Вы найдете список наборов шифров, поддерживаемых поставщиками Sun вплоть до Java 8 здесь.
Типичное имя набора шифров выглядит так:
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
Здесь,
ECDHE расшифровывается как Elliptic Curve Diffie Hellman Ephemeral. Это алгоритм обмена ключами. Вариант Elliptic (первый E) используется для производительности, тогда как вариант Ephemeral (последний E) предназначен для прямой секретности. Прямая секретность означает, что если злоумышленник продолжает записывать все сообщения через TLS и в более поздний момент каким-то образом завладевает закрытым ключом, он / она не может расшифровать прошлые записанные сообщения.
ECDSA - это алгоритм цифровой подписи, используемый для подписи ключа и используется для аутентификации (проверки целостности) общего секрета. ECDSA слабее и медленнее, чем другие алгоритмы аутентификации, такие как HMAC. Тем не менее, он используется для аутентификации с общим ключом, потому что верификатору не нужно знать секретный ключ, использованный для создания тега аутентификации. Сервер очень хорошо может использовать свой закрытый ключ для проверки целостности сообщения.
AES_128_GCM - после того, как общий секретный ключ используется обеими сторонами (обычно браузером и веб-сервером), для шифрования обмена сообщениями между сторонами используется алгоритм симметричного блочного шифрования. В данном конкретном случае используется блочный шифр AES со 128-битным ключом и режим аутентификации GCM.
SHA256 - Алгоритм хеширования для PRF
@Mathew, пожалуйста, проверьте включенные в настоящее время комплекты шифров, выполнив эту команду: jrunscript -e "print(java.util.Arrays.toString(javax.net.ssl.SSLServerSocketFactory.getDefault().getDefaultCipherSuites()))"jrunscript -e "print(java.util.Arrays.toString(javax.net.ssl.SSLServerSocketFactory.getDefault().getSupportedCipherSuites()))"
Я получаю список из множества разных наборов, включая TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA и т. д.
Когда я запускаю эту команду (запускаю свой сервер в режиме отладки с отладкой ssl) java -Djavax.net.debug=ssl Server.ForSaleServer, я получаю System property jdk.tls.client.cipherSuites is set to 'null' System property jdk.tls.server.cipherSuites is set to 'null' Ignoring disabled cipher suite: SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA Ignoring disabled cipher suite: SSL_DH_anon_EXPORT_WITH_RC4_40_MD5
@Matthew Теперь я предоставил рабочий пример вместе с дальнейшими пояснениями.
Ладно, я всю информацию перевариваю. Спасибо, что нашли время все это описать. Я пытаюсь это понять. Я дам тебе знать. Еще раз спасибо.
Спасибо, что нашли время сделать это. Я использовал приведенный выше код (измененный немного иначе, чем мне нравится), и он работает. Большое Вам спасибо. Вопрос, однако, возможно ли, чтобы клиент произвольно создавал свой собственный сертификат, чтобы каждое клиентское соединение шифровалось по-разному?
@Matthew Если клиент случайным образом создает сертификаты, вновь созданные сертификаты не могут быть добавлены в хранилище доверенных сертификатов сервера во время выполнения. Однако я думаю, что то, что вы хотите, вроде бы доступно в ECDHE (обратите внимание на этот акроним в наборе шифров), где последняя буква «E» означает «эфемерный», что указывает на то, что временная пара ключей создается в каждом соединении для достижения «пересылки». секретность », что означает, что если кто-то записывает все ваши сообщения и позже узнает ваш закрытый ключ, он не сможет расшифровать ранее записанные сообщения. Однако вам придется защищать закрытый ключ.
Да! Это то, что я ищу. Я пытаюсь реализовать приложение для Android, которое взаимодействует с машиной x86, на которой запущена Java, прослушивающая SSLServerSocket. Мне все еще трудно понять, как получить все сертификаты, ключи или что-то еще, что нужно для этого. Я загрузил графический интерфейс KeyStore Explorer, чтобы, надеюсь, мне помочь. Есть лакомые кусочки?
@SaptarshiBasu, пожалуйста, дайте мне совет? Я пробовал несколько вещей, но очень застрял, не зная, как действовать. stackoverflow.com/questions/56523042/…
На стороне сервера, почему вы принимаете обычный Socket вместо SSLSocket?
Вы неправильно задаете системные свойства.
Серверу нужны javax.net.keyStore (не trustStore) и javax.net.keyStorePassword - а иногда и javax.net.keyStoreType, но, вероятно, не в вашем случае. Для сервера, использующего самозаверяющий сертификат, клиенту требуется, чтобы магазин доверия содержал этот сертификат, и ему вообще не нужно какое-либо хранилище ключей. Хотя это не задокументировано, если они находятся в одной системе, вы можете использовать серверное хранилище ключей (содержащее privateKeyEntry) в качестве клиентского хранилища доверенных сертификатов, а JSSE автоматически обрабатывает листовой сертификат privateKey как trustCert. В любой другой ситуации вам необходимо извлечь сертификат сервера (не ключ), скопировать / отправить его клиенту и использовать keytool -importcert для помещения сертификата (не ключа) в хранилище ключей файл, которое используется в качестве хранилища доверенных сертификатов; это может быть либо пользовательский файл (идентифицируемый с помощью sysprops), либо хранилище доверенных сертификатов по умолчанию в JRE / lib / security / cacerts (за исключением текущей Windows, если JRE находится под \Program Files[ (x86)], как это используется установщиком по умолчанию, потому что теперь Windows беспорядок с людьми, которые пытаются поменять там файлы).
И эти sysprops (если они используются) должны быть установлены ДО ЗАГРУЗКИ КЛАССОВ JSSE - установка их при вызове SSL[Server]SocketFactory обычно слишком поздно. Обычно они устанавливаются в командной строке, которая вызывает Java с опцией -Dname=value; это гарантированно будет сделано достаточно рано. Если вы не можете этого сделать, поместите вызовы setProperty в начало метода main или эквивалент для графического интерфейса.
Этот проявляет означает `` нет общего / общего шифра '', потому что без ключа + сертификата сервер не может поддерживать шифровальные комплекты, которые выполняют аутентификацию сервера, а Java / JSSE по умолчанию включает только шифровальные комплекты, которые выполняют аутентификацию сервера, потому что другие небезопасны во многих / большинстве ситуаций .
Я уверен, что отвечал на вторую часть раньше, но не могу найти обман. Первая часть встречается во многих ответах, но обычно не представлена четко в вопросе.
Сейчас сервер и клиент находятся на одной машине в одной среде IDE. Я щелкаю файл сервера «запустить», затем щелкаю файл клиента и нажимаю «запустить». Та же машина, те же каталоги, все.