如何绝对积极地设置安全的客户端/服务器?

时间:2015-02-23 10:59:54

标签: java encryption

我正在创建服务器和客户端程序,在阅读了所有可能出现的加密问题后,我仍然想知道我是否正在做一些非常具有破坏性的事情。
关于一些程序员做了多少事情很糟糕而且永远不应该做的事情,我经常会看到强烈的反应。我希望对下面的代码做出这样的反应。

我在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密钥对。这样安全吗?

2 个答案:

答案 0 :(得分:1)

最重要的关键不是重新发明安全协议或实现;它们很棘手并且有各种各样的边缘情况。使用经过打包测试的实施。所有主要的servlet容器,包括Tomcat,Jetty和Undertow,都内置了SSL支持,每个可以想象的目标平台都知道如何发出HTTPS请求。

答案 1 :(得分:0)

您似乎做错的一件事是使用常量初始化向量

请参阅此处进行讨论:https://crypto.stackexchange.com/questions/2576/encryption-with-constant-initialization-vector-considered-harmful

您是否有理由自行实施安全连接?在这种情况下,将 HTTPS 与客户端和服务器证书结合使用似乎更好。 (遗憾的是,这不容易设置。)