当前位置: 首页 > 工具软件 > SSL Decoder > 使用案例 >

java nio ssl_java连接MQTT+SSL服务器

韦宣
2023-12-01

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();

}

 类似资料: