接理解加密演算法(一)——加密演算法分類、理解加密演算法(二)——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:
抓包:
可以看到,表白資訊全被別人看了去了 :(
可能有人會說:我臉皮厚,隨便看~
但是要注意了,所有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機構,可以通俗地理解為:
- 生成一份非對稱加密私鑰
- 生成一份明文的證照檔案,證照中記錄該機構的地址、名稱、email、機構公鑰、有效期等資訊
- 為自己的證照籤名:即通過雜湊函式計算出證照檔案的雜湊值,並通過私鑰對雜湊值加密。將簽名附加到證照中。
這樣,我們就得到了一份稱為“根證照”的證照檔案,瀏覽器如果信任我們的CA機構,就可以把我們的根證照內建到瀏覽器中。
對應的openssl命令為:
openssl genrsa -out caPrivate.key 2048
建立一個2048位的非對稱加密私鑰openssl req -new -key caPrivate.key -out ca.csr
通過私鑰建立一個正式簽名請求檔案,期間會要求輸入機構名稱、地址、email等資訊。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安全通訊。
- 新建目錄 TlsServer
- 建立私鑰
openssl genrsa -out private.key 2048
- 建立證照籤名請求
openssl req -new -key private.key -out request.csr
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/