这是一个我最近一直在努力解决的问题,很大程度上是因为我觉得互联网上关于这个问题的信息太多了,没有帮助。所以,因为我刚刚找到了适合我的解决方案,所以我决定在这里发布问题和解决方案,希望我能够让互联网成为一个稍微好一点的地方,让那些追随我的人! (希望这不会导致“无益”的内容!)使用自签名证书的Android SSLSockets
我有一个Android应用程序,我一直在开发。直到最近,我刚刚使用ServerSockets和套接字在我的应用程序和我的服务器之间进行通信。但是,通信需要安全,所以我一直在试图将这些转换为SSLServerSockets和SSLSockets,结果比我预期的要困难得多。
鉴于这只是一个原型,在使用自签名证书时没有(安全)危害,这正是我正在做的。正如你可能猜到的那样,这是问题出现的地方。这就是我所做的,以及我遇到的问题。
我生成的文件 “mykeystore.jks” 用下面的命令:
keytool -genkey -alias serverAlias -keyalg RSA -keypass MY_PASSWORD -storepass MY_PASSWORD -keystore mykeystore.jks
这是服务器代码:
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;
/**
* Server
*/
public class simplesslserver {
// Global variables
private static SSLServerSocketFactory ssf;
private static SSLServerSocket ss;
private static final int port = 8081;
private static String address;
/**
* Main: Starts the server and waits for clients to connect.
* Each client is given its own thread.
* @param args
*/
public static void main(String[] args) {
try {
// System properties
System.setProperty("javax.net.ssl.keyStore","mykeystore.jks");
System.setProperty("javax.net.ssl.keyStorePassword","MY_PASSWORD");
// Start server
ssf = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
ss = (SSLServerSocket) ssf.createServerSocket(port);
address = InetAddress.getLocalHost().toString();
System.out.println("Server started at "+address+" on port "+port+"\n");
// Wait for messages
while (true) {
SSLSocket connected = (SSLSocket) ss.accept();
new clientThread(connected).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Client thread.
*/
private static class clientThread extends Thread {
// Variables
private SSLSocket cs;
private InputStreamReader isr;
private OutputStreamWriter osw;
private BufferedReader br;
private BufferedWriter bw;
/**
* Constructor: Initialises client socket.
* @param clientSocket The socket connected to the client.
*/
public clientThread(SSLSocket clientSocket) {
cs = clientSocket;
}
/**
* Starts the thread.
*/
public void run() {
try {
// Initialise streams
isr = new InputStreamReader(cs.getInputStream());
br = new BufferedReader(isr);
osw = new OutputStreamWriter(cs.getOutputStream());
bw = new BufferedWriter(osw);
// Get request from client
String tmp = br.readLine();
System.out.println("received: "+tmp);
// Send response to client
String resp = "You said '"+tmp+"'!";
bw.write(resp);
bw.newLine();
bw.flush();
System.out.println("response: "+resp);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
这是从的Android的提取物应用程序(客户端):
String message = "Hello World";
try{
// Create SSLSocketFactory
SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault();
// Create socket using SSLSocketFactory
SSLSocket client = (SSLSocket) factory.createSocket("SERVER_IP_ADDRESS", 8081);
// Print system information
System.out.println("Connected to server " + client.getInetAddress() + ": " + client.getPort());
// Writer and Reader
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream()));
// Send request to server
System.out.println("Sending request: "+message);
writer.write(message);
writer.newLine();
writer.flush();
// Receive response from server
String response = reader.readLine();
System.out.println("Received from the Server: "+response);
// Close connection
client.close();
return response;
} catch(Exception e) {
e.printStackTrace();
}
return "Something went wrong...";
当我运行代码时,它不起作用,这是我得到的输出。
客户端输出:
Connected to server /SERVER_IP_ADDRESS: 8081
javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
... stack trace ...
服务器输出:
received: null
java.net.SocketException: Connection closed by remote host
由于证书是自签名的应用程序不信任它。我查看了Google,总的来说,我需要创建一个基于自定义TrustManager的SSLContext(在客户端),该自定义TrustManager接受这个自签名证书。很简单,我想。在接下来的一周里,我尝试了更多的方法来解决这个问题,但我无法记住,但没有成功。我现在请你回到我原来的陈述:那里有太多不完整的信息,这使得解决方案比本来要困难得多。
我发现的唯一工作解决方案是使TrustManager接受ALL证书。
private static class AcceptAllTrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {}
@Override
public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {}
@Override
public X509Certificate[] getAcceptedIssuers() { return null; }
}
可以使用这样
SSLContext sslctx = SSLContext.getInstance("TLS");
sslctx.init(new KeyManager[0], new TrustManager[] {new AcceptAllTrustManager()}, new SecureRandom());
SSLSocketFactory factory = sslctx.getSocketFactory();
SSLSocket client = (SSLSocket) factory.createSocket("IP_ADDRESS", 8081);
并给出了没有例外的好和快乐的输出!
Connected to server /SERVER_IP_ADDRESS: 8081
Sending request: Hello World
Received from the Server: You said 'Hello World'!
然而,这不是一个好主意,因为应用程序仍然是不安全的,由于潜在的人在这方面的中间人攻击。
所以我被卡住了。我怎么能让应用程序信任我自己的自签名证书,但不仅仅是那里的任何证书?