我不确定这个问题是否属于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();
}
}
[另外,请注意,该代码非常简单,尚不处理异常。现在只是一个原型。
它们通过使用AES加密进行安全通信。
我读为:我正在尝试实现传输协议,但是我不知道正确使用AES的不同操作模式。
客户端生成一个随机对称密钥,并使用服务器的公共密钥对其进行加密,然后将其发送到服务器。然后,服务器可以解密密钥,并使用它与客户端进行通信。
是的,这就是使混合加密(不对称和对称加密)工作的方式。
我听说了diffie hellman密钥交换,并想知道我的代码是否像这样安全。
您似乎在使用证书,这表明您可能已经考虑过确保公钥可以信任。是的,直到TLS(包括1.2)中的所有RSA密码套件都使用主密钥的RSA加密,然后从中[从中加密会话密钥。
以我的方式进行操作是否有风险,使用Diffie-Hellman密钥交换是否有任何优势?
是。如果使用临时Diffie-Hellman,则可能具有前向安全性。这意味着即使静态密钥丢失,也无法解密会话。当然,您仍然需要单独的静态(RSA)密钥来认证服务器和可能的客户端。这是TLS 1.3不再具有RSA_ ciphersuites
的原因之一。
RSA无法真正实现转发安全性,因为RSA密钥对生成效率太低。