理解加密算法(三)——建立CA機構,簽發證書並開始TLS通訊

理解加密算法(一)——加密算法分類理解加密算法(二)——TLS/SSLjavascript

1 不安全的TCP通訊

普通的TCP通訊數據是明文傳輸的,因此存在數據泄露和被篡改的風險,咱們能夠寫一段測試代碼試驗一下。java

TCP Server:node

const net=require('net');
const server=net.createServer();
const serverHost='127.0.0.1';
const serverPort=8888;

server.on('connection',(clientSocket)=>{
    clientSocket.setEncoding('utf8');
    clientSocket.on('data',(data)=>{
        console.log(`client say:${data}`);
    });
    clientSocket.on('error',()=>{});
});

server.listen({host:serverHost,port:serverPort},()=>{
    console.log(`server is listening on port ${8888}`)
});

TCP Client:git

const net=require('net');
const socket=new net.Socket();
const serverHost='127.0.0.1';
const serverPort=8888;

let index=0;
socket.on('error',()=>{});
socket.connect({host:serverHost,port:serverPort},()=>{
    console.log(`client has connected to host ${serverHost} , port ${serverPort}`);
    setInterval(()=>{
        socket.write(`i love u ${index++}`);
    },3000);
});

啓動Server和Client後,能夠在Server的控制檯中看到來自Client的消息:github

client say:i love u 0
client say:i love u 1
client say:i love u 2
client say:i love u 3
client say:i love u 4

1.1 數據泄露

數據在傳輸的過程當中是能夠被全部人看到的,能夠用WireShark抓包測試一下。因爲WireShark沒法直接抓取發送給本地的TCP包,我將Server部署到了另一臺機器上,須要作以下修改:算法

  • serverHost都修改成另外一臺機器的IP。
  • 打開Server機器防火牆的8888端口。

配置好抓取IP:

抓包:

能夠看到,表白信息全被別人看了去了 :(chrome

可能有人會說:我臉皮厚,隨便看~瀏覽器

可是要注意了,全部http協議的請求,他們的數據都是這樣發送的!能夠認爲,在一個使用http協議而不是https協議的網站上,你的遊戲帳號、銀行卡密碼,都是這樣赤果果的暴露在別人眼前的!安全

不只如此,別人還能夠隨意篡改你的數據!服務器

1.2 數據篡改

咱們上網的過程當中,數據從咱們的電腦到達目標服務器的過程當中,可能會通過層層代理和屢次路由,最終纔到達目標服務器並非像上面咱們的Demo那樣是直連的!

爲了模擬這種狀況,咱們能夠在Demo的Client和Server之間加上一個耿直的Proxy:

const net=require('net');
const proxyServer=net.createServer();

const proxyHost='127.0.0.1';
const proxyPort=8889;

const serverHost='127.0.0.1';
const serverPort=8888;

//代理鏈接到真實目標Server
const proxySocket=new net.Socket();
proxySocket.connect({host:serverHost,port:serverPort},()=>{
    console.log(`proxy has connected to host ${serverHost} , port ${serverPort}`);
});
//啓動代理Server
proxyServer.on('connection',(clientSocket)=>{
    //直接將客戶端的數據發給真實目標Server
    clientSocket.pipe(proxySocket);
});
proxyServer.listen({host:proxyHost,port:proxyPort},()=>{
    console.log(`proxy server is listening on port ${8889}`)
});

修改Client的鏈接端口,連到proxy的8889端口而不是真實目標的8888端口,依次啓動Server→Proxy→Client,能夠看到Server收到了:

client say:i love u 0
client say:i love u 1
client say:i love u 2
client say:i love u 3

須要注意到這一行代碼 clientSocket.pipe(proxySocket),因此說這是一個耿直的代理:)

換一個不耿直的代理,它會這樣作:

clientSocket.setEncoding('utf8');
    clientSocket.on('data',(data)=>{
    data=data.replace(/love/g,'hate');
    proxySocket.write(data);
});

從新依次啓動Server→Proxy→Client,能夠看到Server收到了:

client say:i hate u 0
client say:i hate u 1
client say:i hate u 2
client say:i hate u 3
client say:i hate u 4

這下shabi了吧,妹子確定是追不到了:( 咋辦呢?

2 CA機構

先梳理一下思路:按照以前瞭解的加密算法原理,咱們可讓Server給Client下發一份非對稱加密的公鑰,client用公鑰加密數據而後發送,這樣就不存在數據泄露和篡改的風險了。

然而,這個世界是很險惡的,會有人把本身假裝成Server,給Client下發他們本身的公鑰,並攔截真實Server下發給Client的真實公鑰。

因爲咱們沒辦法斷定Client拿到的公鑰是真實Server仍是惡意代理髮過來的,因此咱們須要一個可信賴的第三方,來告訴Client拿到的公鑰究竟是不是可信的,這個第三方就是CA機構,Certificate Authority,證書受權中心。

引入了CA機構後,獲取證書流程以下:

在實際Client應用中,例如瀏覽器中,扮演可信角色——CA機構的其實是瀏覽器提早內置好的,一部分瀏覽器廠商認爲可信的CA機構的根證書,下面演示一下如何創辦一家CA機構,併爲一個服務器頒發CA證書。

2.1 創辦CA機構

咱們能夠利用開源的openssl庫來創辦一傢俬人的CA機構,上面的演示Demo目錄結構爲:

├─client
│      client.js
├─proxy
│      proxy.js
└─server
        server.js

新建一個CA目錄,建立一家CA機構,能夠通俗地理解爲:

  1. 生成一份非對稱加密私鑰
  2. 生成一份明文的證書文件,證書中記錄該機構的地址、名稱、email、機構公鑰、有效期等信息
  3. 爲本身的證書籤名:即經過散列函數計算出證書文件的散列值,並經過私鑰對散列值加密。將簽名附加到證書中。

這樣,咱們就獲得了一份稱爲「根證書」的證書文件,瀏覽器若是信任咱們的CA機構,就能夠把咱們的根證書內置到瀏覽器中。

對應的openssl命令爲:

  1. openssl genrsa -out caPrivate.key 2048 建立一個2048位的非對稱加密私鑰
  2. openssl req -new -key caPrivate.key -out ca.csr 經過私鑰建立一個正式簽名請求文件,期間會要求輸入機構名稱、地址、email等信息。
  3. openssl x509 -req -in ca.csr -signkey caPrivate.key -out ca.crt使用x509證書協議爲剛剛建立的證書籤名請求籤名,獲得ca.crt文件,即「根證書」。

至此,咱們成功創辦了一家擁有本身根證書的CA機構,文件列表:
ca.crt
ca.csr
caPrivate.key

證書的細節遠不止這麼簡單,具體的能夠參見CA證書標準X.509,https://www.ietf.org/rfc/rfc5280.txt

2.2 簽發CA證書

爲了安全,咱們升級一下前邊Demo中的Server,建立本身的證書,並請求CA機構簽名頒發CA證書,來進行TLS安全通訊。

  1. 新建目錄 TlsServer
  2. 建立私鑰 openssl genrsa -out private.key 2048
  3. 建立證書籤名請求 openssl req -new -key private.key -out request.csr
  4. openssl x509 -req -CA ../CA/ca.crt -CAkey ../CA/caPrivate.key -CAcreateserial -in request.csr -out server.crt CA機構爲LtsServer的證書籤名,並頒發CA證書文件server.crt

這裏要注意的是,本地測試的時候,Common Name屬性要填寫localhost,若填寫線上應用地址,則使用時客戶端會報錯:

Error: Hostname/IP doesn't match certificate's altnames: "Host: localhost. is not cert's CN: zoucz.com"

2.3 開始安全的TLS通訊

使用上面一步頒發的CA證書來進行TLS通訊,須要三個步驟:

  • 使用CA機構爲Tls Server頒發的CA證書建立TLS Server
  • 將CA機構的根證書內置到TLS Client中
  • 使用CA根證書建立TLS Client

① TLS Server:

const tls = require('tls');
const fs=require('fs');
const serverHost='127.0.0.1';
const serverPort=8888;
const options = {
    key: fs.readFileSync('private.key'),
    cert: fs.readFileSync('server.crt'),
};
var tlsServer = tls.createServer(options,(clientSocket) => {
    clientSocket.setEncoding('utf8');
    clientSocket.on('data',(data)=>{
        console.log(`client say:${data}`);
    });
    clientSocket.on('error',(e)=>{console.log(e)});
});

tlsServer.listen({host:serverHost,port:serverPort},()=>{
    console.log(`lts server is listening on port ${8888}`)
});

② 將CA機構根證書內置到Client中:

③ 建立 TLS Client

const tls = require('tls');
const fs = require('fs');

const serverHost='127.0.0.1';
const serverPort=8888;
const options = {
    ca: [ fs.readFileSync('ca.crt') ]
};
let index=0;
var tlsSocket = tls.connect(serverPort, options, () => {
    console.log(`tls client has connected to host ${serverHost} , port ${serverPort}`);
    setInterval(()=>{
        tlsSocket.write(`i love u ${index++}`);
    },3000);
});
tlsSocket.on('error',(e)=>{console.log(e)});

再將服務端部署到另一臺機器上,抓包:

如今看到的內容就是亂碼了,沒有內容泄露的風險。同理,在數據傳輸的過程當中,第三方也沒法篡改咱們的數據了。

將本身的測試TLS服務部署到另一臺機器上時,有個要注意的地方,TlsClient的option中須要修改以下:

const options = {
    ca: [ fs.readFileSync('ca.crt') ],
    checkServerIdentity: function (host, cert) {
        return undefined;
    }
};

這是由於TLS通訊時,對於服務端身份的檢查,使用域名和使用IP的狀況下,驗證的策略不一樣,當咱們在本地測試,使用IP時,須要將IP加入證書的SAN擴展(Subject Alternative Name)中,關於此擴展的內容,能夠到https://www.ietf.org/rfc/rfc5280.txt查詢,我沒有深刻研究。

3 基於TLS的HTTPS協議

前邊1.1小節中說道,http協議是基於tcp傳輸協議的不安全協議,那麼https協議爲何被認爲是安全的協議呢? 答案就是,它是基於tls傳輸協議的應用層協議。

3.1 建立https服務

有了前邊對LTS通訊原理的瞭解,再來看https就很是簡單了,咱們能夠直接複用剛剛爲TLS Server頒發的CA證書,來建立一個https服務器。

var https = require('https');
var fs = require('fs');
var options = {
    key: fs.readFileSync('./private.key'),
    cert: fs.readFileSync('./server.crt')
};
https.createServer(options, function(req, res) {
    res.writeHead(200);
    res.end('hello https');
}).listen(8866);


chrome會這樣提示你,咱們的瀏覽器裏邊找不到爲這個服務器CA證書籤名的CA證書,這極可能是一個騙子網站,這是由於咱們的CA機構根證書沒有被內置到chrome裏邊。點繼續訪問:

查看證書:

3.2 讓本身的CA機構被chrome信任

能夠將咱們的CA機構根證書導入chrome,在chrome設置中:

重啓chrome,再次訪問咱們的https服務

看,變成小綠鎖了~

最後,本文全部Demo代碼存放於:https://github.com/zouchengzhuo/nodejsLearn/tree/master/caAndTLS

原文來自個人我的站點:http://zoucz.com/blog/2017/01/05/understand-crypto-3/

相關文章
相關標籤/搜索