【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
相關文章
相關標籤/搜索