【HTTP基础】HTTPS原理及WebSocket原理

因为HTTP没有加密机制,其传输的内容很容易泄漏,而且HTTP协议无法确认通讯方,也没法保证接收到的报文在传输过程当中是否被篡改,所以HTTPS是在HTTP协议的基础上提供了加密、认证和完整性保护的功能。HTTPS并不是是应用层的一种新协议,只是HTTP通讯接口部分用SSL和TLS协议代替而已,一般HTTP直接和传输层的TCP协议通讯,当使用了SSL后,HTTP先和SSL协议通讯,SSL再和TCP协议通讯。java

加密方法

近代的加密方法中加密算法是公开的,而密钥倒是保密的,加密和解密都须要用到密钥,没有密钥就无法对加密内容进行解密,经过这种方式得以保持加密方法的安全性。加密和解密同用一个密钥的方式陈为共享密钥加密,也被称为对称密钥加密。以共享密钥方式的加密必须将密钥也发给对方,在网络上发送密钥很容易被攻击者获取。而且服务器若是对全部客户端都使用一样的共享密钥,无异于没有加密,因此HTTPS采用生成的随机数来做为共享加密算法的密钥。web

公开密钥加密使用一对非对称的密钥,一把叫作私有密钥,一把叫作公有密钥,发送密文的一方使用对方的公开密钥进行加密处理,对方收到被加密的信息后,再使用本身的私有密钥进行解密,这样就不用担忧密钥被攻击者获取。算法

HTTPS采用共享密钥加密和公开密钥加密二者并用的混合加密机制,若是仅仅保证密钥的安全性,使用公开密钥加密的方式就能够实现了,可是公开密钥加密方式比共享密钥加密,其处理速度要慢。因此HTTPS在交换密钥环节采用公开密钥加密方式,以后创建通讯交换报文阶段采用共享密钥加密方式数组

公开密钥加密方式也不能保证公开密钥自己的真实性,好比,在与某台服务器创建链接时,没法保证接收到的公开密钥就是须要链接的那个服务器的密钥,这个时候能够采用数字证书认证机构和其相关机关颁发的公开密钥证书。浏览器

HTTPS的握手过程

  1. 首先,客户端会发送一个https的请求,把自身支持的一系列密钥算法组件(Cipher Suite)发送给服务器。
  2. 服务器接收到客户端全部的Cipher后与自身支持的对比,若是不支持则链接断开,反之则会从中选择一种加密算法和HASH算法以证书的形式返回给客户端,证书中包含了加密公钥,颁证机构,网站地址,失效日期等等。
  3. 客户端收到服务器端的响应后会作如下几件事:
    1:验证证书的合法性,颁发证书的机构是否合法与是否过时,证书中包含的网站地址是否与正在访问的地址一致等,证书验证经过后,在浏览器的地址栏会加上一把小锁。
    2:证书验证经过后,生成随机密码,用证书中的公钥加密。
    3:使用约定好的HASH计算握手消息,并使用生产的随机数对消息进行加密,最后将以前生成的全部消息发送给网站。
  4. 服务器接收到客户端传来的密文,用本身的私钥来解密取出随机数密码,而后用随机数密码解密浏览器发送过来的握手消息,并验证HASH是不是与浏览器发来的一致,而后使用密码加密一段握手消息,发送给浏览器。
  5. 客户端用随机数解密并计算出握手消息的HASH,若是与服务器端发来的HASH一致,此时握手过程结束。

以后全部的通讯数据将由以前浏览器生成的随机密码并利用对称加密算法进行加密。由于这串密码只有客户端和服务器知道,全部即便中间请求被拦截也是无法解密数据的,以此保证了通讯的安全。
其中非对称加密算法用于在握手消息过程当中加密生成的随机数密码,对称加密算法用于对真正传输的数据进行加密,而HASH算法用于验证数据的完整性,TLS握手过程当中若是有任何错误,都会使加密链接断开,从而阻止了隐私信息的传输,因为HTTPS很是的安全,攻击者没法从中找到下手的地方,因而更多的是采用了假证书的手法来欺骗客户端,从而获取明文的信息。安全

HTTPS攻击手段

浏览器在对证书进行验证时,如下几种状况会致使验证失败:服务器

  1. SSL证书不是由受信任的CA机构颁发的
  2. 证书过时
  3. 访问的网站域名与证书绑定的域名不一致

对HTTPS最多见的攻击手段就是SSL证书欺骗或者叫SSL劫持,是一种典型的中间人攻击,不过SSL劫持并不是只是用于攻击目的,在一些特殊状况下利用SSL劫持能够更顺畅的访问网络。SSL劫持须要将攻击者接入到浏览器与目标网站之间,在传输数据的过程当中,替换目标网站发给浏览器的证书,以后解密传输的数据,中间人攻击最好的环境是在局域网中,由于局域网中全部的计算机须要经过网关来接入互联网,所以攻击者只须要实施一次中间人攻击就能够顺利的截获全部计算机与网关之间传输的数据。通常SSL劫持,浏览器会给出证书错误的提示,若是继续访问,全部加密的数据均可以被攻击者解密。websocket

SSLStrip攻击也须要将攻击者设置为中间人,以后将HTTPS访问替换为HTTP返回给浏览器,因为HTTP协议传输的数据都是未加密的,从而截获用户访问的数据。对于登陆帐号密码等关键信息,能够在发送以前用javaScript进行一次加密处理,这种方法对SSLScrip和SSL劫持都是有效的。网络

HTTPS也存在一些问题,那就是处理速度会变慢,一是因为存在HTTPS握手环节,致使通讯变慢,二是因为存在传输信息加密处理,消耗CPU及内存资源,这对于高并发的服务器而言,更是一种性能瓶颈,因此服务器只是对含有敏感信息的数据进行加密。并发

WebSoket原理

HTTP协议的通讯都是浏览器发出一个请求,服务器接收请求后进行处理并返回结果,浏览器再将接收到的信息进行渲染,这种机制对于实时性要求高的应用场景显得捉襟见肘,传统请求-响应模式的web开发一般采用轮询方案,就是浏览器以必定时间间隔频繁的向服务器请求数据,来保持客户端数据的实时更新,可是服务器数据可能并无更新,会带来不少无谓的请求,浪费带宽,效率低下。

webSocket是HTML5下的一种新的协议,基于TCP传输协议,自己属于应用层协议,而且复用了HTTP握手通道。它实现了浏览器与服务器的全双工通讯,能更好的节省服务器资源和带宽并达到实时通信的目的,webSocket与HTTP长链接的区别:HTTP长链接在创建TCP链接后,在每一个请求中任须要单独发送请求头,数据传输的效率低,而且服务器不能主动给浏览器推送数据。

WebSocket创建链接

webSocket借用HTTP的协议来完成握手,首先经过HTTP发起请求报文,报文以下:

GET / HTTP/1,1
Host: localhost:8080
Origin: http://127.0.0.1:3000
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-key:w4v7O6xFTi36lq3RNcgctw==
Sec-WebSocket-Protocol:chat, superchat
Sec-WebSocket-Version:13

在上面的HTTP请求头中,Connection和Upgrade字段表示请求服务器升级协议为webSocket,其中Sec-WebSocket-key的值是随机生成的Base64编码的字符串,Sec-WebSocket-Protocol和Sec-WebSocket-Version字段指定子协议和版本。服务器响应头以下:

HTTP/1.1 101 Switching Protocols
Connection:Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: Oy4NRAQ13jhfONC7bP8dTKb4PTU=

状态码101表示协议切换,到此完成协议升级,后续的数据交互都按照新的协议来。Sec-WebSocket-Accept是根据请求字段Sec-WebSocket-key计算出来的,其计算过程为:

  1. 将Sec-WebSocket-Key跟258EAFA5-E914-47DA-95CA-C5AB0DC85B11字符串进行拼接,造成新的字符串w4v7O6xFTi36lq3RNcgctw==258EAFA5-E914-47DA-95CA-C5AB0DC85B11;
  2. 经过sha1算法计算出结果,并转换成base64字符串。

浏览器会校验Sec-WebSocket-Accept的值,若是成功,webSocket的握手成功将开始接下来的数据传输。

webSocket传输数据的基本单位是帧,通常webScoket传输的一条消息会被切割成多个帧,数据帧有一个标志位FIN,FIN=1表示这是消息的最后一帧,接收端接收到帧数据后,若是该帧的FIN标识为1,就会将已经接收到的数据帧组装成一个完整的消息。

WebSocket实战

HTML5中的WebSoket

var Socket = new WebSocket(url,[protocol])

URL字符串必须以“ws”或“wss”(加密通讯时)文字做为开头,protocol是Web应用可以使用的协议,能够是字符串,也能够是数组,若是一下代码中proto1和proto2是定义明确、可能已注册且标准化的协议名称,它们可以同时为客户端和服务器所理解,服务器会从列表中选择首选协议。webSocket支持onopen、onmessage、onclose、onerror四个异步事件。

if(window.WebScoket){
        var url = 'ws://localhost:8080/test'
        var socket = new WebSocket(url,["proto1","proto2"])

        socket.onopen = function(){
            socket.send('message')
        }
        socket.onmessage = function(e){
            console.log(e.data)
        }
        socket.onclose = function(){

        }
        socket.onerror = function(){

        }

        var arr = new Uint8Array([8,3,4,5,7,0,9])
        socket.send(arr.buffer)

        socket.binaryType = 'arrayBuffer'    // 设置接收ArrayBuffer对象
        socket.binaryType = 'blob'       // 设置接收blob对象
        socket.onmessage = function(e){
            console.log(e.data)
        }
    }

webSocket.bufferAmount属性表示已在WebSocket上发送但还没有写入网络的字节数,通常用于调节发送速率。WebSocket API支持以二进制发送Blob和ArrayBuffer实例。固然也能够设置接收Blob和ArrayBuffer对象。

Nodejs中的webSocket

webSocket的数据帧格式为如下部分:
图片描述
FIN:1位,表示是否结束, 1:结束
RSV[1-3]:用于协商扩展
opcode: 4位,0,1,2属于数据帧,8,9,10属于控制帧
mask:掩码,0表示不使用亚掩码,通常服务器回消息不会使用掩码,接收消息须要掩码
payloadLen 0-125 直接表示 数据长度

126,后面16位对应数据长度
        127,后面64位对应数据长度

masking-key:若是mask为1,后面32位做为masking-key,mask为0,则缺省
payload Data: 负载的数据
其链接的代码为:

const server = http.createServer((req,res) => {
    res.writeHead(200,{'Content-Type':'text/plain'})
    res.send('hello world')
})



server.on('upgrade',(req,socket,upgradeHead) => {
    var key = req.headers['sec-websocket-key']
    var accept = crypto.createHash('sha1').update(key + GUID).digest('base64')
    var headers = [
        'HTTP/1.1 101 Switching protocols',
        'Upgrade: websocket',
        'Connection: Upgrade',
        'Sec-WebSocket-Accept: ' + accept,
        '\r\n'
    ]
    socket.setNoDelay(true)
    socket.write(headers.join('\r\n'))
    var websocket = new WebSocket()
    websocket.setSocket(socket)
})
// websocket对象

function WebSocket(){
    this.socket = null
    this.buffer = null
}
WebSocket.prototype.setSocket = function(socket){
    this.socket = socket
    var data = []
    this.socket.on('data',(chunk) => {
        data.push(chunk)
    })
    this.socket.on('end',() => {
        this.buffer = Buffer.concat(data)
        var frame = this.decodeMessage(this.buffer)
        console.log(frame)
        console.log(frame.payloadData.toString())
    })
}
WebSocket.prototype.decodeMessage = (buffer) => {
    let start = 0
    let frame = {
        isFinal:(buffer[start] & 0x80) === 0x80,
        opcode:buffer[start++] & 0xF,
        masked:(buffer[start] & 0x80) === 0x80,
        payloadLen:buffer[start++] & 0x7F,
        maskingKey:'',
        payloadData:null
    }
    if(frame.payloadLen === 126){
        frame.payloadLen = (buffer[start++] << 8) + buffer[start++]
    }else if(frame.payloadLen === 127){
        frame.payloadLen = 0
        for(let i = 7; i >= 0; i--){
            frame.payloadLen = (buffer[start++] << (8 * i))
        }
    }
    if(frame.payloadLen){
        if(frame.masked){
            const maskingKey = [
                buffer[start++],
                buffer[start++],
                buffer[start++],
                buffer[start++]
            ]
            frame.maskingKey = maskingKey
            frame.payloadData = buffer.slice(start,start + frame.payloadLen)
                .map((byte,idx) => byte ^ maskingKey[idx % 4])
        }else{
            frame.payloadData = buffer.slice(start,start + frame.payloadLen)
        }
    }
    return frame
}


module.exports = WebSocket
相关文章
相关标签/搜索