Java: Is it secure to send a random symmetric key encrypted with servers public key to this server?

Cookie04 :

I'm unsure if this question belongs to stackoverflow or the crypto stackexchange, but because it contains source code I'm going to post it here.

Here goes my question: I wrote two programs, one is the client, and the other is the server. They communicate securely by encrypting with AES. The client generates a random symmetric key, encrypts it with the public key of the server and sends it to the server. The server can then decrypt the secret key and use it to communicate with the client.

I heard about the diffie hellman key exchange, and wondered if my code is as secure as that. Is it risky to do it my way, and is there any kind of advantage using the diffie hellman key exchange?

Clients source:

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.util.HashMap;
import java.util.concurrent.TimeoutException;

public class Client {
    public static HashMap<String, String> arguments = new HashMap<>();
    public static String sessionKey;
    public static void start(String[] args) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InterruptedException, TimeoutException {
        if(args.length % 2 != 0) {
            System.out.println("Ungültige Argumentelänge: Muss gerade sein, Muster: Feld Wert");
            return;
        }
        for(int i = 0; i < args.length; i+=2) {
            switch (args[i].toLowerCase()) {
                case "help":
                    System.out.println("");
                    break;
                case "ip":
                    arguments.put("ip", args[i + 1].toLowerCase());
                    break;
                case "publickey":
                    arguments.put("publickey", args[i + 1]);
                    break;
                default:
                    System.out.println("Unbekannte Option: " + args[i]);
                    break;
            }
        }
        if(arguments.containsKey("ip") && arguments.containsKey("publickey")) {
            Socket s = new Socket();
            s.connect(new InetSocketAddress(arguments.get("ip"), 6577));
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
            PublicKey publicKey = Main.publicKeyFromString(String.join("", Main.read(new File(arguments.get("publickey")))));
            sessionKey = Main.generateSessionKey();
            String encryptedSessionKey = Main.encrypt(sessionKey, publicKey, Main.RSA);
            bw.write(encryptedSessionKey);
            bw.flush();
            SecretKeySpec key = Main.StringtoKey(sessionKey);
            BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
            BufferedReader inbr = new BufferedReader(new InputStreamReader(System.in));
            while (true) {
                if(inbr.ready()) {
                    String line = Main.readAsMuchAsPossible(System.in);
                    bw.write(Main.encrypt(line, key, "AES") + "\n");
                    bw.flush();
                }
                if(br.ready()) {
                    String line2 = Main.readAsMuchAsPossible(s.getInputStream());
                    System.out.println(Main.decrypt(line2, key, "AES"));
                }
            }
            //System.out.println(Main.decrypt(read, , "AES"));
        }
    }
}

Servers source:

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.concurrent.TimeoutException;

public class Server {
    public static HashMap<String, String> arguments = new HashMap<>();
    static PrivateKey privateKey;
    public static void start(String[] args) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InterruptedException, NoSuchPaddingException, BadPaddingException, IllegalBlockSizeException, InvalidKeyException, TimeoutException {
        if(args.length % 2 != 0) {
            System.out.println("Ungültige Argumentelänge: Muss gerade sein, Muster: Feld Wert");
            return;
        }
        for(int i = 0; i < args.length; i+=2) {
            if (args[i].toLowerCase().equals("privkey")) {
                arguments.put("privkey", args[i + 1]);
            } else {
                System.out.println("Unbekannte Option: " + args[i]);
            }
        }
        if(arguments.containsKey("privkey")) {
            String encodedPrivateKey = String.join("", Main.read(new File(arguments.get("privkey"))));
            privateKey = Main.privateKeyFromString(encodedPrivateKey);
            ServerSocket serverSocket = new ServerSocket(6577);
            while (true) {
                final Socket socket = serverSocket.accept();
                /*Thread t = new Thread(() -> {
                    try {
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                });
                t.start();*/
                System.out.println("Verbindung empfangen!");
                BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
                int timeout = 100;
                int current = 0;
                while (!br.ready()) {
                    Thread.sleep(100);
                    if (current >= timeout) {
                        System.out.println("Timeout erreicht, Client reagiert nicht, Verbindung wird geschlossen!");
                        socket.close();
                        return;
                    }
                    current++;
                }
                String read = Main.readFromInputStream(socket.getInputStream(), 10000, 100);
                if (read.equals("")) {
                    System.out.println("read ist leer");
                    return;
                }
                String sessionkey = Main.decrypt(read, privateKey, Main.RSA);
                SecretKeySpec key = Main.StringtoKey(sessionkey);
                BufferedReader inbr = new BufferedReader(new InputStreamReader(System.in));
                while (true) {
                    if (inbr.ready()) {
                        String line2 = Main.readAsMuchAsPossible(System.in);
                        bw.write(Main.encrypt(line2, key, "AES") + "\n");
                        bw.flush();
                    }
                    if (br.ready()) {
                        String line2 = br.readLine();//Main.readAsMuchAsPossible(socket.getInputStream());
                        String decrypted = Main.decrypt(line2, key, "AES");
                        if(decrypted.startsWith("cmd")) {
                            String[] arr = decrypted.split(" ");
                            String cmd = String.join(" ", Arrays.copyOfRange(arr, 1, arr.length));
                            System.out.println("cmd: " + cmd);
                            Process process = Runtime.getRuntime().exec(cmd);
                            InputStream in = process.getInputStream();
                            do {
                                StringBuilder complete = new StringBuilder();
                                while (in.available() > 0) {
                                    complete.append((char)in.read());
                                }
                                if(!complete.toString().equals(""))  {
                                    bw.write(Main.encrypt("processout: \"" + complete.toString() + "\"", key, "AES"));
                                    bw.flush();
                                }
                            } while (process.isAlive());
                        }
                        System.out.println(decrypted);
                    }
                }
            }
        } else {
            System.out.println("Kein privater Schlüssel angegeben!");
        }
    }
}

And I have a util class that's what "Main" is referring to:

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Random;
import java.util.concurrent.TimeoutException;

public class Main {
    public static final String RSA = "RSA";

    public static String[] read(File f) throws IOException {
        BufferedReader br = new BufferedReader(new FileReader(f));
        ArrayList<String> lines = new ArrayList<>();
        String line;
        while((line = br.readLine()) != null) {
            lines.add(line);
        }
        String[] array = new String[lines.size()];
        lines.toArray(array);
        return array;
    }

    public static PrivateKey privateKeyFromString(String s) throws InvalidKeySpecException, NoSuchAlgorithmException {
        return privateKeyFromBytes(Base64.getDecoder().decode(s));
    }

    public static PrivateKey privateKeyFromBytes(byte[] bytes) throws InvalidKeySpecException, NoSuchAlgorithmException {
        PKCS8EncodedKeySpec ks = new PKCS8EncodedKeySpec(bytes);
        KeyFactory kf = KeyFactory.getInstance(RSA);
        return kf.generatePrivate(ks);
    }

    public static String privateKeyToString(PrivateKey k) {
        return Base64.getEncoder().encodeToString(privateKeyToBytes(k));
    }

    public static byte[] privateKeyToBytes(PrivateKey k) {
        PKCS8EncodedKeySpec ks = new PKCS8EncodedKeySpec(k.getEncoded());
        return ks.getEncoded();
    }

    public static PublicKey publicKeyFromString(String s) throws NoSuchAlgorithmException, InvalidKeySpecException {
        return publicKeyFromBytes(Base64.getDecoder().decode(s));
    }

    public static PublicKey publicKeyFromBytes(byte[] bytes) throws InvalidKeySpecException, NoSuchAlgorithmException {
        X509EncodedKeySpec ks = new X509EncodedKeySpec(bytes);
        KeyFactory kf = KeyFactory.getInstance(RSA);
        return kf.generatePublic(ks);
    }

    public static String publicKeyToString(PublicKey k) {
        return Base64.getEncoder().encodeToString(publicKeyToBytes(k));
    }

    public static byte[] publicKeyToBytes(PublicKey k) {
        X509EncodedKeySpec ks = new X509EncodedKeySpec(k.getEncoded());
        return ks.getEncoded();
    }

    public static String readFromInputStream(InputStream i, int timeoutmillis, int period) throws IOException, InterruptedException, TimeoutException {
        int current = 0;
        while (i.available() == 0) {
            Thread.sleep(period);
            current = current + period;
            if(current > timeoutmillis) {
                throw new TimeoutException("Timeout erreicht");
            }
        }
        ArrayList<Character> buffer = new ArrayList<>();
        while(i.available() > 0) {
            int c = i.read();
            if(c == -1) {
                return BuffertoString(buffer);
            } else {
                buffer.add((char) c);
            }
        }
        return BuffertoString(buffer);
    }
    public static String BuffertoString(ArrayList<Character> buffer) {
        StringBuilder out = new StringBuilder();
        for(char a : buffer) {
            out.append(a);
        }
        return out.toString();
    }

    public static String encrypt(String msg, Key key, String verfahren) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        return Base64.getEncoder().encodeToString(encryptBytes(msg.getBytes(), key, verfahren));
    }

    public static byte[] encryptBytes(byte[] msg, Key key, String verfahren) throws BadPaddingException, IllegalBlockSizeException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException {
        Cipher c = Cipher.getInstance(verfahren);
        c.init(Cipher.ENCRYPT_MODE, key);
        return c.doFinal(msg);
    }

    public static String decrypt(String msg, Key key, String verfahren) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        return new String(decryptBytes(Base64.getDecoder().decode(msg), key, verfahren));
    }

    public static byte[] decryptBytes(byte[] msg, Key key, String verfahren) throws BadPaddingException, IllegalBlockSizeException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException {
        Cipher c = Cipher.getInstance(verfahren);
        c.init(Cipher.DECRYPT_MODE, key);
        return c.doFinal(msg);
    }

    static final String lowercase = "abcdefghijklmnopqrstuvwxyz";
    static final String uppercase = lowercase.toUpperCase();
    static final String digits = "0123456789";
    static final char[] combined = (uppercase + lowercase + digits).toCharArray();

    public static String generateSessionKey() {
        StringBuilder resultBuilder = new StringBuilder();
        Random random = new Random();
        for(int i = 0; i < 128; i++) {
            resultBuilder.append(combined[(int)(random.nextDouble() * combined.length)]);
        }
        return resultBuilder.toString();
    }

    public static SecretKeySpec StringtoKey(String s) throws NoSuchAlgorithmException {
        byte[] digest = MessageDigest.getInstance("SHA-256").digest(s.getBytes());
        digest = Arrays.copyOf(digest, 16);
        return new SecretKeySpec(digest, "AES");
    }
    public static String readAsMuchAsPossible(InputStream in) throws IOException {
        StringBuilder stringBuilder = new StringBuilder();
        while(in.available() > 0) {
            int c = in.read();
            if(c == -1) {
                return stringBuilder.toString();
            } else {
                stringBuilder.append((char) c);
            }
        }
        return stringBuilder.toString();
    }
}

Also, please notice that the code is very bare bones, and doesn't handle exceptions yet. It's just a prototype for now.

Maarten Bodewes :

They communicate securely by encrypting with AES.

I read this as: I'm trying to implement a transport protocol, but I don't know about the different modes of operation to make correct use of AES.

The client generates a random symmetric key, encrypts it with the public key of the server and sends it to the server. The server can then decrypt the secret key and use it to communicate with the client.

Yes, that's how hybrid crypto (asymmetric & symmetric crypto) can be made to work.

I heard about the diffie hellman key exchange, and wondered if my code is as secure as that.

You seem to make use of certificates, which shows that you probably have thought about making sure that the public key can be trusted. Yes, all the RSA ciphersuites in TLS up to and including 1.2 make use of RSA encryption of a master secret, from which the session keys are then derived.

Is it risky to do it my way, and is there any kind of advantage using the Diffie-Hellman key exchange?

Yes. If you use ephemeral Diffie-Hellman then it is possible to have forward security. That means that even if the static keys are lost, that the sessions cannot be decrypted. You would of course still need separate static (RSA) keys to authenticate the server and possibly client. This is one of the reasons why TLS 1.3 doesn't have RSA_ ciphersuites anymore.

Forward security isn't really possible with RSA because RSA key pair generation is too inefficient.


Suffice to say that creating a cryptographically secure transport protocol is not for the uninitiated. Even without seeing all the code, I can tell your protocol is not secure in many ways; use TLS instead.

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=373562&siteId=1