java nio ssl_java连接MQTT+SSL服务器
java用ssl加密方式連接mqtt服務器。其它ssl加密的也可以參考,SSLSocketFactory獲取部分都是一樣的。踩了很多坑,根據生成工具不同(openssl和keytool)以及秘鑰文件編碼不同有若干種方法。這里把自己遇到的所有情況都統一記錄一下。
一、連接MQTT服務器
不加密的連接方式之前有寫過,就不贅述了,這里列出不同的地方
mqttClient = new MqttClient(host, clientId, new MemoryPersistence());
MqttConnectOptions options = new MqttConnectOptions();
options.setCleanSession(true);
// 這里多了一步設置SSLSocketFactory的步驟
options.setSocketFactory(SslUtil.getSocketFactoryByCert(caPath,certPath,privateKeyPath, privateKeyPwd));
SSLSocketFactory獲取方式有兩種:
通過CA證書、客戶端證書、客戶端私鑰、私鑰密碼 獲取(使用openssl生成的,keytool能生成證書,但是不能直接導出秘鑰文件)
直接通過keystore和truststore獲取(通過keytool生成的)
讀取證書和秘鑰也有兩種方式(證書獲取的方式)
使用bcpkix-jdk15on包提供的方法,需要引包
使用原生方法,但是不支持直接讀取pom秘鑰文件,需要先把文件PKCS8編碼一下。(編碼方法在openssl的文章里)
稍微解釋一下上面的兩種方式
第一種,通過證書的方式
CA證書是用來驗證服務端發過來的證書,因為這里是雙向認證,所以需要CA證書來認證服務端發過來的是否是合法證書。
客戶端證書,發給服務端,讓服務端驗證的。(需要用CA證書簽發,這樣服務端那邊才能用CA證書驗證合法)
客戶端私鑰,服務端拿到客戶端證書后會用證書里的公鑰加密信息發過來,需要用私鑰解密拿到原信息
私鑰密碼,openssl生成私鑰的時候設置的密碼(具體生成方式之前的文章有)
第二種,通過keystore和truststore
keystore是用jdk自帶的工具keytool生成的秘鑰和證書管理庫,用來保存自己的秘鑰和證書。需要用keytool生成并導入客戶端的證書和秘鑰。具體使用之前有文章可以參考。
truststore本質也是keystore,只是里面存的是受信的證書。用來驗證服務端證書是否可信,將CA導入即可
第一種方式本質也是通過keystore和truststore驗證,只不過導入的步驟用代碼實現了,第二種方式使用命令實現的。
二、SslUtil具體實現
導入依賴
org.bouncycastle
bcpkix-jdk15on
1.47
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMReader;
/***
* 兩種方式驗證
* @author colin
* @date 2021-02-03 14:39
* @since 1.0.0
*/
public class SslUtil {
/**
* 用證書和私鑰配置sslContext
*
* @param caCrtFile
* CA證書(驗證連接)
* @param crtFile
* 發給對方的證書
* @param keyFile
* pem 私鑰(請求連接的消息是用公鑰加密的,需要用私鑰解密)
* @param password
* 私鑰密碼
* @return
* @throws Exception
*/
public static SSLSocketFactory getSocketFactoryByCert(final String caCrtFile, final String crtFile,
final String keyFile, final String password) throws Exception {
Security.addProvider(new BouncyCastleProvider());
// 加載CA證書(用于驗證的根證書)
PEMReader reader =
new PEMReader(new InputStreamReader(new ByteArrayInputStream(Files.readAllBytes(Paths.get(caCrtFile)))));
X509Certificate caCert = (X509Certificate)reader.readObject();
reader.close();
// 加載自己的證書,用于發送給客戶端
reader = new PEMReader(new InputStreamReader(new ByteArrayInputStream(Files.readAllBytes(Paths.get(crtFile)))));
X509Certificate cert = (X509Certificate)reader.readObject();
reader.close();
// 加載私鑰
reader = new PEMReader(new InputStreamReader(new ByteArrayInputStream(Files.readAllBytes(Paths.get(keyFile)))),
() -> password.toCharArray());
KeyPair key = (KeyPair)reader.readObject();
reader.close();
// 用CA證書創建TrustManagerFactory
KeyStore caKs = KeyStore.getInstance(KeyStore.getDefaultType());
caKs.load(null, null);
caKs.setCertificateEntry("ca-certificate", caCert);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(caKs);
// 用證書和私鑰創建KeyManagerFactory
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null, null);
ks.setCertificateEntry("certificate", cert);
ks.setKeyEntry("private-key", key.getPrivate(), password.toCharArray(),
new java.security.cert.Certificate[] {cert});
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, password.toCharArray());
SSLContext context = SSLContext.getInstance("TLSv1");
// kmf用于發送關鍵信息讓服務端校驗,tmf用于校驗服務端的證書。雙向認證
context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
return context.getSocketFactory();
}
/**
* 通過keyStore加載
*
* @param keyStorePath
* keystore路徑(保存自己的秘鑰和證書)
* @param trustKeyStorePath
* truststore路徑(保存受信的證書)
* @param ksPass
* keystore密碼
* @param tsPass
* truststore密碼
* @return
* @throws Exception
*/
public static SSLSocketFactory getSocketFactoryByKeystore(String keyStorePath, String trustKeyStorePath,
String ksPass, String tsPass) throws Exception {
// keytool生成的keystore的類型就是JKS
KeyStore keyStore = KeyStore.getInstance("JKS");
KeyStore trustKeyStore = KeyStore.getInstance("JKS");
// 通過密碼加載keystore
keyStore.load(new FileInputStream(keyStorePath), ksPass.toCharArray());
// 加載trustKeyStore
trustKeyStore.load(new FileInputStream(trustKeyStorePath), tsPass.toCharArray());
// 創建管理JKS密鑰庫的密鑰管理器 (SunX509)
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
// 使用密鑰內容源初始化此工廠。 提供者通常使用 KeyStore 來獲取在安全套接字協商期間所使用的密鑰內容
kmf.init(keyStore, ksPass.toCharArray());
// SunX509
TrustManagerFactory tmFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmFactory.init(trustKeyStore);
// 初始sslcontext
SSLContext sslContext = SSLContext.getInstance("SSLv3");
// SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), tmFactory.getTrustManagers(), new SecureRandom());
return sslContext.getSocketFactory();
}
}
三、不引包的方式
將pem秘鑰文件pkcs8編碼
openssl pkcs8 -topk8 -in client.private.pem -out pkcs8.client.private.pem -nocrypt
代碼
/**
* 用證書和私鑰配置sslContext
*
* @param caCrtFile
* CA證書(驗證連接)
* @param crtFile
* 發給對方的證書
* @param keyFile
* 私鑰(請求連接的消息是用公鑰加密的,需要用私鑰解密)
* @param password
* 私鑰密碼
* @return
* @throws Exception
*/
public static SSLSocketFactory getSocketFactoryByCert(final String caCrtFile, final String crtFile,
final String keyFile, final String password) throws Exception {
// 加載CA證書(用于驗證的根證書)
X509Certificate caCert = getCertificate(caCrtFile);
// 加載自己的證書,用于發送給客戶端
X509Certificate cert = getCertificate(crtFile);
// 加載私鑰
final PrivateKey privateKey = getPrivateKey(keyFile);
// 用CA證書創建TrustManagerFactory
KeyStore caKs = KeyStore.getInstance(KeyStore.getDefaultType());
caKs.load(null, null);
caKs.setCertificateEntry("ca-certificate", caCert);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(caKs);
// 用證書和私鑰創建KeyManagerFactory
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null, null);
ks.setCertificateEntry("certificate", cert);
ks.setKeyEntry("private-key", privateKey, password.toCharArray(), new java.security.cert.Certificate[] {cert});
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, password.toCharArray());
SSLContext context = SSLContext.getInstance("TLSv1");
// kmf用于發送關鍵信息讓服務端校驗,tmf用于校驗服務端的證書。雙向認證
context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
return context.getSocketFactory();
}
/**
* 讀取x509格式的證書
*
* @param certPath
* @return
* @throws FileNotFoundException
* @throws CertificateException
*/
private static X509Certificate getCertificate(String certPath) throws FileNotFoundException, CertificateException {
InputStream inStream = new FileInputStream(certPath);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate caCert = (X509Certificate)cf.generateCertificate(inStream);
return caCert;
}
/**
* 讀取 PKCS8 編碼的 RSA 秘鑰文件
*
* @param path
* @return
* @throws IOException
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
*/
private static PrivateKey getPrivateKey(String path)
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
BufferedReader br = new BufferedReader(new FileReader(path));
String s = br.readLine();
String str = "";
s = br.readLine();
while (s.charAt(0) != '-') {
str += s + "\r";
s = br.readLine();
}
// BASE64Decoder base64decoder = new BASE64Decoder();
byte[] bytes = Base64.getMimeDecoder().decode(str);
// byte[] bytes = base64decoder.decodeBuffer(str);
// 生成私鑰
KeyFactory kf = KeyFactory.getInstance("RSA");
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytes);
PrivateKey privateKey = kf.generatePrivate(keySpec);
return privateKey;
}
發現項目中有生成好的p12證書,可以直接使用。這里再追加一種p12證書和CA證書驗證的方式
/**
* 通過p12證書和ca證書雙向認證
*
* @param caCrtFile
* @param p12Keystore
* @param p12Pwd
* @return
* @throws Exception
*/
public static SSLSocketFactory getSocketFactoryByP12AndCA(String caCrtFile, String p12Keystore, String p12Pwd)
throws Exception {
// 加載CA證書(用于驗證的根證書)
X509Certificate caCert = getCertificate(caCrtFile);
// 用CA證書創建TrustManagerFactory
KeyStore caKs = KeyStore.getInstance(KeyStore.getDefaultType());
caKs.load(null, null);
caKs.setCertificateEntry("ca-certificate", caCert);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(caKs);
KeyStore keyStore = KeyStore.getInstance("pkcs12");
keyStore.load(new FileInputStream(p12Keystore), p12Pwd.toCharArray());
// 創建管理JKS密鑰庫的密鑰管理器 (SunX509)
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
// 使用密鑰內容源初始化此工廠。 提供者通常使用 KeyStore 來獲取在安全套接字協商期間所使用的密鑰內容
kmf.init(keyStore, p12Pwd.toCharArray());
// 初始sslcontext
SSLContext sslContext = SSLContext.getInstance("SSLv3");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
return sslContext.getSocketFactory();
}
總結
以上是生活随笔為你收集整理的java nio ssl_java连接MQTT+SSL服务器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 米哈游新作《崩坏:星穹铁道》登陆苹果 A
- 下一篇: 适配 M1 的新版 Skype 运行速度