美文网首页
java连接MQTT+SSL服务器

java连接MQTT+SSL服务器

作者: 策马踏清风 | 来源:发表于2021-02-09 18:27 被阅读0次

    javassl加密方式连接mqtt服务器。其它ssl加密的也可以参考,SSLSocketFactory获取部分都是一样的。踩了很多坑,根据生成工具不同(opensslkeytool)以及秘钥文件编码不同有若干种方法。这里把自己遇到的所有情况都统一记录一下。

    一、连接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获取方式有两种:

    1. 通过CA证书、客户端证书、客户端私钥、私钥密码 获取(使用openssl生成的,keytool能生成证书,但是不能直接导出秘钥文件)
    2. 直接通过keystoretruststore获取(通过keytool生成的)

    读取证书和秘钥也有两种方式(证书获取的方式)

    1. 使用bcpkix-jdk15on包提供的方法,需要引包
    2. 使用原生方法,但是不支持直接读取pom秘钥文件,需要先把文件PKCS8编码一下。(编码方法在openssl的文章里)

    稍微解释一下上面的两种方式

    • 第一种,通过证书的方式
    1. CA证书是用来验证服务端发过来的证书,因为这里是双向认证,所以需要CA证书来认证服务端发过来的是否是合法证书。
    2. 客户端证书,发给服务端,让服务端验证的。(需要用CA证书签发,这样服务端那边才能用CA证书验证合法)
    3. 客户端私钥,服务端拿到客户端证书后会用证书里的公钥加密信息发过来,需要用私钥解密拿到原信息
    4. 私钥密码,openssl生成私钥的时候设置的密码(具体生成方式之前的文章有)
    • 第二种,通过keystoretruststore
    1. keystore是用jdk自带的工具keytool生成的秘钥和证书管理库,用来保存自己的秘钥和证书。需要用keytool生成并导入客户端的证书和秘钥。具体使用之前有文章可以参考。
    2. truststore本质也是keystore,只是里面存的是受信的证书。用来验证服务端证书是否可信,将CA导入即可
    3. 第一种方式本质也是通过keystoretruststore验证,只不过导入的步骤用代码实现了,第二种方式使用命令实现的。

    二、SslUtil具体实现

    1. 导入依赖
    <dependency>
        <groupId>org.bouncycastle</groupId>
        <artifactId>bcpkix-jdk15on</artifactId>
        <version>1.47</version>
    </dependency>
    
    
    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
            FileInputStream fis = new FileInputStream(keyStorePath);
            keyStore.load(fis, ksPass.toCharArray());
            fis.close();
            // 加载trustKeyStore
            FileInputStream trustFis = new FileInputStream(trustKeyStorePath);
            trustKeyStore.load(trustFis, tsPass.toCharArray());
            trustFis.close();
            // 创建管理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();
        }
    
    }
    

    三、不引包的方式

    1. pem秘钥文件pkcs8编码
    openssl pkcs8 -topk8 -in client.private.pem -out pkcs8.client.private.pem -nocrypt
    
    1. 代码
    /**
         * 用证书和私钥配置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);
            inStream.close();
            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);
            br.close();
            // 生成私钥
            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");
            FileInputStream p12Fis = new FileInputStream(p12Keystore);
            keyStore.load(p12Fis , p12Pwd.toCharArray());
            p12Fis.close();
            // 创建管理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();
        }
    
    • 单向认证,即不认证服务端的证书
    // 自定义一个不验证的TrustManager 即可
            final TrustManager trustManager = new X509TrustManager(){
                @Override
                public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    
                }
    
                @Override
                public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    
                }
    
                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[0];
                }
            };
            sslContext.init(kmf.getKeyManagers(), new TrustManager[] {trustManager}, new SecureRandom());
    
    • 不验证证书及ip是否匹配
            final TrustManager trustManager = new X509ExtendedTrustManager(){
    
                @Override
                public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException {
    
                }
    
                @Override
                public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException {
    
                }
    
                @Override
                public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException {
    
                }
    
                @Override
                public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException {
    
                }
    
                @Override
                public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    
                }
    
                @Override
                public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    
                }
    
                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[0];
                }
            };
            sslContext.init(kmf.getKeyManagers(), new TrustManager[] {trustManager}, new SecureRandom());
    

    关键词: java,ssl,mqtt

    相关文章

      网友评论

          本文标题:java连接MQTT+SSL服务器

          本文链接:https://www.haomeiwen.com/subject/fodcxltx.html