Java:向服务器发送使用服务器公钥加密的随机对称密钥是否安全?

问题描述 投票:0回答:1

我不确定这个问题是否属于stackoverflow或crypto stackexchange,但由于它包含源代码,因此我将其发布在这里。

这里是我的问题:我写了两个程序,一个是客户端,另一个是服务器。它们通过使用AES加密进行安全通信。客户端生成一个随机对称密钥,并使用服务器的公共密钥对其进行加密,然后将其发送到服务器。然后,服务器可以解密密钥,并使用它与客户端进行通信。

我听说了diffie hellman密钥交换,并想知道我的代码是否像这样安全。用我的方式进行操作是否冒险,使用diffie hellman密钥交换是否有任何优势?

客户来源:

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

服务器源:

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!");
        }
    }
}

而且我有一个util类,“ Main”所指的是:

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

[另外,请注意,该代码非常简单,尚不处理异常。现在只是一个原型。

java encryption encryption-asymmetric diffie-hellman
1个回答
1
投票

它们通过使用AES加密进行安全通信。

我读为:我正在尝试实现传输协议,但是我不知道正确使用AES的不同操作模式。

客户端生成一个随机对称密钥,并使用服务器的公共密钥对其进行加密,然后将其发送到服务器。然后,服务器可以解密密钥,并使用它与客户端进行通信。

是的,这就是使混合加密(不对称和对称加密)工作的方式。

我听说了diffie hellman密钥交换,并想知道我的代码是否像这样安全。

您似乎在使用证书,这表明您可能已经考虑过确保公钥可以信任。是的,直到TLS(包括1.2)中的所有RSA密码套件都使用主密钥的RSA加密,然后从中[从中加密会话密钥。

以我的方式进行操作是否有风险,使用Diffie-Hellman密钥交换是否有任何优势?

是。如果使用临时Diffie-Hellman,则可能具有前向安全性。这意味着即使静态密钥丢失,也无法解密会话。当然,您仍然需要单独的静态(RSA)密钥来认证服务器和可能的客户端。这是TLS 1.3不再具有RSA_ ciphersuites的原因之一。

RSA无法真正实现转发安全性,因为RSA密钥对生成效率太低。


可以说,创建密码学安全的传输协议不是针对未启动的。即使没有看到所有代码,我仍然可以从许多方面看出您的协议不安全;请改用TLS。
© www.soinside.com 2019 - 2024. All rights reserved.