方法在最后一小節(jié)佛南,前面都是用到的知識的總結(jié)梗掰,了解的可以跳過。
1. 對稱加密和非對稱加密
對稱加密:加密和解密都用同一個密碼
非對稱加密:公鑰對所有人公開嗅回,發(fā)送者加密用公鑰及穗,接收者唯一掌握私鑰,對公鑰加密內(nèi)容的解密用且只能用私鑰
非對稱加密缺點:加密速度比對稱慢
非對稱加密優(yōu)點:公鑰本身就是公開給別人的妈拌,所以不用擔(dān)心被竊取拥坛,私鑰永遠在自己手里,只有自己能解密消息
2. https和http
??https在http的基礎(chǔ)上尘分,對傳輸內(nèi)容進行了加密猜惋。
??我們都知道http建立連接的三次握手和四次揮手過程。如果使用的是https培愁,在三次握手過后著摔,需要進行SSL握手,然后客戶端和服務(wù)端通信過程中使用SSL(新版本叫TLS了)的互相加解密定续。通信完之后谍咆,在四次揮手之前禾锤,先關(guān)閉SSL連接。
??從這里能看出摹察,https比http要慢(多了SSL握手恩掷、揮手,就多了好幾次TCP通信供嚎,建立連接之后每次通信還要加解密黄娘,http/1.1開始支持長連接,以及http/2支持多路復(fù)用克滴,都可以減少建立連接的次數(shù)逼争,也就減少了多次http請求下每次http請求的平均耗時)。
??https相比http還可以防篡改劝赔。接收到消息之后計算出數(shù)字簽名誓焦,對比收到的數(shù)字簽名,就知道消息體是否被篡改了着帽。(想了解數(shù)字簽名可以參考阿里云視頻監(jiān)控產(chǎn)品OpenAPI的簽名機制)
3. https單向認證和雙向認證
??從上面對比可以看到杂伟,https使用的非對稱加密是用在客戶端和服務(wù)端交互SSL信息的過程中,SSL握手完成后启摄,客戶端和服務(wù)端通信使用的還是對稱加密稿壁,對稱加密的密鑰在本次連接隨機生成幽钢,其他的連接以及本次連接關(guān)閉后都不能使用歉备。
??雙向認證和單向認證的區(qū)別就在于,客戶端需要向服務(wù)端發(fā)送自己的證書匪燕,服務(wù)端需要校驗客戶端的證書蕾羊,以及服務(wù)端選擇加密方案通知客戶端時也是加密的。
4. CA證書和自制證書
??CA(Certificate Authority)是負責(zé)管理和簽發(fā)證書的第三方權(quán)威機構(gòu)帽驯,常見的有Symantec龟再、GeoTrust、Comodo等等尼变,他們是所有行業(yè)和公眾都信任的利凑、認可的,并負責(zé)審核向他們申請證書的網(wǎng)站的安全性嫌术。
??CA證書哀澈,就是CA頒發(fā)的證書,可用于驗證網(wǎng)站是否可信(針對HTTPS)度气、驗證某文件是否可信(是否被篡改)等割按,也可以用一個證書來證明另一個證書是真實可信,最頂級的證書稱為根證書磷籍。除了根證書(自己證明自己是可靠)适荣,其它證書都要依靠上一級的證書现柠,來證明自己。
??我們用的操作系統(tǒng)都預(yù)置了很多可信任的根證書弛矛,SSL握手時服務(wù)器會把它的服務(wù)器證書發(fā)給瀏覽器够吩。例如CSDN的服務(wù)器證書是GeoTrust頒發(fā)的,本地的GeoTrust根證書可以證明CSDN的服務(wù)器證書是真的丈氓,值得信任废恋,于是我們訪問CSDN時瀏覽器就建立了和CSDN服務(wù)器的https連接。
??我們也可以自己做CA根證書扒寄,我們自己的機器或者訪問我們服務(wù)的客戶的機器鱼鼓,都安裝上該CA根證書。12306以前就是這樣干的(SRCA就是12306的根證書该编,現(xiàn)在已經(jīng)換成DigiCert的證書了)迄本。
??關(guān)于CA證書更多的解釋,以及各種證書文件的區(qū)別课竣,參考這里嘉赎。
??自制CA證書可以用OpenSSL命令行工具,linux基本都自帶于樟,也可以使用GUI工具公条,參考這里。
5. 利用chrome和nginx實踐一下單向認證和雙向認證
??先假定我們已經(jīng)做好了證書文件迂曲,包括根證書ca.pem靶橱、服務(wù)端證書server.pem、服務(wù)端私鑰server.key路捧、客戶端證書client.pem关霸、客戶端私鑰client.key,可以來這里下載我做好的杰扫,可以用于你自己測試队寇,提取碼:yd11。
??nginx加上配置文件:
server {
listen 443 ssl; # ssl代表該端口監(jiān)聽啟用https
server_name localhost;
ssl_certificate /data/sslKey/server.pem; # 證書
ssl_certificate_key /data/sslKey/server.key; # 私鑰
ssl_client_certificate /data/sslKey/ca.pem; # 根證書章姓,用于驗證各個下級證書
ssl_verify_client on; # 校驗客戶端證書開啟
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
其中:
ssl_certificate /data/sslKey/server.pem; # server證書公鑰
ssl_certificate_key /data/sslKey/server.key; # server私鑰
很常見佳遣。
??通常我們需要讓自己的網(wǎng)站變成https訪問時都是這么做。使用nginx對外暴露https請求接口凡伊,nginx到后端的內(nèi)網(wǎng)服務(wù)仍然是http零渐,改動小,效率高窗声。
??只有這兩項配置就是單向認證相恃,即客戶端需要校驗服務(wù)端的證書來確信服務(wù)端是安全網(wǎng)站。這時候訪問https://localhost:443
會提示不安全的網(wǎng)站(以下所有截圖都是chrome瀏覽器的交互,其他瀏覽器可能不是這樣拦耐,自行處理)耕腾,因為服務(wù)端提供的證書是我們自制的,操作系統(tǒng)預(yù)存的證書無法識別該證書杀糯。
??點擊繼續(xù)訪問代表我們手動告訴瀏覽器信任這個網(wǎng)站扫俺,就可以繼續(xù)訪問了。
再加上:
ssl_client_certificate /data/sslKey/ca.pem; # 根證書固翰,用于驗證各個下級證書
ssl_verify_client on; # 校驗客戶端證書開啟
這兩項配置就是雙向認證狼纬。
??服務(wù)端需要驗證客戶端的證書,我們直接訪問就會得到報錯400 No Required SSL certificate was sent
骂际,需要在chrome中導(dǎo)入自制的client.pem證書疗琉,導(dǎo)入方法參考這里。
??需要注意的是windows導(dǎo)入證書的格式不是pem和crt歉铝,需要轉(zhuǎn)換一下才能導(dǎo)入盈简,OpenSSL命令行工具可以轉(zhuǎn)換,上面的GUI工具也可以在導(dǎo)出時選擇指定格式太示。
??再次訪問chrome會彈窗提示柠贤,
??選擇剛才導(dǎo)入的證書就能正常訪問了。
6. 利用自制CA證書雙向認證實現(xiàn)安全訪問
??以go代碼示例类缤,仍然使用上面的證書文件臼勉,加載證書文件的代碼:
import (
"crypto/tls"
"crypto/x509"
"io/ioutil"
"log"
"google.golang.org/grpc/credentials"
)
// GetServerCredentials 服務(wù)端證書
func GetServerCredentials() credentials.TransportCredentials {
cert, err := tls.LoadX509KeyPair("cert/server.pem", "cert/server.key")
if err != nil {
log.Fatalf("加載服務(wù)端證書失敗, err: %v\n", err)
}
certPool := x509.NewCertPool()
ca, err := ioutil.ReadFile("cert/ca.pem")
if err != nil {
log.Fatalf("讀取公鑰文件失敗: %v\n", err)
}
certPool.AppendCertsFromPEM(ca)
creds := credentials.NewTLS(&tls.Config{
Certificates: []tls.Certificate{cert},
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: certPool,
})
return creds
}
// GetClientCredentials 客戶端證書
func GetClientCredentials() credentials.TransportCredentials {
cert, err := tls.LoadX509KeyPair("cert/client.pem", "cert/client.key")
if err != nil {
log.Fatalf("加載客戶端證書失敗, err: %v\n", err)
}
certPool := x509.NewCertPool()
ca, err := ioutil.ReadFile("cert/ca.pem")
if err != nil {
log.Fatalf("讀取公鑰文件失敗: %v\n", err)
}
certPool.AppendCertsFromPEM(ca)
creds := credentials.NewTLS(&tls.Config{
Certificates: []tls.Certificate{cert},
ServerName: "localhost",
RootCAs: certPool,
})
return creds
}
??服務(wù)端加上:
opts := []grpc.ServerOption{
grpc.Creds(utils.GetServerCredentials()),
}
??客戶端加上:
grpc.Dial(
"localhost:8080",
grpc.WithTransportCredentials(cert.GetClientCredentials()),
??如果服務(wù)端使用了證書,客戶端沒有使用證書餐弱,在grpc.Dial()
時連接可以建立成功宴霸,但是訪問時會報錯:
rpc error: code = Unavailable desc = connection closed
并且可以一直訪問一直報錯。
??這里涉及到gRPC連接機制的問題岸裙。調(diào)用Dial或者DialContext函數(shù)創(chuàng)建連接時猖败,默認只是返回ClientConn結(jié)構(gòu)體指針,同時會啟動一個Goroutine異步的去建立連接降允,連接失敗會一直重試,這個機制可以避免服務(wù)器因為連接空閑時間過長關(guān)閉連接艺糜、服務(wù)器重啟等造成的客戶端連接失效問題剧董,可以完美的解決連接的超時與保活問題破停。
??如果想要等連接建立完再返回(起到創(chuàng)建連接時就檢測連接是否可用的目的)翅楼,可以指定grpc.WithBlock()。連接不上就會報錯:
context deadline exceeded
??如果服務(wù)端沒有使用證書真慢,客戶端使用了毅臊,也是可以成功建立連接,但是訪問時報錯:
connection error: desc = "transport: authentication handshake failed: tls: first record does not look like a TLS handshake"
??因為客戶端收不到服務(wù)端的TLS握手信息(服務(wù)端不使用證書黑界,根本就不知道要TLS握手)管嬉。