HTTPS 是移動端網路安全的基石。本文系統解析 SSL/TLS 握手原理,並針對 Android 客戶端實作三種不同安全等級的 HTTPS 連線策略:**預設信任系統 CA、信任自簽憑證(忽略驗證)、攜帶自訂 KeyStore 驗證特定憑證**,附完整可編譯程式碼與 Nginx 憑證生成指引。
HTTPS = HTTP + SSL/TLS。核心目標解決三大風險:
握手四步驟(簡化):
# 1. 生成私鑰
openssl genrsa -out server.key 2048
# 2. 生成自簽憑證 (有效期 10 年)
openssl req -new -x509 -key server.key -out server.crt -days 3650 -subj "/CN=myapp.local"
# 3. Nginx 配置片段
server {
listen 443 ssl;
server_name myapp.local;
ssl_certificate /etc/nginx/ssl/server.crt;
ssl_certificate_key /etc/nginx/ssl/server.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
}
基礎依賴: Android 6.0+ 建議改用 HttpURLConnection 或 OkHttp;本文為相容舊專案示範 Apache HttpClient(已棄用),新專案請直接使用 OkHttp。
public class HttpsClientDefault {
public static HttpClient create() {
SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
return new DefaultHttpClient(new ThreadSafeClientConnManager(new BasicHttpParams(), registry), new BasicHttpParams());
}
}
public class TrustAllSSLSocketFactory extends SSLSocketFactory {
private final SSLContext sslContext = SSLContext.getInstance("TLS");
public TrustAllSSLSocketFactory(KeyStore truststore) throws Exception {
super(truststore);
TrustManager tm = new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() { return null; }
public void checkServerTrusted(X509Certificate[] chain, String authType) {}
public void checkClientTrusted(X509Certificate[] chain, String authType) {}
};
sslContext.init(null, new TrustManager[]{tm}, null);
}
@Override public Socket createSocket() throws IOException { return sslContext.getSocketFactory().createSocket(); }
@Override public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
}
public static SSLSocketFactory getSocketFactory() {
try {
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null, null);
return new TrustAllSSLSocketFactory(ks);
} catch (Exception e) { throw new RuntimeException(e); }
}
}
// 使用方式:registry.register(new Scheme("https", TrustAllSSLSocketFactory.getSocketFactory(), 443));
⚠️ 風險: 完全失去身分驗證,易遭中間人攻擊(MITM)。
步驟 1:將 server.crt 轉為 BKS KeyStore(需 BouncyCastle Provider)
keytool -importcert -v -trustcacerts -alias myapp -file server.crt \
-keystore myapp.bks -storetype BKS \
-providerclass org.bouncycastle.jce.provider.BouncyCastleProvider \
-providerpath bcprov-jdk16-146.jar \
-storepass your_keystore_password
將生成的 myapp.bks 放入 Android 專案 res/raw/ 目錄。
步驟 2:自訂 SocketFactory 載入 KeyStore
public class PinnedKeyStoreSocketFactory extends SSLSocketFactory {
private final SSLContext sslContext;
public PinnedKeyStoreSocketFactory(KeyStore truststore) throws Exception {
super(truststore);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(truststore);
sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);
}
@Override public Socket createSocket() throws IOException { return sslContext.getSocketFactory().createSocket(); }
@Override public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
}
public static SSLSocketFactory getSocketFactory(Context context) {
try (InputStream in = context.getResources().openRawResource(R.raw.myapp)) {
KeyStore ks = KeyStore.getInstance("BKS");
ks.load(in, "your_keystore_password".toCharArray());
return new PinnedKeyStoreSocketFactory(ks);
} catch (Exception e) { throw new RuntimeException(e); }
}
}
// 使用方式:registry.register(new Scheme("https", PinnedKeyStoreSocketFactory.getSocketFactory(context), 443));
新專案強烈建議使用 OkHttp,程式碼極簡且安全:
OkHttpClient client = new OkHttpClient.Builder()
.certificatePinner(new CertificatePinner.Builder()
.add("myapp.local", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.build())
.build();
Request request = new Request.Builder().url("https://myapp.local/api").build();
client.newCall(request).enqueue(new Callback() { ... });
sha256/... 為伺服器憑證公鑰的 SHA-256 指紋,可透過 openssl x509 -in server.crt -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | base64 取得。
正確實作 HTTPS 驗證策略,是保障用戶資料傳輸安全的底線。