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

桃子夭夭發表於2017-01-05

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

1 不安全的TCP通訊

普通的TCP通訊資料是明文傳輸的,所以存在資料洩露和被篡改的風險,我們可以寫一段測試程式碼試驗一下。

TCP Server:

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:

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的訊息:

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:
理解加密演算法(三)——建立CA機構,簽發證書並開始TLS通訊
抓包:
理解加密演算法(三)——建立CA機構,簽發證書並開始TLS通訊
可以看到,表白資訊全被別人看了去了 :(

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

但是要注意了,所有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的真實公鑰。
理解加密演算法(三)——建立CA機構,簽發證書並開始TLS通訊

由於我們沒辦法判定Client拿到的公鑰是真實Server還是惡意代理髮過來的,所以我們需要一個可信賴的第三方,來告訴Client拿到的公鑰到底是不是可信的,這個第三方就是CA機構,Certificate Authority,證照授權中心。

引入了CA機構後,獲取證照流程如下:
理解加密演算法(三)——建立CA機構,簽發證書並開始TLS通訊
在實際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中:
理解加密演算法(三)——建立CA機構,簽發證書並開始TLS通訊
③ 建立 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)});

再將服務端部署到另外一臺機器上,抓包:
理解加密演算法(三)——建立CA機構,簽發證書並開始TLS通訊
現在看到的內容就是亂碼了,沒有內容洩露的風險。同理,在資料傳輸的過程中,第三方也無法篡改我們的資料了。

將自己的測試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);

理解加密演算法(三)——建立CA機構,簽發證書並開始TLS通訊
chrome會這樣提示你,我們的瀏覽器裡邊找不到為這個伺服器CA證照籤名的CA證照,這很可能是一個騙子網站,這是因為我們的CA機構根證照沒有被內建到chrome裡邊。點繼續訪問:
理解加密演算法(三)——建立CA機構,簽發證書並開始TLS通訊
檢視證照:
理解加密演算法(三)——建立CA機構,簽發證書並開始TLS通訊

3.2 讓自己的CA機構被chrome信任

可以將我們的CA機構根證照匯入chrome,在chrome設定中:
理解加密演算法(三)——建立CA機構,簽發證書並開始TLS通訊
重啟chrome,再次訪問我們的https服務
理解加密演算法(三)——建立CA機構,簽發證書並開始TLS通訊
看,變成小綠鎖了~

最後,本文所有Demo程式碼存放於:https://github.com/zouchengzhuo/nodejsLearn/tree/master/caAndTLS

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

相關文章