我正在创建服务器和客户端程序,在阅读了所有可能出现的加密问题后,我仍然想知道我是否正在做一些非常具有破坏性的事情。
关于一些程序员做了多少事情很糟糕而且永远不应该做的事情,我经常会看到强烈的反应。我希望对下面的代码做出这样的反应。
我在java中提供了一个绝对最小的设置,可以在没有任何额外库的情况下运行。这都是1个功能(服务器为1,客户端为1),因此很容易理解。
概述: 它当前生成RSA priv / pub对,并与服务器交换客户端生成的对称AES密钥。服务器还签署客户端的公钥,以便客户端知道服务器知道客户端。此外,客户端检查服务器是否具有可信公钥。传输的数据通过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();
}
}
}
所以我的问题是:“我做错了什么?”或者更确切地说“我需要做什么才能绝对肯定地建立绝对安全的连接?”。 也;假设“publickey”文件仅仅是所有客户端上的共享文件。可以省略在服务器上生成新的公钥/私钥,这只是为了表明我在生成公钥/私钥对时可能做错了。客户端将始终为每个连接生成新的RSA密钥对以及AES密钥对。这样安全吗?
答案 0 :(得分:1)
最重要的关键不是重新发明安全协议或实现;它们很棘手并且有各种各样的边缘情况。使用经过打包测试的实施。所有主要的servlet容器,包括Tomcat,Jetty和Undertow,都内置了SSL支持,每个可以想象的目标平台都知道如何发出HTTPS请求。
答案 1 :(得分:0)
您似乎做错的一件事是使用常量初始化向量。
您是否有理由自行实施安全连接?在这种情况下,将 HTTPS 与客户端和服务器证书结合使用似乎更好。 (遗憾的是,这不容易设置。)