0

I'm creating server and client programs, and after reading about all that can go wrong with encryption I am still wondering if I am doing something terribly destructive.
I often read strong reactions regarding how many things some programmers do are terrible and should never be done. I wish for such reactions to the code below.

I have provided an absolutely minimal setup in java that will run without any extra libraries. It's all 1 function (1 for the server, 1 for the client) so it's easy to follow.

Overview: It currently generates an RSA priv/pub pair and exchanges a client-generated symmetric AES key with the server. The server also signs the client's public key so the client knows the server is aware of the client. In addition, the client checks if the server has a trusted public key. Transmitted data is encrypted via AES.

MinimalServer.java

    public class MinimalServer
    {
        public static void main(String[] args)
        {
            try
            {
                java.net.ServerSocket       server_socket;
                java.net.Socket             client_socket;
                java.io.InputStream         input_from_client;
                java.io.OutputStream        output_to_client;
                java.security.PrivateKey    server_private_key;
                java.security.PublicKey     server_public_key;
                java.security.PublicKey     client_public_key;
                java.security.Key           symmetric_key;

                // Generate a new public/private keypair
                java.security.KeyPairGenerator keygen = java.security.KeyPairGenerator.getInstance("RSA");
                java.security.SecureRandom random = java.security.SecureRandom.getInstance("SHA1PRNG", "SUN");
                keygen.initialize(512, random);
                java.security.KeyPair pair = keygen.generateKeyPair();
                java.io.FileOutputStream output = new java.io.FileOutputStream("publickey");
                output.write(pair.getPublic().getEncoded());
                output = new java.io.FileOutputStream("privatekey");
                output.write(pair.getPrivate().getEncoded());

                // Load the public and private key files into memory
                java.nio.file.Path path = java.nio.file.Paths.get("publickey");
                byte[] public_key_raw = java.nio.file.Files.readAllBytes(path);
                java.security.spec.X509EncodedKeySpec pubkey_spec = new java.security.spec.X509EncodedKeySpec(public_key_raw);
                java.security.KeyFactory key_factory = java.security.KeyFactory.getInstance("RSA");
                server_public_key = key_factory.generatePublic(pubkey_spec);

                path = java.nio.file.Paths.get("privatekey");
                byte[] private_key_raw = java.nio.file.Files.readAllBytes(path);
                java.security.spec.PKCS8EncodedKeySpec privkey_spec = new java.security.spec.PKCS8EncodedKeySpec(private_key_raw);
                key_factory = java.security.KeyFactory.getInstance("RSA");
                server_private_key = key_factory.generatePrivate(privkey_spec);

                // Wait for clients to connect
                server_socket = new java.net.ServerSocket(7777);
                client_socket = server_socket.accept();
                client_socket.setSoTimeout(5000);
                input_from_client = client_socket.getInputStream();
                output_to_client = client_socket.getOutputStream();

                // Send server's public key to client
                output_to_client.write(server_public_key.getEncoded());

                // Get the public key from the client
                byte[] bytes = new byte[512];
                int number = input_from_client.read(bytes);
                bytes = java.util.Arrays.copyOf(bytes, number);
                pubkey_spec = new java.security.spec.X509EncodedKeySpec(bytes);
                key_factory = java.security.KeyFactory.getInstance("RSA");
                client_public_key = key_factory.generatePublic(pubkey_spec);

                // Sign the client's public key
                java.security.Signature signature = java.security.Signature.getInstance("SHA1withRSA");
                signature.initSign(server_private_key);
                signature.update(client_public_key.getEncoded());
                byte[] signed_signature = signature.sign();

                // Send the certificate to the client
                output_to_client.write(signed_signature);

                // Wait for the symmetric key from the client
                bytes = new byte[512];
                int code = input_from_client.read(bytes);
                bytes = java.util.Arrays.copyOf(bytes, code);
                javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance("RSA/ECB/PKCS1PADDING");
                cipher.init(javax.crypto.Cipher.DECRYPT_MODE, server_private_key);
                bytes = cipher.doFinal(bytes);
                symmetric_key = new javax.crypto.spec.SecretKeySpec(bytes, "AES");

                // Read super secret incoming data
                bytes = new byte[512];
                code = input_from_client.read(bytes);
                bytes = java.util.Arrays.copyOf(bytes, code);
                cipher = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding");
                byte[] iv = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
                javax.crypto.spec.IvParameterSpec ivspec = new javax.crypto.spec.IvParameterSpec(iv);
                cipher.init(javax.crypto.Cipher.DECRYPT_MODE, symmetric_key, ivspec);
                byte[] raw = cipher.doFinal(bytes);
                System.out.println(new String(raw));

                // Send a confirmation to the client
                cipher = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding");
                ivspec = new javax.crypto.spec.IvParameterSpec(iv);
                cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, symmetric_key, ivspec);
                bytes = cipher.doFinal("OK".getBytes());
                output_to_client.write(bytes);
                server_socket.close();
            }
            catch (Exception exc)
            {
                exc.printStackTrace();
            }
        }
    }

MinimalClient.java

    public class MinimalClient
    {
        public static void main(String[] args)
        {
            try
            {
                java.net.Socket             client_socket = null;
                java.io.InputStream         input_from_server = null;
                java.io.OutputStream        output_to_server = null;
                java.security.PrivateKey    client_private_key = null;
                java.security.PublicKey     server_public_key, trusted_server = null;
                java.security.PublicKey     client_public_key = null;
                java.security.Key           symmetric_key = null;
                String                      data = "super secret data";

                // Load trusted server pubkey into memory
                java.nio.file.Path path = java.nio.file.Paths.get("publickey");
                byte[] trusted = java.nio.file.Files.readAllBytes(path);
                java.security.spec.X509EncodedKeySpec pubkey_spec = new java.security.spec.X509EncodedKeySpec(trusted);
                java.security.KeyFactory key_factory = java.security.KeyFactory.getInstance("RSA");
                trusted_server = key_factory.generatePublic(pubkey_spec);

                // Generate new RSA keypair
                java.security.KeyPairGenerator keygen = java.security.KeyPairGenerator.getInstance("RSA");
                java.security.SecureRandom random = java.security.SecureRandom.getInstance("SHA1PRNG", "SUN");
                keygen.initialize(512, random);
                java.security.KeyPair pair = keygen.generateKeyPair();
                client_private_key = pair.getPrivate();
                client_public_key = pair.getPublic();

                // Then generate the symmetric key
                javax.crypto.KeyGenerator kg = javax.crypto.KeyGenerator.getInstance("AES");
                random = new java.security.SecureRandom();
                kg.init(random);
                symmetric_key = kg.generateKey();

                // Connect to host
                client_socket = new java.net.Socket("localhost", 7777);
                client_socket.setSoTimeout(5000);
                output_to_server = client_socket.getOutputStream();
                input_from_server = client_socket.getInputStream();

                // Send client's public key to the server
                output_to_server.write(client_public_key.getEncoded());

                // Wait for the server to send its public key, and load it into memory
                byte[] bytes = new byte[1024];
                int number = input_from_server.read(bytes);
                bytes = java.util.Arrays.copyOf(bytes, number); 
                pubkey_spec = new java.security.spec.X509EncodedKeySpec(bytes);
                key_factory = java.security.KeyFactory.getInstance("RSA");
                server_public_key = key_factory.generatePublic(pubkey_spec);

                // Check if trusted
                if (!java.util.Arrays.equals(server_public_key.getEncoded(), trusted_server.getEncoded()))
                    return;

                // Get server certificate (basically the client's signed public key)
                int length = input_from_server.read(bytes);

                // Verify the server's authenticity (signature)
                bytes = java.util.Arrays.copyOf(bytes, length);
                java.security.Signature sig = java.security.Signature.getInstance("SHA1withRSA");
                sig.initVerify(server_public_key);
                sig.update(client_public_key.getEncoded());
                if (!sig.verify(bytes))
                    return;

                // Send the symmetric key encrypted via RSA
                javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance("RSA/ECB/PKCS1PADDING");
                cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, server_public_key);
                output_to_server.write(cipher.doFinal(symmetric_key.getEncoded()));


                // Send the data that must remain a secret
                cipher = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding");
                byte[] iv = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
                javax.crypto.spec.IvParameterSpec ivspec = new javax.crypto.spec.IvParameterSpec(iv);
                cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, symmetric_key, ivspec);
                byte[] raw = cipher.doFinal(data.getBytes());
                output_to_server.write(raw);

                // Get a response
                bytes = new byte[1024];
                length = input_from_server.read(bytes);
                bytes = java.util.Arrays.copyOf(bytes, length);
                cipher = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding");
                ivspec = new javax.crypto.spec.IvParameterSpec(iv);
                cipher.init(javax.crypto.Cipher.DECRYPT_MODE, symmetric_key, ivspec);
                raw = cipher.doFinal(bytes);
                System.out.println("Response from server: '" + new String(raw) + "'");
            }
            catch (Exception exc)
            {
                exc.printStackTrace();
            }
        }
    }

So my question is: "What am I doing wrong?" or rather "What do I need to do to absolutely positively have a connection that is absolutely positively secure?". Also; assume that the "publickey" file is merely a shared file on all clients. The generation of a new public/private key on the server can be omitted, it's just there to show that I may be doing something wrong when generating a public/private key pair. A client will always generate a new RSA keypair as well as AES keypair for each connection. Is this secure?

Ultimate Hawk
  • 580
  • 4
  • 16
  • 1
    To absolutely positively ensure nothing goes wrong, you need to turn off the machine and burn it. However, you can minimize your risk by using libraries that many users use (and debug) regularly, and avoid trying to reinvent the wheel when it comes to security. Even then, you are not absolutely positively safe. for example - [HeartBleed](http://en.wikipedia.org/wiki/Heartbleed) – amit Feb 23 '15 at 11:04
  • @amit I'm attempting to use the libraries provided by Java. I'll not attempt to go down to the bignum level and generate primes for RSA. Are you implying there is something higher-levelled than this? I've looked at TLS/SSL but that seems to be useful only when clients can be trusted as well (aka need to store their own pub/priv keys). If you think maybe this is a better method then I'd love to hear how simple message passing could work and be secure. – Ultimate Hawk Feb 23 '15 at 11:06
  • @BourgondAries Don't use client certificates for authentication unless you're using smart cards, in which case the smart card will have the protocol implementation built in. – chrylis -cautiouslyoptimistic- Feb 23 '15 at 11:08
  • 1
    And quite apart from the security aspects there's more basic issues with this code like failing to close many of your output streams when you've finished writing. – Ian Roberts Feb 23 '15 at 11:12
  • To verify the authenticity of the client, i.e. making sure you're talking to who you think you're talking to you can implement some ticketing system (http://en.wikipedia.org/wiki/Kerberos_(protocol)) and incorporate that into your security model which should help with your argument against "trusting clients". Having said that, as @amit pointed out, there's no such thing as "absolutely positively secure". You can just try to make it harder to crack. – kha Feb 23 '15 at 11:12
  • @Ultimate hawk did you manage to solve the exercise? I'm very interested in seeing it !! PLEASE!! :( – Montse Mkd May 07 '18 at 18:24

2 Answers2

1

The most important key is not to reinvent security protocols or implementations; they're tricky and have all sorts of edge cases. Use packaged, tested implementations. All of the major servlet containers, including Tomcat, Jetty, and Undertow, provide SSL support built-in, and every conceivable target platform knows how to make HTTPS requests.

chrylis -cautiouslyoptimistic-
  • 75,269
  • 21
  • 115
  • 152
  • Would something like http://stilius.net/java/java_ssl.php suffice as a plenty secure example? Does not the client here know about the certificate such that any client can fake itself for being the server? Or is that data secured by the keyStorePassword and trustStorePassword? – Ultimate Hawk Feb 23 '15 at 11:21
  • @BourgondAries You're talking about *man-in-the-middle (MITM)* attacks. The current known solutions require the client to have some sort of preinstalled certificate that provides assurance that the server is correctly identified (in TLS, these preinstalled root certificates are used to sign per-server certificates). If necessary, which it *probably* isn't, you could ship your application with your own root certificate in a trust store. This doesn't need to be private. – chrylis -cautiouslyoptimistic- Feb 23 '15 at 11:30
  • Do you perhaps know of any method to allow and check for multiple different certified servers? The application I am developing will allow a client to connect to known/certified hosts securely. – Ultimate Hawk Feb 23 '15 at 19:02
  • @BourgoundAries That's the entire point of having a root certificate (deployed) that signs server certificates. That you're not already familiar with this infrastructure, which is fundamental to modern crypto implementation, argues loudly to learn and use existing systems rather than trying to develop your own. – chrylis -cautiouslyoptimistic- Feb 24 '15 at 02:14
0

One thing you seem to do wrong is using a constant initialization vector.

See here for a discussion: https://crypto.stackexchange.com/questions/2576/encryption-with-constant-initialization-vector-considered-harmful

Do you have a reason to implement the secure connection yourself? It seems better in such casees to use HTTPS in combination with client and server certificates. (Thought that isn't exactly easy to setup either unfortunately.)

Community
  • 1
  • 1
Joe23
  • 5,683
  • 3
  • 25
  • 23
  • So the idea is to send the iv over via RSA along with AES? – Ultimate Hawk Feb 23 '15 at 11:14
  • As far as I know (very little), the iv is needed to create variation in the encrypted data. It is not a secret like the key. See here for a more authorative answer: http://stackoverflow.com/a/1540663/136247 – Joe23 Feb 23 '15 at 11:27