【发布时间】: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