https 原理與實(shí)踐
經(jīng)典三問(wèn),是什么,為什么,怎么做?
是什么
是一種http的安全協(xié)議,在tcp ip網(wǎng)絡(luò)模型里,http應(yīng)用層是在tcp 傳輸層之上的,https協(xié)議規(guī)定了在tcp傳輸層之上還有一層tls/ssl層,這一層對(duì)http應(yīng)用層發(fā)出去和接收的報(bào)文做加密和解密。
為什么
出現(xiàn)https原因,在我看來(lái)有兩點(diǎn)
1,因?yàn)閔ttp是明文傳輸,極不安全,需要對(duì)報(bào)文進(jìn)行加密。
2,我們無(wú)法確認(rèn)瀏覽的網(wǎng)站的身份信息,如果是釣魚(yú)網(wǎng)站,誘使我們輸入銀行賬號(hào)密碼之類(lèi)的就麻煩了。
怎么做
簡(jiǎn)而言之,「https規(guī)定了 加密算法對(duì)報(bào)文進(jìn)行加密,解決明文傳輸?shù)膯?wèn)題。采用數(shù)字證書(shū)的方式解決對(duì)服務(wù)端的身份認(rèn)證問(wèn)題」。
加密報(bào)文方式
對(duì)稱(chēng)加密和非對(duì)稱(chēng)加密
對(duì)稱(chēng)加密是加密和解密都采用同一個(gè)密鑰。 非對(duì)稱(chēng)加密 將密鑰分為公鑰和私鑰, 公鑰進(jìn)行加密的報(bào)文,用私鑰可以解密。私鑰加密的報(bào)文,用公鑰可以解密(這種加密方式也是數(shù)字證書(shū)采用的原理)
一般對(duì)稱(chēng)加密會(huì)比非對(duì)稱(chēng)加密性能高幾個(gè)數(shù)量級(jí)。但是就安全性而言,非對(duì)稱(chēng)加密安全性會(huì)更高,那么https采用的是什么加密方式呢?
兩者結(jié)合的加密方式
由于對(duì)稱(chēng)加密性能更好,所以https在真正加密報(bào)文時(shí)還是采用的對(duì)稱(chēng)加密方式,但是對(duì)稱(chēng)加密的密鑰是通過(guò)非對(duì)稱(chēng)加密協(xié)商后傳給對(duì)方的。這樣的加密方式就能保證在性能更好的前提下也有不錯(cuò)的安全性。
數(shù)字證書(shū)原理以及應(yīng)用
數(shù)字證書(shū)是什么
數(shù)字證書(shū) 是一個(gè)可信組織驗(yàn)證和簽發(fā)的識(shí)別信息。有點(diǎn)類(lèi)似于現(xiàn)實(shí)生活中的身份證,公安機(jī)關(guān)給我們頒發(fā)身份證為的就是證明個(gè)人身份,而在網(wǎng)絡(luò)世界里,這個(gè)組織就是ca組織。
來(lái)看看向ca組織提交注冊(cè)信息以及驗(yàn)證數(shù)字證書(shū)的過(guò)程。

go語(yǔ)言精進(jìn)之路截圖
server.key是網(wǎng)站擁有的自己的私鑰,ca.key是ca的私鑰,server.crt是網(wǎng)站的數(shù)字證書(shū)。
1,首先網(wǎng)站服務(wù)將自身的一些基本信息通過(guò)自身的私鑰進(jìn)行加密,然后將自身的公鑰和基本信息密文通過(guò)證書(shū)簽名請(qǐng)求的格式傳遞給ca。 2,ca得到請(qǐng)求后,利用網(wǎng)站服務(wù)的公鑰,對(duì)其基本信息進(jìn)行解密。然后再按x509數(shù)字證書(shū)規(guī)范生成公鑰證書(shū)。
這里涉及到ca對(duì)證書(shū)信息的加密方式,前面提到用私鑰加密的數(shù)據(jù),可用公鑰解密,當(dāng)將證書(shū)信息通過(guò)私鑰加密后,客戶(hù)端就能通過(guò)ca的公鑰去解密證書(shū),能成功解密,說(shuō)明證書(shū)的確是ca頒發(fā)的。
但實(shí)際加密卻不是這樣,因?yàn)榉菍?duì)稱(chēng)加密算法 加密的信息越多性能越差,所以是將證書(shū)信息通過(guò)摘要算法生成固定長(zhǎng)度的字符后,再采用ca私鑰對(duì)其摘要進(jìn)行加密。 摘要算法(常見(jiàn)的如MD5,sha)確保了數(shù)據(jù)的完整性,因?yàn)槎啻蜗嗤瑑?nèi)容的數(shù)據(jù)用相同摘要算法計(jì)算的結(jié)果一致,所以能基本保證數(shù)據(jù)未被篡改。
3,x509數(shù)字證書(shū)里面包含 以下信息: 服務(wù)端公鑰(server.pub); 證書(shū)相關(guān)屬性信息,如域名、有效期等; 證書(shū)頒發(fā)機(jī)構(gòu)的簽名信息 4,然后就是客戶(hù)端的解密過(guò)程,拿到證書(shū)以后,通過(guò)ca公鑰對(duì)證書(shū)以及公鑰信息的摘要密文進(jìn)行解密,得到消息摘要,然后用摘要算法對(duì)證書(shū)信息以及公鑰信息進(jìn)行摘要計(jì)算,將客戶(hù)端得到的摘要和密文解密后的摘要進(jìn)行對(duì)比,如果相同,說(shuō)明內(nèi)容未被篡改,證書(shū)有效。
數(shù)字證書(shū)的加密算法總結(jié)
通過(guò)私鑰加密,公鑰解密的方式提供證書(shū)的頒發(fā)證明,能成功解密說(shuō)明證書(shū)的確是ca頒發(fā)的。 通過(guò)摘要算法,確保了證書(shū)的完整性,未被篡改的證明。
https握手過(guò)程簡(jiǎn)析
建立在https要解決什么問(wèn)題的基礎(chǔ)上,理解https的握手過(guò)程,看看它究竟做了什么設(shè)計(jì),讓握手過(guò)程更高效,更安全。
https是為了解決明文傳輸?shù)牟话踩?,以及?duì)端身份認(rèn)證的問(wèn)題。
1,客戶(hù)端發(fā)送client hello信息將自身支持的tls版本,密碼套件的信息傳給服務(wù)端。
2,服務(wù)端接收后會(huì)發(fā)送server hello信息 選擇tls版本和加密算法發(fā)給客戶(hù)端。
3,然后服務(wù)端發(fā)送server certificate 信息將自身的證書(shū)下發(fā)給客戶(hù)端。
4,然后服務(wù)端接著又發(fā)送密鑰協(xié)商請(qǐng)求 server key exchange, 在密鑰協(xié)商環(huán)節(jié),通常會(huì)使用到Diffie-Hellman(DH)密鑰交換算法,這是一種密鑰協(xié)商的協(xié)議,支持通信雙方在不安全的通道上生成對(duì)稱(chēng)加密算法所需的共享密鑰。注意這種交換算法使用的目的是既讓通信雙方都擁有一樣的密鑰,但是呢,密鑰又不是通過(guò)數(shù)據(jù)傳輸?shù)綄?duì)面的,是協(xié)商產(chǎn)生的。
5 接著客戶(hù)端收到證書(shū)后,先檢驗(yàn)證書(shū)的有效性, 然后客戶(hù)端生成接下來(lái)加密通信的密鑰,同時(shí)將生成密鑰的一些參數(shù) 用服務(wù)端的公鑰加密后 發(fā)送給 服務(wù)端,這個(gè)階段稱(chēng)為client key exchange階段,服務(wù)端收到后按這些參數(shù)后用自身的私鑰解密 然后也生成相同密鑰。
6,最后客戶(hù)端和服務(wù)端分別會(huì)將連接至今的所有報(bào)文進(jìn)行加密發(fā)送給對(duì)方,以驗(yàn)證tls握手成功。
golang聲明https服務(wù)器
懂了交互邏輯以后,就知道了,聲明一個(gè)https服務(wù)的時(shí)候,得有一個(gè)ca頒發(fā)的證書(shū),證書(shū)里面包含服務(wù)端自身的公鑰信息和一些基本信息,然后還得指明明服務(wù)器的私鑰,用于tls握手過(guò)程中對(duì)密鑰協(xié)商參數(shù)的加解密。
package main
import (
// "fmt"
// "io"
"net/http"
"log"
)
func HelloServer(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.Write([]byte("This is an example server.\n"))
// fmt.Fprintf(w, "This is an example server.\n")
// io.WriteString(w, "This is an example server.\n")
}
func main() {
http.HandleFunc("/hello", HelloServer)
err := http.ListenAndServeTLS(":443", "server.crt", "server.key", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
基于上述https原理,下面我將會(huì)用openssl 工具以及golang程序來(lái)演示下https加解密過(guò)程。
生成服務(wù)器私鑰
openssl genrsa -out server.key 2048
生成x509證書(shū)
openssl req -new -x509 -key server.key -out server.crt -days 3650
server.crt 就是我們的自簽名證書(shū)了,然后啟動(dòng)go https服務(wù)
啟動(dòng)go https服務(wù)
package main
import (
"log"
// "fmt"
// "io"
"net/http"
)
func HelloServer(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.Write([]byte("This is an example server.\n"))
// fmt.Fprintf(w, "This is an example server.\n")
// io.WriteString(w, "This is an example server.\n")
}
func main() {
http.HandleFunc("/hello", HelloServer)
err := http.ListenAndServeTLS(":443", "server.crt", "server.key", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
先測(cè)試下訪問(wèn)
(base) ? ~ curl https://proxy.example.com:443/hello
curl: (60) SSL certificate problem: self signed certificate
More details here: https://curl.se/docs/sslcerts.html
curl 提示是自簽名證書(shū),因?yàn)槲覀兪褂玫膐penssl 按x509標(biāo)準(zhǔn)生成的證書(shū),不是ca頒發(fā)的,所以curl有這個(gè)錯(cuò)誤。
讓系統(tǒng)信任該證書(shū)
不同操作系統(tǒng)添加信任的方式不同,我使用的是mac os,在鑰匙串應(yīng)用里將證書(shū)添加進(jìn)來(lái)并設(shè)置為信任。
注意這里我設(shè)置了證書(shū)的域名是proxy.example.com ,所以我還需要設(shè)置下這個(gè)域名對(duì)應(yīng)的ip
設(shè)置域名
vim /etc/hosts
255.255.255.255 broadcasthost
127.0.0.1 localhost
::1 localhost
## 添加上這行
127.0.0.1 proxy.example.com
再次訪問(wèn)
(base) ? ~ curl https://proxy.example.com:443/hello
This is an example server.
發(fā)現(xiàn)當(dāng)前訪問(wèn)已經(jīng)成功了。
完美收工。。。
參考文獻(xiàn): go 語(yǔ)言精進(jìn)之路