8

I'm working on encrypting a tcp connection between a server and a client. In the course of research and testing I'm leaning towards using secret key encryption. My problem is that I cannot find any tutorials on how to implement this feature. The tutorials I have found revolve around one-shot https requests, all I need is a SSL Socket.

The code I've written so far is below. I'm almost certain that it needs to be extended, I just don't know how. Any help is appreciated.

private ServerSocketFactory factory;
private SSLServerSocket serverSocket;

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

Server code for accepting client connections

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

I just don't know how to actually do the handshake.

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

Warren Dew
  • 8,790
  • 3
  • 30
  • 44
Kyte
  • 834
  • 2
  • 12
  • 27
  • 1
    Interested in an answer here with hopefully an example. I've wanted to use SSL sockets before, but kind of like you, never quite understood how the keys were exchanged, but then I never put much effort in, just kind of read the docs and was like, hmm, that doesn't sound straightforward, lol. – Jared Mar 29 '14 at 23:13
  • 2
    I've provided what I hope amounts to a tutorial in my answer below. In your case, you mostly just need to move the `socket.startHandshake` call to the client, getting the client's SSLSocket appropriately - and you need to make sure you have your certificates straight. – Warren Dew Mar 30 '14 at 01:33
  • 1
    @WarrenDew, too bad we can't downvote comments, but what you've said above really doesn't make sense. `socket.startHandshake` is a Java API call that doesn't have that much to do with who actually starts the handshake at the protocol level. It can be called on either client or server side (although it mostly makes sense on the server as part of a renegotiation). It's generally optional. "Moving" it to the client side shouldn't really matter. – Bruno Mar 30 '14 at 11:59

2 Answers2

18

SSL socket connections are well supported in Java and are likely a good choice for you. The one thing to understand in advance is that SSL provides both encryption and server authentication; you can't easily get just the encryption. For reference, the encryption protects against network eavesdropping, while the server authentication protects against "man in the middle" attacks, where the attacker acts as a proxy between the client and the server.

Since authentication is an integral part of SSL, the server will need to provide an SSL certificate, and the client will need to be able to authenticate the certificate. The server will need a "key store" file where its certificate is stored. The client will need a "trust store" file where it stores the certificates it trusts, one of which must either be the server's certificate, or a certificate from which a "chain of trust" can be traced to the server's certificate.

Note that you do not have to know anything about the ins and outs of SSL in order to use Java SSL sockets. I do think it is interesting to read through information on how SSL works, for example in the Wikipedia article on TLS, but the complicated multistep handshake and the setup of the actual connection encryption is all handled under the covers by the SSLServerSocket and SSLSocket classes.

The code

All of the above is just background information to explain some of the following code. The code assumes some familiarity with regular, unencrypted sockets. On the server, you will need code like this:

/**
 * 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;
}

The SSLServerSocket can then be used exactly like you would use any other ServerSocket; the authentication, encryption and decryption will be completely transparent to the calling code. In fact, the cognate function in my own code declares a return type of just plain ServerSocket, so the calling code can't get confused.

Note: if you want to use the JRE's default cacerts file as your key store file, you can skip the line creating the SSLContext, and use ServerSocketFactory.getDefault() to get the ServerSocketFactory. You will still have to install the server's public/private key pair into the key store file, in this case the cacerts file.

On the client, you will need code like this:

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;
}

As in the case of the SSLServerSocket in the server code, the returned SSLSocket here is used just like a regular Socket; I/O into and out of the SSLSocket is done with unencrypted data, and all the cryptographic stuff is done inside.

As with the server code, if you want to use the default JRE cacerts file as your trust store, you can skip creation of the SSLContext and use SSLSocketFactory.getDefault() instead of sslContext.getSocketFactory(). In this case, you will only need to install the server's certificate if the server's certificate was self signed or not otherwise issued by one of the major certificating authorities. In addition, to ensure you aren't trusting a certificate that is legitimately issued within a certificate chain you trust, but to an entirely different domain than you are trying to get to, you should add the following (untested) code just after the line where you create the SSLSocket:

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

This would also apply if you are using your own trust store file, but trusting all certificates issued by one or more certificating authorities in that file, or trusting a number of certificates for different servers in that file.

Certificates, key stores, and trust stores

Now for the hard, or at least, slightly harder part: generating and installing the certificates. I recommend using the Java keytool, preferably version 1.7 or above, to do this work.

If you are creating a self signed certificate, first generate the server's keypair from the command line with a command like the following: 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. Substitute your own names and values. In particular, this command creates a key pair that expires in 365242 days - 1000 years - for a server that will be at IP address 192.168.1.129. If the clients will be finding the server through the domain name system, use something like san=dns:server.example.com instead of san=ip:192.168.1.129. For more information on keytool options, use man keytool.

You will be prompted for the key store's password - or to set the key store's password, if this is a new key store file - and to set the password for the new key pair.

Now, export the server's certificate using keytool -export -alias server -file server.cer -keystore server_key_store_file -rfc -v. This creates a server.cer file containing the certificate with the server's public key.

Finally, move the server.cer file to the client machine and install the certificate into the client's trust store, using something like, keytool -import -alias server -file server.cer -keystore client_trust_store_file -v. You will be prompted for the password to the trust store file; the prompt will say "Enter keystore password", since the Java keytool works with both key store files and trust store files. Note that if you are using the default JRE cacerts file, the initial password is changeit, I believe.

If you are using a certificate purchased from a generally recognized certificating authority, and you're using the default JRE cacerts file on the client, you only have to install the certificate in the server's key store file; you don't have to mess with the client's files. Server installation instructions should be provided by the certificating authority, or again you can check man keytool for instructions.

There's a tremendous amount of mystique surrounding sockets and, especially, SSL sockets, but they're actually quite easy to use. In many cases, ten lines of code will avoid the need for complex and fragile messaging or message queueing infrastructure. Good for you for considering this option.

Warren Dew
  • 8,790
  • 3
  • 30
  • 44
  • 1
    Can you explain how to generate the certificates? It would be nice to also explain how to give the certificate to the client as well, but I think that may be off topic here. – Jared Mar 30 '14 at 00:21
  • 1
    I've added a description of how to generate the key pair and associated certificate, and if it's a self generated certificate, how to install it in the client. – Warren Dew Mar 30 '14 at 00:55
  • 1
    Great answer, thanks. I will be bookmarking this page for further reference. – Jared Mar 30 '14 at 00:59
  • 2
    @WarrenDew, this is by far the most concise tutorial I've seen. I'll give this a go, thanks! – Kyte Mar 30 '14 at 02:02
  • 1
    You're both welcome! Hope this works for you, Kyte. – Warren Dew Mar 30 '14 at 04:18
  • Good answer, but you're missing hostname verification on the client side. Using [`sslParameters.setEndpointIdentificationAlgorithm("HTTPS")`](http://stackoverflow.com/a/18174689/372643) is probably the best way to do this (even for non-HTTPS). In addition, you don't need to use `startHandshake`, it will all be done for you when you start using the I/O streams. – Bruno Mar 30 '14 at 11:41
  • 2
    @Jared, you should read [this question](http://security.stackexchange.com/q/20803/2435) on Security.SE. The server doesn't encrypt a certificate at all. The client encrypts a pre-master secret (RSA key exchange) with the server's pubkey (or something a bit more complex for DHE exchange, where the server signs a newly-generated key). From then on, it's all symmetric crypto, no public/private keys. – Bruno Mar 30 '14 at 11:44
  • @Bruno I added the information on hostname verification in cases where one might trust the wrong certificate - thanks. As for `startHandshake`, I use it to force immediate handshake negotiation - and, possibly, connection, as the Javadoc does not make it clear when the socket actually connects - even in cases where the connection may be idle for some time, with no data being transferred. – Warren Dew Mar 30 '14 at 17:49
  • 1
    @Bruno Thanks, I'm going to delete my comments (so it doesn't confuse anyone). – Jared Mar 30 '14 at 19:08
  • If you want help generating the certificates, have a look at https://gpotter2.github.io/tutos/en/sslsockets – Cukic0d May 13 '19 at 16:35
  • I am trying to SLLSockets on my LAN. Is there a way I can skip the certificates? I want the data to be encrypted so that only the client and server can read it, but I don't need to validate the server's authenticity. I am also not concerned about MITM attacks. – Cardinal System May 24 '19 at 18:07
  • It seems as if the provided code doesn't work anymore. "SSLConnections" isn't found. Can anyone update this post with updated code, because I think it's a very helpful one, if it wasn't for the old code. – Gereon99 Dec 27 '19 at 02:03
1

You've started the handshake. That's all you have to do: in fact you don't even have to do that, as it will happen automatically. All you have to do now is normal input and output, same as you would with a plaintext socket.

user207421
  • 305,947
  • 44
  • 307
  • 483
  • I would note that usually the handshake is started by the client. – Warren Dew Mar 30 '14 at 00:56
  • @WarrenDew I would note that as far as the application is concerned it is automatic, as I said, and also that either side, or indeed both sides, can call startHandshake(). Over the wire it is *always* started by the client (in SSL terms), i.e. with a ClientHello message, but it's not something the OP needs to be concerned about. – user207421 Mar 30 '14 at 00:59
  • As far as the application code is concerned, that is true. However, from a configuration standpoint, it's the side that does not start the handshake that needs to have a key store with a public/private key pair. The side that starts the handshake just needs a trust store with the appropriate certificate. – Warren Dew Mar 30 '14 at 01:09
  • 1
    @WarrenDew, I think what EJP is referring to is the fact that "*any attempt to read or write application data on this socket causes an implicit handshake*" (see introduction of [SSLSocket Javadoc](http://docs.oracle.com/javase/7/docs/api/javax/net/ssl/SSLSocket.html)). Neither the server nor the client needs to do this explicitly in Java. By definition, the initial handshake is *always* started by the SSL/TLS client (see [glossary](http://tools.ietf.org/html/rfc5246#appendix-B)), and you more or less always want to authenticate the server in any SSL/TLS connection indeed. – Bruno Mar 30 '14 at 11:52
  • @Bruno I don't disagree with all that. However, in the general case, it cannot be assumed that part of the application desired to be the client is necessarily the first to read or write data on the connection. Thus, there may be cases where you do want to explicitly start the handshake from the client. There are definitely no cases where you want to explicitly start the handshake from the server, as the question's original code had it. I was not disagreeing with EJP; I was merely adding additional related information that might be relevant to some readers. – Warren Dew Mar 30 '14 at 15:08
  • @WarrenDew I don't know what this has to do with my answer. Nor does the first side to do application I/O determine which is the SSL client. It is configured into the SSL socket which is the client and which the server. Your own answer appears comprehensive enough, although I haven't read it, that you don't need to polish the apple by adding information to mine, especially misinformation. – user207421 Mar 30 '14 at 16:50
  • @WarrenDew "*[...] it cannot be assumed that part of the application desired to be the client is necessarily the first to read or write data on the connection*". I think you can make that assumption on the `SSLSocket`. In a server-talks-first proto. (e.g. IMAPS), the client will first read the `InputStream`, triggering the handshake. Same for a client-talks-first proto. (e.g. HTTPS) with the client writing to the `OutputStream`. Where there's an upgrade to SSL (e.g. SMTP+STARTLS), what happens before isn't on the `SSLSocket`, and using its I/O stream will also trigger the handshake. – Bruno Mar 30 '14 at 18:37