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?