KeyStore, SSL/TLS & Secure Networking
Sau bài này, bạn sẽ:
- Hiểu và sử dụng được KeyStore và TrustStore
- Thành thạo
keytoolCLI để quản lý khoá và chứng chỉ - Hiểu quy trình SSL/TLS handshake
- Cấu hình
SSLContextvàHttpsURLConnection - Biết về Certificate Pinning và khi nào nên dùng
KeyStore và TrustStore
KeyStore là gì?
KeyStore là kho lưu trữ bảo mật cho khoá (keys) và chứng chỉ (certificates). Java hỗ trợ nhiều định dạng:
| Định dạng | Mô tả | File extension | Khuyến nghị |
|---|---|---|---|
| PKCS12 | Chuẩn công nghiệp, cross-platform | .p12, .pfx | Khuyến nghị (mặc định từ Java 9) |
| JKS | Java KeyStore (proprietary) | .jks | Legacy, tránh dùng mới |
| JCEKS | JKS mở rộng, hỗ trợ mã hoá mạnh | .jceks | Legacy |
So sánh các loại KeyStore
Nếu đang dùng JKS, migrate sang PKCS12 bằng lệnh:
keytool -importkeystore \
-srckeystore old.jks \
-destkeystore new.p12 \
-deststoretype PKCS12
KeyStore vs TrustStore
| Đặc điểm | KeyStore | TrustStore |
|---|---|---|
| Chứa gì | Private keys + certificates | Trusted CA certificates |
| Mục đích | Xác minh danh tính của mình | Xác minh danh tính của đối phương |
| Khi nào dùng | Server cần certificate để client trust | Client cần verify server certificate |
| System property | javax.net.ssl.keyStore | javax.net.ssl.trustStore |
KeyStore vs TrustStore trong TLS Handshake
Quy tắc nhớ đơn giản:
- KeyStore = "Đây là tôi" (chứng minh danh tính của mình)
- Server dùng để gửi certificate cho client
- Client dùng trong mTLS để gửi certificate cho server
- TrustStore = "Tôi tin ai?" (xác minh người khác)
- Client dùng để verify server certificate
- Server dùng trong mTLS để verify client certificate
Thao tác với KeyStore trong Java
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.security.KeyStore;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
public class KeyStoreExample {
public static void main(String[] args) throws Exception {
String keystorePath = "mykeys.p12";
char[] password = "keystorepass".toCharArray();
// === TẠO KeyStore mới ===
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(null, password); // null = tạo mới
// Tạo và lưu AES key
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256);
SecretKey secretKey = keyGen.generateKey();
KeyStore.SecretKeyEntry keyEntry = new KeyStore.SecretKeyEntry(secretKey);
KeyStore.ProtectionParameter protParam =
new KeyStore.PasswordProtection("keypass".toCharArray());
ks.setEntry("my-aes-key", keyEntry, protParam);
// Lưu KeyStore ra file
try (var fos = new FileOutputStream(keystorePath)) {
ks.store(fos, password);
}
System.out.println("KeyStore saved: " + keystorePath);
// === ĐỌC KeyStore ===
KeyStore ks2 = KeyStore.getInstance("PKCS12");
try (var fis = new FileInputStream(keystorePath)) {
ks2.load(fis, password);
}
// Lấy key từ KeyStore
KeyStore.Entry entry = ks2.getEntry("my-aes-key", protParam);
if (entry instanceof KeyStore.SecretKeyEntry ske) {
SecretKey loadedKey = ske.getSecretKey();
System.out.println("Algorithm: " + loadedKey.getAlgorithm());
System.out.println("Key loaded successfully!");
}
}
}
keytool — Công cụ quản lý KeyStore
keytool là command-line tool đi kèm JDK, dùng để quản lý khoá và chứng chỉ.
Các lệnh thường dùng
# 1. Tạo self-signed certificate
keytool -genkeypair \
-alias myserver \
-keyalg RSA \
-keysize 2048 \
-validity 365 \
-keystore server.p12 \
-storetype PKCS12 \
-storepass changeit \
-dname "CN=localhost, OU=Dev, O=MyCompany, L=HCMC, ST=HCMC, C=VN"
# 2. Xem nội dung KeyStore
keytool -list -keystore server.p12 -storepass changeit
# 3. Xem chi tiết certificate
keytool -list -v -keystore server.p12 -storepass changeit -alias myserver
# 4. Export certificate (để gửi cho client)
keytool -exportcert \
-alias myserver \
-keystore server.p12 \
-storepass changeit \
-file server.cer
# 5. Import certificate vào TrustStore
keytool -importcert \
-alias myserver \
-file server.cer \
-keystore client-truststore.p12 \
-storetype PKCS12 \
-storepass changeit
# 6. Xem default TrustStore (cacerts) của JDK
keytool -list -keystore $JAVA_HOME/lib/security/cacerts -storepass changeit
Java đi kèm file cacerts chứa certificates của các CA lớn (DigiCert, Let's Encrypt, GlobalSign...). File này nằm tại $JAVA_HOME/lib/security/cacerts với password mặc định là changeit.
SSL/TLS — Secure Communication
TLS Handshake
Certificate Chain Validation
Khi client kết nối tới server qua TLS, nó phải xác minh chuỗi chứng chỉ (certificate chain):
Java dùng CertPathValidator để xác minh chain:
import java.security.cert.*;
import java.util.*;
public class CertChainValidator {
public static void validateChain(X509Certificate[] chain,
KeyStore trustStore) throws Exception {
// Tạo CertPath từ certificate chain
CertificateFactory cf = CertificateFactory.getInstance("X.509");
CertPath certPath = cf.generateCertPath(Arrays.asList(chain));
// Cấu hình PKIX validator
PKIXParameters params = new PKIXParameters(trustStore);
params.setRevocationEnabled(true); // Kiểm tra revocation
// Validate
CertPathValidator validator =
CertPathValidator.getInstance("PKIX");
validator.validate(certPath, params); // Throws nếu invalid
}
}
Certificate Revocation — Kiểm tra chứng chỉ bị thu hồi
Certificate có thể bị thu hồi (revoke) trước khi hết hạn vì private key bị lộ hoặc thông tin sai.
| Phương pháp | Cách hoạt động | Ưu điểm | Nhược điểm |
|---|---|---|---|
| CRL (Certificate Revocation List) | Download danh sách cert bị thu hồi | Đơn giản | File lớn, không real-time |
| OCSP (Online Certificate Status Protocol) | Query trực tiếp CA | Real-time | Phụ thuộc CA server |
| OCSP Stapling | Server đính kèm OCSP response | Nhanh, giảm tải CA | Server phải hỗ trợ |
// Bật OCSP checking
System.setProperty("com.sun.net.ssl.checkRevocation", "true");
Security.setProperty("ocsp.enable", "true");
// Hoặc trong PKIXParameters
PKIXParameters params = new PKIXParameters(trustStore);
params.setRevocationEnabled(true);
Mutual TLS (mTLS)
Trong Mutual TLS, cả client VÀ server đều phải xuất trình certificate. Phổ biến trong kiến trúc microservices để xác minh danh tính giữa các service.
// Server-side: yêu cầu client certificate
SSLContext sslContext = SSLContext.getInstance("TLSv1.3");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
SSLServerSocketFactory ssf = sslContext.getServerSocketFactory();
SSLServerSocket serverSocket = (SSLServerSocket) ssf.createServerSocket(8443);
// Bắt buộc client phải có certificate
serverSocket.setNeedClientAuth(true);
// Hoặc: serverSocket.setWantClientAuth(true); // Optional
TLS Debugging
Khi gặp vấn đề TLS/SSL, bật debug logging:
# Debug toàn bộ SSL
java -Djavax.net.debug=ssl:handshake MyApp
# Chỉ debug handshake
java -Djavax.net.debug=ssl:handshake:verbose MyApp
# Debug certificate chain
java -Djavax.net.debug=certpath MyApp
Cấu hình SSLContext
import javax.net.ssl.*;
import java.io.FileInputStream;
import java.security.KeyStore;
public class SSLContextExample {
/**
* Tạo SSLContext với custom KeyStore và TrustStore.
*/
public static SSLContext createSSLContext(
String keystorePath, String keystorePass,
String truststorePath, String truststorePass) throws Exception {
// 1. Load KeyStore (cho server authentication)
KeyStore keyStore = KeyStore.getInstance("PKCS12");
try (var fis = new FileInputStream(keystorePath)) {
keyStore.load(fis, keystorePass.toCharArray());
}
KeyManagerFactory kmf = KeyManagerFactory.getInstance(
KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, keystorePass.toCharArray());
// 2. Load TrustStore (để verify đối phương)
KeyStore trustStore = KeyStore.getInstance("PKCS12");
try (var fis = new FileInputStream(truststorePath)) {
trustStore.load(fis, truststorePass.toCharArray());
}
TrustManagerFactory tmf = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
// 3. Tạo SSLContext
SSLContext sslContext = SSLContext.getInstance("TLSv1.3");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
return sslContext;
}
}
HTTPS Request với HttpsURLConnection
import javax.net.ssl.HttpsURLConnection;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URI;
public class HttpsExample {
public static void main(String[] args) throws Exception {
var url = URI.create("https://api.github.com").toURL();
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
// Kiểm tra thông tin SSL
conn.connect();
System.out.println("Cipher Suite: " + conn.getCipherSuite());
System.out.println("Server Cert: " +
conn.getServerCertificates()[0].getType());
// Đọc response
try (var reader = new BufferedReader(
new InputStreamReader(conn.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
}
}
HTTPS với HttpClient (Java 11+)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import javax.net.ssl.SSLContext;
public class HttpClientSSL {
public static void main(String[] args) throws Exception {
// HttpClient tự dùng default SSLContext
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
// .sslContext(customSSLContext) // Nếu cần custom
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://httpbin.org/get"))
.header("Accept", "application/json")
.build();
HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString());
System.out.println("Status: " + response.statusCode());
System.out.println("Body: " + response.body().substring(0, 200));
}
}
// ❌ TUYỆT ĐỐI KHÔNG làm thế này trong production!
TrustManager[] trustAll = new TrustManager[] {
new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String type) {}
public void checkServerTrusted(X509Certificate[] chain, String type) {}
public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
}
};
// Đây là lỗi bảo mật nghiêm trọng — cho phép MITM attack!
Certificate Pinning
Certificate Pinning là gì?
Certificate Pinning là kỹ thuật chỉ chấp nhận một certificate hoặc public key cụ thể, thay vì tin tưởng toàn bộ CA chain. Giúp phòng chống:
- MITM attack với certificate giả (do CA bị compromise)
- Rogue CA cấp certificate cho attacker
Implement Certificate Pinning
import javax.net.ssl.*;
import java.security.MessageDigest;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Base64;
public class CertificatePinning {
// SHA-256 hash của public key mong đợi
private static final String EXPECTED_PIN =
"sha256/abcdef1234567890abcdef1234567890abcdef12345678=";
/**
* Tạo TrustManager với certificate pinning.
*/
public static TrustManager[] createPinnedTrustManager() {
return new TrustManager[] {
new X509TrustManager() {
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws java.security.cert.CertificateException {
// Verify chain bình thường trước
// Sau đó kiểm tra pin
try {
X509Certificate serverCert = chain[0];
String pin = getPublicKeyPin(serverCert);
if (!EXPECTED_PIN.equals("sha256/" + pin)) {
throw new java.security.cert.CertificateException(
"Certificate pinning failed! Expected: " +
EXPECTED_PIN + ", Got: sha256/" + pin);
}
} catch (Exception e) {
throw new java.security.cert.CertificateException(e);
}
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
};
}
/**
* Lấy SHA-256 pin của public key.
*/
public static String getPublicKeyPin(X509Certificate cert) throws Exception {
byte[] publicKeyEncoded = cert.getPublicKey().getEncoded();
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] pin = md.digest(publicKeyEncoded);
return Base64.getEncoder().encodeToString(pin);
}
public static void main(String[] args) throws Exception {
// Lấy pin của một server
var url = new java.net.URI("https://github.com").toURL();
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.connect();
for (Certificate cert : conn.getServerCertificates()) {
if (cert instanceof X509Certificate x509) {
System.out.println("Subject: " + x509.getSubjectX500Principal());
System.out.println("Pin: sha256/" + getPublicKeyPin(x509));
System.out.println();
}
}
}
}
Ví dụ thực tế: Secure API Client
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
public class SecureApiClient {
private final HttpClient client;
public SecureApiClient() throws Exception {
SSLContext sslContext = SSLContext.getInstance("TLSv1.3");
sslContext.init(null, null, null); // Dùng default TrustStore
SSLParameters sslParams = new SSLParameters();
sslParams.setProtocols(new String[]{"TLSv1.3", "TLSv1.2"});
this.client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.sslContext(sslContext)
.sslParameters(sslParams)
.connectTimeout(Duration.ofSeconds(10))
.build();
}
public String get(String url) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.timeout(Duration.ofSeconds(30))
.header("Accept", "application/json")
.build();
HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new RuntimeException("HTTP " + response.statusCode());
}
return response.body();
}
public static void main(String[] args) throws Exception {
SecureApiClient api = new SecureApiClient();
String result = api.get("https://httpbin.org/get");
System.out.println("Response: " + result.substring(0, 200));
}
}
SSL/TLS Best Practices
Khi triển khai SSL/TLS trong ứng dụng Java, cần tuân theo các best practices sau:
Checklist triển khai production:
- Dùng TLS 1.2 hoặc 1.3 (không dùng SSL/TLS 1.0/1.1)
- Cấu hình cipher suites an toàn (AEAD, Forward Secrecy)
- Sử dụng PKCS12 format cho KeyStore (không dùng JKS mới)
- Password KeyStore phức tạp (>16 ký tự, mixed case, special chars)
- Lưu password trong secret management (Vault, AWS Secrets, etc.)
- Bật certificate revocation checking (OCSP)
- Theo dõi certificate expiry và auto-renewal
- Implement hostname verification (
setHostnameVerifier) - Log SSL handshake failures để audit
- Test với multiple TLS versions và ciphers
- Certificate pinning cho mobile/high-security apps
- Rotate keys và certificates định kỳ
Dùng tools như Let's Encrypt với Certbot để tự động renew certificates. Trong Kubernetes, dùng cert-manager để quản lý certificates tự động.
Lỗi thường gặp
1. Dùng TLS version cũ:
// ❌ Sai — TLS 1.0 và 1.1 đã bị phá
SSLContext ctx = SSLContext.getInstance("TLSv1");
// ✅ Đúng — dùng TLS 1.2 hoặc 1.3
SSLContext ctx = SSLContext.getInstance("TLSv1.3");
2. Password KeyStore quá yếu:
# ❌ Sai
keytool -genkeypair -storepass 123456
# ✅ Đúng — password mạnh
keytool -genkeypair -storepass "Str0ng!P@ssw0rd#2025"
3. Quên close connection:
// ❌ Sai — resource leak
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.getInputStream().read();
// ✅ Đúng — dùng try-with-resources hoặc HttpClient
try (var is = conn.getInputStream()) {
// read data
} finally {
conn.disconnect();
}
Đề thi OCP hay hỏi: store nào chứa gì?
| KeyStore | TrustStore | |
|---|---|---|
| Chứa | Private key + certificate của mình | CA certificates để verify người khác |
| System property | javax.net.ssl.keyStore | javax.net.ssl.trustStore |
| Dùng bởi | KeyManager | TrustManager |
| Vai trò | "Đây là danh tính của tôi" | "Tôi tin ai?" |
Nhầm lẫn thường gặp: đặt server certificate vào TrustStore thay vì KeyStore → handshake fail.
PKCS12 là default từ Java 9. Nếu đề thi cho KeyStore.getInstance("JKS") với file .p12, sẽ throw IOException vì format không khớp.
// Java 9+: PKCS12 là default
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
// getDefaultType() trả về "pkcs12"
// Explicit PKCS12
KeyStore ks = KeyStore.getInstance("PKCS12");
// JKS — legacy, chỉ dùng cho file .jks cũ
KeyStore ks = KeyStore.getInstance("JKS");
JSSE (Java Secure Socket Extension) cung cấp framework cho SSL/TLS trong Java. SSLContext là entry point chính — nó kết hợp KeyManager (identity), TrustManager (verification), và SecureRandom (randomness) để tạo SSLSocketFactory và SSLServerSocketFactory.
Tham khảo: Oracle JSSE Reference Guide
Bài tập
Bài 1: KeyStore Manager [Cơ bản]
Viết chương trình command-line quản lý KeyStore:
create <file> <password>— tạo KeyStore mớilist <file> <password>— liệt kê tất cả entriesadd-key <file> <password> <alias>— thêm AES key mớidelete <file> <password> <alias>— xoá entry
Gợi ý
Dùng switch trên args[0] để xử lý các command. Mỗi command là một method riêng.
Bài 2: HTTPS Health Checker [Trung bình]
Viết chương trình kiểm tra SSL/TLS của một list URLs:
- Kiểm tra certificate expiry date
- Kiểm tra TLS version
- Kiểm tra cipher suite
- In cảnh báo nếu certificate sắp hết hạn (< 30 ngày)
Gợi ý
Dùng HttpsURLConnection để connect, getServerCertificates() để lấy cert info, cast sang X509Certificate để đọc getNotAfter().
Bài 3: Mutual TLS Client-Server [Thách thức]
Xây dựng hệ thống client-server với mutual TLS (cả client và server đều có certificate):
- Tạo CA certificate tự ký
- Tạo server certificate ký bởi CA
- Tạo client certificate ký bởi CA
- Server chỉ chấp nhận client có certificate hợp lệ
Gợi ý
Dùng keytool để tạo CA, server cert, client cert. Cấu hình SSLContext với cả KeyManager (identity) và TrustManager (verification) cho cả client và server.
Tóm tắt
| Khái niệm | Mô tả |
|---|---|
| KeyStore | Kho lưu trữ private keys và certificates (PKCS12 format) |
| TrustStore | Kho chứa CA certificates để verify đối phương |
| keytool | CLI tool của JDK để quản lý KeyStore |
| SSL/TLS | Protocol mã hoá kết nối mạng (dùng TLSv1.2/1.3) |
| Certificate Pinning | Chỉ chấp nhận certificate/public key cụ thể |
Key takeaways:
- Dùng PKCS12 format cho KeyStore (không dùng JKS mới)
- Luôn dùng TLS 1.2 hoặc 1.3, không dùng TLS 1.0/1.1 hoặc SSL
- Không bao giờ disable SSL verification trong production
- Certificate Pinning tăng bảo mật nhưng cần cơ chế update pin
Tiếp theo
Ở bài tiếp theo, chúng ta sẽ tìm hiểu Secure Coding Practices — các kỹ thuật lập trình an toàn để phòng chống SQL injection, XSS và các lỗ hổng phổ biến.