Java WebSocket Security 加密通信实践

作者: 捉影T_T900 | 来源:发表于2018-06-27 16:29 被阅读12次

    最近在项目中用到了WebSocket通信的内容

    第一版的实现方案:spring-websocket + nv-websocket-client

    第一版的模式已经正常得到应用,但有个问题,就是传输过程没有加密,完全明文的方式,这在当前的时代不合时宜,所以考虑对通信过程进行加密传输。

    第二版的实现方案:Java-WebSocket

    这是一个Java的WebSocket开发组件,支持整合到后端、客户端,支持SSL/TLS加密传输协议。由于我目前的项目主要应用场景在局域网内,所以对于常规的CA签名证书部署并不合适,我就用自签名的形式进行加密传输检验。

    话不多说,直接开干。

    一、准备工作

    准备自签名证书

    keytool -genkey -validity 3650 -keyalg RSA -sigalg SHA256withRSA -keystore "keystore.jks" -storepass "storepassword" -keypass "keypassword" -alias "default" -dname "CN=127.0.0.1, OU=MyOrgUnit, O=MyOrg, L=MyCity, S=MyRegion, C=MyCountry"
    

    这里要特别注意 -keyalg RSA -sigalg SHA256withRSA 这两个属性,后面要将jks转换成bks才能在Android平台使用,没有这两个属性会导致handshake的时候报“Possible no handshake recieved!”,说明签名校验不通过。
    github上的Issue说明

    将jks签名文件转换成bks,因为Android只能读取bks格式的签名秘钥,转换方法有好几种,这里介绍一个命令行方式的
    转换方法

    二、程序实现

    java后台

            WebSocketImpl.DEBUG = true;
            ChatServer chatServer = new ChatServer(9089); // 这里可以自定义端口,默认80
            logger.info("charServer port:" + chatServer.getPort());
    
            // load up the key store
            String STORETYPE = "JKS";
            String KEYSTORE = "/Users/randysu/keystore.jks";
            String STOREPASSWORD = "storepassword"; // 之前在制作keystore.jks时指定的store密码
            String KEYPASSWORD = "keypassword"; // 之前在制作keystore.jks时指定的key密码
    
            KeyStore ks = KeyStore.getInstance( STORETYPE );
            File kf = new File( KEYSTORE );
            ks.load( new FileInputStream( kf ), STOREPASSWORD.toCharArray() );
    
            KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
            kmf.init( ks, KEYPASSWORD.toCharArray() );
            TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
            tmf.init( ks );
    
            SSLContext sslContext = null;
            sslContext = SSLContext.getInstance( "TLS" );
            sslContext.init( kmf.getKeyManagers(), tmf.getTrustManagers(), null );
    
            // DefaultSSLWebSocketServerFactory  这里用的是默认的加密仓库,默认允许加载所有的签名方式,也可以自定义加密仓库,移除不能校验通过的签名方式
    //        SSLEngine engine = sslContext.createSSLEngine();
    //        List<String> ciphers = new ArrayList<String>( Arrays.asList(engine.getEnabledCipherSuites()));
    //        ciphers.remove("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256");
    //        List<String> protocols = new ArrayList<String>( Arrays.asList(engine.getEnabledProtocols()));
    //        protocols.remove("SSLv3");
    //        CustomSSLWebSocketServerFactory factory = new CustomSSLWebSocketServerFactory(sslContext, protocols.toArray(new String[]{}), ciphers.toArray(new String[]{}));
    //        chatserver.setWebSocketFactory(factory);
    
            chatServer.setWebSocketFactory( new DefaultSSLWebSocketServerFactory( sslContext ) );
    
            chatServer.start();
    

    Android端

            // load up the key store
            String STORETYPE = "bks";
            String KEYSTORE = Environment.getExternalStorageDirectory().getPath() + File.separator + "keystore.bks";
            String STOREPASSWORD = "storepassword";
            String KEYPASSWORD = "keypassword";
    
            KeyStore ks = KeyStore.getInstance(STORETYPE);
            File kf = new File(KEYSTORE);
            ks.load(new FileInputStream(kf), STOREPASSWORD.toCharArray());
    
            KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
            kmf.init(ks, KEYPASSWORD.toCharArray());
            TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
            tmf.init(ks);
    
            SSLContext sslContext = null;
            sslContext = SSLContext.getInstance("TLS");
            sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
    
            SSLSocketFactory factory = sslContext.getSocketFactory();
    
            URI socketUri = new URI("wss://192.168.1.230:9089/server");
            SSLWebSocketClient socketClient = new SSLWebSocketClient(socketUri, mWsListener);
            socketClient.setSocket(factory.createSocket());
            socketClient.connect();
    //        socketClient.connectBlocking();  // 阻塞当前线程直至后台返回连接成功或失败
    

    SSLWebSocketClient 继承 WebSocketClient(Java-WebSocketde的对象),mWsListener是一个连接状态的回调接口,在里面处理连接状态。

    mWsListener = new WsListener() {
                @Override
                public void onConnected(ServerHandshake handshakedata) {
                    //连接成功
                    Logger.i("websoeket opened connection");
                   
                }
    
                @Override
                public void onTextMessage(String message) {
                    //服务端消息来了
                    Logger.i("websoeket received:" + message);
                    
                }
    
                @Override
                public void onDisconnected(int code, String reason, boolean remote) {
                    //连接断开,remote判定是客户端断开还是服务端断开
                    Logger.i("websoeket Connection closed by " + (remote ? "remote server" : "us"));
                    
                }
    
                @Override
                public void onConnectError(Exception ex) {
                    // 连接错误
                    Logger.i("websoeket error:" + ex);
                    
                }
            };
        }
    

    java后台向Android发送消息

    WebSocket对象的send(String str)方法
    

    Android端向java后台发送消息

    SSLWebSocketClient的send(String str)方法
    

    这里要注意一点,Android定义的证书格式是“ X509”,Java后台定义的证书格式是“ SunX509”,如果Android定义“SunX509”会报错,同理也不能在Java后台定义“X509”。

    三、实测结果

    屏幕快照 2018-06-27 下午4.27.24.png

    推荐一本书《HTTPS权威指南》
    Done!

    相关文章

      网友评论

        本文标题:Java WebSocket Security 加密通信实践

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