WebSocket SSL 加密浅析

1 WebSocket 原理

1.1 背景

WebSocket 是基于Http 协议的改进,Http 为无状态协议,基于短连接,需要频繁的发起请求,第二 Http 只能客户端发起请求,服务端无法主动请求。

1.2 相同点

都是基于TCP的应用层协议。
都使用Request/Response模型进行连接的建立。
在连接的建立过程中对错误的处理方式相同,在这个阶段WS可能返回和HTTP相同的返回码。
都可以在网络中传输数据。

1.3 不同点

WS使用HTTP来建立连接,但是定义了一系列新的header域,这些域在HTTP中并不会使用。
WS的连接不能通过中间人来转发,它必须是一个直接连接。
WS连接建立之后,通信双方都可以在任何时刻向另一方发送数据。
WS连接建立之后,数据的传输使用帧来传递,不再需要Request消息。
WS的数据帧有序。

WebSocket 分为握手和数据传输

1.4 握手

WebSocket 的握手基于http GET方法进行,

  1. 必须是有效的http request 格式
    HTTP request method 必须是GET,协议应不小于1.1 如: Get /chat HTTP/1.1
  2. 必须包括Upgrade 头域,并且其值为“websocket”,表明http 协议升级为WebSocket.
  3. 必须包括"Connection" 头域,并且其值为 "Upgrade"
  4. 必须包括"Sec-WebSocket-Key"头域,其值采用base64编码的随机16字节长的字符序列, 服务器端根据该域来判断client 确实是websocket请求而不是冒充的,如http。响应方式是,首先要获取到请求头中的Sec-WebSocket-Key的值,再把这一段GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"加到获取到的Sec-WebSocket-Key的值的后面,然后拿这个字符串做SHA-1 hash计算,然后再把得到的结果通过base64加密,就得到了返回给客户端的Sec-WebSocket-Accept的http响应头的值。
  5. 必须包括"Sec-webSocket-Version" 头域,当前值必须是13.
    可能包括"Sec-WebSocket-Protocol",表示client(应用程序)支持的协议列表,server选择一个或者没有可接受的协议响应之。
    可能包括"Sec-WebSocket-Extensions", 协议扩展, 某类协议可能支持多个扩展,通过它可以实现协议增强
  6. 可能包括任意其他域,如cookie
客户端的握手如下:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13

服务端的握手如下:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat

客户端和服务端都发送了握手,并且成功,数据传输即可开始。

1.5 数据传输

通过Http握手之后,如果是http 协议的话,tcp 连接会断开,这里在http 头部指明了升级为 websocket, 所以tcp 连接不断开。 WebSocket在握手后发送数据并象下层TCP协议那样由用户自定义,还是需要遵循对应的应用协议规范。 WebSocket 数据传输以数据帧的形式传输。

数据帧的结构如下图:

0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
|I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
|N|V|V|V|       |S|             |   (if payload len==126/127)   |
| |1|2|3|       |K|             |                               |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|     Extended payload length continued, if payload len == 127  |
+ - - - - - - - - - - - - - - - +-------------------------------+
|                               |Masking-key, if MASK set to 1  |
+-------------------------------+-------------------------------+
| Masking-key (continued)       |          Payload Data         |
+-------------------------------- - - - - - - - - - - - - - - - +
:                     Payload Data continued ...                :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|                     Payload Data continued ...                |
+---------------------------------------------------------------+
  1. FIN:1位,是否是消息的结束帧(分片)
  2. RSV1, RSV2, RSV3: 分别都是1位, 预留,用于约定自定义协议。 如果双方之间没有约定自定义协议,那么这几位的值都必须为0,否则必须断掉WebSocket连接;
  3. Opcode:4位操作码,定义有效负载数据,如果收到了一个未知的操作码,连接也必须断掉,以下是定义的操作码:
  1. 0 表示连续消息分片

  2. 1 表示文本消息分片

  3. 2 表未二进制消息分片

  4. 3-7 为将来的非控制消息片断保留的操作码

  5. 8 表示连接关闭

  6. 9 表示心跳检查的ping

  7. A 表示心跳检查的pong, 当收到一个Ping帧时,一个端点必须在响应中发送一个Pong帧

  8. B-F 为将来的控制消息片断的保留操作码

  9. Mask: 定义传输的数据是否有加掩码,如果设置为1,掩码键必须放在masking-key区域,客户端发送给服务端的所有消息,此位的值都是1;

  10. Payload length: 传输数据的长度,以字节的形式表示:7位、7+16位、或者7+64位。如果这个值以字节表示是0-125这个范围,那这个值就表示传输数据的长度;如果这个值是126,则随后的两个字节表示的是一个16进制无符号数,用来表示传输数据的长度;如果这个值是127,则随后的是8个字节表示的一个64位无符合数,这个数用来表示传输数据的长度。多字节长度的数量是以网络字节的顺序表示。负载数据的长度为扩展数据及应用数据之和,扩展数据的长度可能为0,因而此时负载数据的长度就为应用数据的长度。注意Payload length不包括Masking-key在内。

  11. Masking-key: 0或4个字节,客户端发送给服务端的数据,都是通过内嵌的一个32位值作为掩码的;掩码键只有在掩码位设置为1的时候存在。 数据Mask方法是,第 i byte 数据 = orig-data ^ (i % 4)

2 AndroidSync 框架

AndroidAsync 是一个基于nio的异步socket ,http(客户端服务器端),websocket,socket.io库,AndroidAsync 是一个底层的网络协议库。AndroidAsync 适合用于一个未被封装的Android的raw Socket, HTTP client/server, WebSocket, and Socket.IO。

特性

  1. 基于NIO,一个线程,回调驱动,高效
  2. 所有的操作返回一个Future,而且可以取消
  3. All operations return a Future that can be cancelled
  4. Socket client + socket server
  5. HTTP client + server
  6. WebSocket client + server
  7. Socket.IO 客户端

2.1 启动websocket 服务

void startWebSocketService(){

        AsyncHttpServer server = new AsyncHttpServer();
        server.setErrorCallback(new CompletedCallback() {
            @Override
            public void onCompleted(Exception ex) {
            }
        });

        server.listen(AsyncServer.getDefault(), 5000);

        server.websocket("/ws", new AsyncHttpServer.WebSocketRequestCallback() {
            @Override
            public void onConnected(final WebSocket webSocket, AsyncHttpServerRequest request) {
                mServiceSocket = webSocket;

                //Use this to clean up any references to your websocket
                webSocket.setClosedCallback(new CompletedCallback() {
                    @Override
                    public void onCompleted(Exception ex) {
                        try {
                            if (ex != null) {
                            }
                        } finally {
                        }
                    }
                });

                webSocket.setStringCallback(new WebSocket.StringCallback() {
                    @Override
                    public void onStringAvailable(String s) {
                        
                    }
                });
            }
        });
    }

2.2 客户端发起连接

void startWebSocketClient(){
        AsyncHttpClient.getDefaultInstance().websocket(WSURL,  null, new AsyncHttpClient.WebSocketConnectCallback() {
            @Override
            public void onCompleted(Exception ex, WebSocket webSocket) {
                if (ex != null) {;
                    return;
                }

                mClientSocket = webSocket;
                webSocket.send("Hello Server");
                webSocket.setStringCallback(new WebSocket.StringCallback() {
                    public void onStringAvailable(String s) {
                      
                    }
                });
            }
        });
    }

3 SSL

3.1 SSL加密

wss 建立在HTTPS 的基础上,在握手的时候使用HTTS 建立连接。HTTPS是HTTP over SSL/TLS,HTTP是应用层协议,TCP是传输层协议,在应用层和传输层之间,增加了一个安全套接层SSL/TLS。
HTTPS 连接过程如下图:

  1. 客户端发起一个https的请求,把自身支持的一系列Cipher Suite(密钥算法套件,简称Cipher)发送给服务端 ClientHello

  2. 服务端接收到客户端所有的Cipher后与自身支持的对比,如果不支持则连接断开,反之则会从中选出一种加密算法和HASH算法,以证书的形式返回给客户端。ServerHello Certificate ServerHelloDone

  3. 客户端收到服务端响应的证书后. client_key_exchange+change_cipher_spec+encrypted_handshake_message

  1. 第一步、校验证书的是否有效。关于客户端校验证书的是否有效已经做了详细的介绍,这里就不赘述了。
  2. 第二步、生成随机密码。如果证书验证通过,或者用户接受了不授信的证书,此时浏览器会生成一串随机密码,然后用证书中的公钥加密。
  3. 第三步、用最开始约定好的HASH方式,把握手消息取HASH值,把用 随机数密码加密 “握手消息+握手消息HASH值(签名)”和用公钥加密的随机密码 一起发送给服务端。把握手消息做一个签名,用于验证握手消息在传输过程中没有被篡改过。
  1. 服务端拿到客户端传来的密文,用自己的私钥来解密,获取随机密码,再用随机数密码 解密 握手消息与HASH值,并与传过来的HASH值做对比确认是否一致。然后用随机密码加密一段握手消息(握手消息+握手消息的HASH值 )给客户端。(此时服务器端已经获取到了客户端生成的随机密码了) 服务端用随机密码解密并计算握手消息的HASH,如果与客户端发来的HASH一致,此时握手过程结束。
    change_cipher_spec+encrypted_handshake_message

AndroidSync API
AndroidSync 对ssl 的支持如下:

        KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());

        ks.load(getContext().getResources().openRawResource(R.raw.keystore), "storepass".toCharArray());
        kmf.init(ks, "storepass".toCharArray());

        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        KeyStore ts = KeyStore.getInstance(KeyStore.getDefaultType());
        ts.load(getContext().getResources().openRawResource(R.raw.keystore), "storepass".toCharArray());
        tmf.init(ts);

        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

        AsyncHttpServer httpServer = new AsyncHttpServer();
        httpServer.listenSecure(8888, sslContext);
        httpServer.get("/", new HttpServerRequestCallback() {
            @Override
            public void onRequest(AsyncHttpServerRequest request, AsyncHttpServerResponse response) {
                response.send("hello");
            }
        });

        Thread.sleep(1000);

        AsyncHttpClient.getDefaultInstance().getSSLSocketMiddleware().setSSLContext(sslContext);
        AsyncHttpClient.getDefaultInstance().getSSLSocketMiddleware().setTrustManagers(tmf.getTrustManagers());
        AsyncHttpClient.getDefaultInstance().executeString(new AsyncHttpGet("https://localhost:8888/"), null).get();

3.2 自签名证书验证

ssl 连接需要证书,通常证书是CA机构发布,保证权威性。但是也可以使用自签名证书。如12306.根据我们车机的特点,可以采用自签名证书。

public SSLSocketFactory get12306SSLSocketFactory() {
        try {
            BufferedInputStream bis = new BufferedInputStream(new ByteArrayInputStream(CER_12306.getBytes()));
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            Certificate cert = certificateFactory.generateCertificate(bis);
            Log.d("https", cert.toString());

            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(null);
            keyStore.setCertificateEntry("12306", cert);


            SSLContext sslContext = SSLContext.getInstance("TLS");
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(keyStore);
            sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());

            return sslContext.getSocketFactory();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    void httpConnect(){
        try {
            URL url;
            url = new URL("https://kyfw.12306.cn/otn/");
            HttpsURLConnection.setDefaultSSLSocketFactory(get12306SSLSocketFactory());
            HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.connect();
            if(conn.getResponseCode() == HttpURLConnection.HTTP_OK){
                Log.e("https", "Https Certificate Success!");
                Message msg = Message.obtain();
                msg.what = HttpsActivity.HTTPS_SUCCES_12306;
                mViewHandler.sendMessage(msg);
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

3.3 SSL 中间人攻击

HTTPS ,是一种网络安全传输协议,利用 SSL/TLS 来对数据包进行加密,以提供对网络服务器的身份认证,保护交换数据的隐私与完整性。 中间人攻击, Man-in-the-middle attack ,缩写: MITM ,是指攻击者与通讯的两端分别创建独立的联系,并交换其所收到的数据,使通讯的两端认为他们正在通过一个私密的连接与对方直接对话,但事实上整个会话都被攻击者完全控制。
https 在理论上是可以抵御 MITM ,但是由于开发过程中的编码不规范,导致 https 可能存在 MITM 攻击风险,攻击者可以解密、篡改 https 数据。 0X02 https 漏洞 Android https 的开发过程中常见的安全缺陷:

1)在自定义实现 X509TrustManager 时, checkServerTrusted 中没有检查证书是否可信,导致通信过程中可能存在中间人攻击,造成敏感数据劫持危害。

2)在重写 WebViewClient 的 onReceivedSslError 方法时,调用 proceed 忽略证书验证错误信息继续加载页面,导致通信过程中可能存在中间人攻击,造成敏感数据劫持危害。

3)在自定义实现 HostnameVerifier 时,没有在 verify 中进行严格证书校验,导致通信过程中可能存在中间人攻击,造成敏感数据劫持危害。

4)在 setHostnameVerifier 方法中使用 ALLOW_ALL_HOSTNAME_VERIFIER ,信任所有 Hostname ,导致通信过程中可能存在中间人攻击,造成敏感数据劫持危害。

Android安全开发之安全使用HTTPS

窃听风暴:Android平台https嗅探劫持漏洞

浅析HTTPS中间人攻击与证书校验

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容