【问题标题】:How To Absolutely Positively Setup a Secure Client/Server?如何绝对积极地设置安全客户端/服务器?
【发布时间】:2015-02-23 10:59:53
【问题描述】:

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

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

【问题讨论】:

  • 为了绝对肯定不会出错,您需要关闭机器并烧掉它。但是,您可以通过使用许多用户定期使用(和调试)的库来最大程度地降低风险,并避免在涉及安全性时尝试重新发明轮子。即使那样,你也不是绝对安全的。例如 - HeartBleed
  • @amit 我正在尝试使用 Java 提供的库。我不会尝试深入到 bignum 级别并为 RSA 生成素数。你是在暗示有比这更高级的东西吗?我已经查看了 TLS/SSL,但这似乎只有在客户端也可以信任时才有用(也就是需要存储自己的 pub/priv 密钥)。如果您认为这可能是一种更好的方法,那么我很想听听简单的消息传递是如何工作并且是安全的。
  • @BourgondAries 除非您使用智能卡,否则不要使用客户端证书进行身份验证,在这种情况下,智能卡将内置协议实现。
  • 除了安全方面之外,此代码还存在更多基本问题,例如在完成编写后无法关闭许多输出流。
  • 要验证客户端的真实性,即确保您正在与您认为正在与您交谈的人交谈,您可以实施一些票务系统 (en.wikipedia.org/wiki/Kerberos_(protocol)) 并将其整合到您的安全模型中这应该有助于您反对“信任客户”的论点。话虽如此,正如@amit 指出的那样,没有“绝对绝对安全”这样的东西。你可以试着让它更难破解。

标签: java encryption


【解决方案1】:

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

【讨论】:

  • stilius.net/java/java_ssl.php 这样的内容是否足以作为一个安全的示例?难道这里的客户端不知道证书,这样任何客户端都可以假装自己是服务器吗?或者这些数据是否由 keyStorePassword 和 trustStorePassword 保护?
  • @BourgondAries 您说的是中间人 (MITM) 攻击。当前已知的解决方案要求客户端具有某种预安装的证书,以确保正确识别服务器(在 TLS 中,这些预安装的根证书用于签署每个服务器的证书)。如有必要,可能不是,您可以将您的应用程序与您自己的根证书一起发送到信任库中。这不需要是私有的。
  • 您是否知道允许和检查多个不同认证服务器的方法?我正在开发的应用程序将允许客户端安全地连接到已知/认证的主机。
  • @BourgoundAries 这就是拥有签署服务器证书的根证书(已部署)的全部意义所在。你还不熟悉这个基础设施,它是现代加密实施的基础,大声争辩要学习和使用现有系统,而不是尝试开发你自己的系统。
【解决方案2】:

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

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

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

【讨论】:

  • 所以想法是通过 RSA 和 AES 一起发送 iv?
  • 据我所知(很少),需要 iv 来创建加密数据的变化。它不像钥匙那样是秘密的。请参阅此处以获得更权威的答案:stackoverflow.com/a/1540663/136247
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-28
  • 1970-01-01
  • 1970-01-01
  • 2016-04-17
  • 1970-01-01
相关资源
最近更新 更多