首頁>技術>

在 WebRTC 中,為了保證媒體傳輸的安全性,引入了 DTLS 來對通訊過程進行加密。DTLS 的作用、原理與 SSL/TLS 類似,都是為了使得原本不安全的通訊過程變得安全。它們的區別點是 DTLS 適用於加密 UDP 通訊過程,SSL/TLS 適用於加密 TCP 通訊過程,正是由於使用的傳輸層協議不同,造成了它們實現上面的一些差異。

基本概念

對稱金鑰加密技術

對稱金鑰加密的含義是加密過程和解密過程使用的是同一個金鑰。常見的對稱加密演算法有 DES、3DES、AES、Blowfish、IDEA、RC5、RC6。

非對稱加密金鑰加密技術

非對稱金鑰加密的含義是加密過程和解密過程使用了不同的金鑰,分別稱為公開金鑰和私有金鑰。公鑰是眾所周知的,但是金鑰只能為報文的目標主機所持有。相比對稱加密技術,它的優點是不用擔心金鑰在通訊過程中被他人竊取;缺點是需要解碼速度慢,消耗更多的 CPU 資源。常見的非對稱加密演算法有 RSA、DSA、DH 等。

數字簽名

數字簽名是附加在報文上的特殊加密校驗碼,即所謂的校驗和,其中利用了非對稱加密金鑰加密技術。數字簽名的主要作用是防止報文被篡改,一旦報文被攻擊者篡改,透過將其與校驗和進行匹配,可以立刻被接收者發現。數字簽名的過程如下圖所示:

傳送者 A 將報文摘要(報文透過 SHA-1 等雜湊演算法生成摘要)透過私有金鑰加密生成簽名,與明文報文一起發給接收者 B,接收者 B 可以透過對收到的資訊進行計算後得到兩份報文摘要,比較這兩份報文摘要是否相等可以驗證報文是否被篡改:

明文報文透過使用與傳送端相同的雜湊演算法生成摘要 1;簽名透過公開金鑰解密後生成摘要 2。

數字證書

數字證書是由一些公認可信的證書頒發機構簽發的,不易偽造。包括如下內容:

證書序列號證書籤名演算法證書頒發者有效期公開金鑰證書籤發機構的數字簽名

數字證書可以用於接收者驗證對端的身份。接收者(例如瀏覽器)收到某個對端(例如 Web 伺服器)的證書時,會對簽名頒發機構的數字簽名進行檢查,一般來說,接收者事先就會預先安裝很多常用的簽名頒發機構的證書(含有公開金鑰),利用預先的公開金鑰可以對簽名進行驗證。

SSL/TLS 協議

要了解 DTLS,首先從我們比較熟悉的 SSL/TLS 開始講起。SSL(Secure Socket Layer) 和 TLS(Transport Layer Security) 簡單理解就是同一件東西的兩個演進階段,同樣都是在應用層和傳輸層之間加入的安全層,最早的時候這個安全層叫做 SSL,由 Netscape 公司推出,後來被 IETF 組織標準化並稱之為 TLS。SSL/TLS 的作用是為了解決網際網路通訊中存在的三種風險:

竊聽風險:第三方可以獲知通訊內容;篡改風險:第三方可以修改通訊內容;冒充風險:第三方可以冒充他人身份參與通訊。

SSL/TLS 協議能夠做到以下這幾點,從而解決上述的三種風險:

所有資訊透過加密傳播,第三方無法竊聽;具有資料簽名及校驗機制,一旦被篡改,通訊雙方立刻可以發現;具有身份證書,防止其他人冒充。

協議棧

SSL/TLS 建立在 TCP 傳輸層上,最常使用 SSL/TLS 的場景是在 HTTPS 中,透過在 HTTP 和 TCP 中加了一層 SSL/TLS,使得不安全的 HTTP 成為了安全的 HTTPS。協議棧如下圖所示:

SSL/TLS 對於 TCP 傳輸層的加密是透過動態金鑰對資料進行加密實現的,而動態金鑰透過握手流程協商制定。因此在 SSL/TLS 握手過程中需要協商的資訊包括:

協議版本號;加密演算法,包括非對稱加密演算法、動態金鑰演算法;數字證書,傳輸雙方透過交換證書及簽名校驗來驗證對方身份;動態金鑰,由於非對稱加密對效能消耗較大,因此主要的通訊過程都是使用動態金鑰進行對稱加密的;對稱加密使用的動態金鑰則在握手過程中透過非對稱加密來傳輸。

握手過程

以 TLS 1.2 為例:

握手過程如上圖所示,大體來說分成三個過程:明文通訊過程、非對稱加密通訊過程、對稱加密通訊過程;

明文通訊過程:在通訊兩端首次向對方傳送 Hello 訊息時,由於雙方都沒有協商好要使用哪種加密方式,因此這個過程中的訊息都是使用明文進行傳送的。 a. Client Hello:客戶端首先向服務端發起握手,在握手訊息中告訴對方自己支援的 SSL/TLS 版本、加密套件(包括非對稱加密時使用的演算法與、非對稱加密時使用的演算法、產生金鑰的偽隨機函式 PRF)與資料壓縮演算法(TLS1.3之後就已經沒有這個欄位)等;還會攜帶一個 Session ID,因為握手流程的開銷比較大,使用 Session ID 可以在下一次與 TLS 握手的過程跳過後續繁瑣的握手流程,重用之前的握手結果(如版本號、加密演算法套件、master-key 等);併產生一個隨機數 A,也告訴給對方; b. Server Hello:服務端響應一個 Server Hello 訊息,攜帶協商出來的 TLS/SSL 版本號、加密套件和資料壓縮演算法,如果服務端同意客戶端重用上次的會話,就返回一個相同的 Session ID,否則就填入一個全新的 Session ID; c. Server Certificate(可選):攜帶服務端數字證書(CA)以驗證服務端身份,裡面攜帶了服務端非對稱加密所使用的公鑰;這步雖然是可選的,但是一般來說客戶端都會要求驗證服務端的身份,在大多數情況下這步都會執行; d. Server Key Exchange(可選):在使用某些非對稱加密演算法(例如 DH 演算法)的情況下,Server Certificate 裡的資訊是不足夠的,或者 Server Certificate 在某些通訊過程中直接被省略了(沒有驗證服務端身份),需要 Server Key Exchange 裡的額外資訊來幫助客戶端生成 pre-master key; e. Client Sertificate Request(可選):在有些安全性要求高的場景,例如銀行支付等,不僅需要驗證服務端的身份,還需要驗證客戶端的身份,這時候服務端就會要求客戶端提供客戶端的身份證書; f. Server Hello Done:表明 Server Hello 結束; g. Client Certificate(可選):如果服務端要求客戶端提供數字證書以驗證身份,則客戶端傳送自己的身份證書給服務端;非對稱加密通訊過程:由於非對稱加密通訊的效能較差,在實際的通訊過程中其實使用的是對稱加密通訊,為了保證對稱加密通訊過程的安全性,也就是需要避免對稱加密金鑰被竊取,這個金鑰在協商過程中使用非對稱加密來進行加密。 a. Client Key Exchange:客戶端在驗證服務端的身份證書後,會取出其中的服務端公鑰,產生一個隨機數 C,作為 pre-master key,在本地使用之前的隨機數 A、B 和這次生成的 C 共同生成對稱加密金鑰 master-key;使用服務端公鑰對 pre-master key 加密後傳送給服務端; b. Certificate Verify(可選):如果服務端要求客戶端提供客戶端證書,那麼客戶端在傳送 Client Key Exchange 之後必須馬上傳送 Certificate Verify,其中的內容是客戶端使用自己的私鑰加密的一段資料,提供給服務端用客戶端的公鑰來進行解密驗證。之所以需要這一步是為了確保客戶端傳送的證書確實是它自己的證書; c. Client Change Cipher Spec:提示服務端隨後使用 master key 來進行對稱加密通訊; d. Client Handshake Finished: 表明客戶端側 SSL/TLS 握手結束; e. Server Change Cipher Spec:提示客戶端隨後使用 master key 來進行對稱加密通訊; f. Server Handshake Finished:表明服務端側 SSL/TLS 握手結束;對稱加密通訊過程:透過上述握手過程協商出對稱加密演算法及使用的對稱加密金鑰之後,隨後的通訊過程,也就是實際的應用通訊過程,都使用的是對稱加密。

握手訊息格式

SSL/TLS 的握手訊息格式如下所示,訊息型別已經在上文握手過程中分別進行了解釋;結構體中包含 訊息型別、訊息長度、訊息體。

enum {       hello_request(0), client_hello(1), server_hello(2),       certificate(11), server_key_exchange (12),       certificate_request(13), server_hello_done(14),       certificate_verify(15), client_key_exchange(16),       finished(20)       (255)   } HandshakeType;   struct {       HandshakeType msg_type;       uint24 length;       select (HandshakeType) {           case hello_request:       HelloRequest;           case client_hello:        ClientHello;           case server_hello:        ServerHello;           case certificate:         Certificate;           case server_key_exchange: ServerKeyExchange;           case certificate_request: CertificateRequest;           case server_hello_done:   ServerHelloDone;           case certificate_verify:  CertificateVerify;           case client_key_exchange: ClientKeyExchange;           case finished:            Finished;       } body;   } Handshake;

每種訊息都會有自己獨立的訊息體,訊息體中的內容就是握手過程中提到的訊息中需要攜帶的一些資訊,以 ClientHello 為例,其中需要包含 SSL/TLS 版本號、隨機數、Session ID、可選的加密套件、可選的壓縮演算法:

struct {           ProtocolVersion client_version;           Random random;           SessionID session_id;           CipherSuite cipher_suites<2..2^16-1>;           CompressionMethod compression_methods<1..2^8-1>;       } ClientHello;
DTLS 協議

DTLS 的全稱為 Datagram Transport Layer Security,從名字上就可以看出它和 TLS 的區別就在於多了一個“Datagram”,因為我們把使用 UDP 傳輸的報文叫做 “Datagram”,也就是 DTLS 是適用於 UDP 傳輸過程的加密協議。 DTLS 在設計上儘可能複用 TLS 現有的程式碼,並做一些小的修改來適配 UDP 傳輸。DTLS 與 TLS 具備了同樣的安全機制和防護等級,同樣能夠防止訊息竊聽、篡改,以及身份冒充等問題。在版本上,DTLS 和 TLS 也有一定的對應關係,如下:

DTLS 1.0 對應 TLS 1.1DTLS 1.2 對應 TLS 1.2DTLS 1.3 對應 TLS 1.3

沒有 DTLS 1.1 應當是為了和 TLS 版本號相一致。

協議棧

在 WebRTC 中,透過引入 DTLS 對 RTP 進行加密,使得媒體通訊變得安全。透過 DTLS 協商出加密金鑰之後,RTP 也需要升級為 SRTP,透過金鑰加密後進行通訊。協議棧如下圖所示:

握手過程

TLS 1.2 及之前都沒有嘗試解決 DoS 攻擊的問題,直到 TLS 1.3 才透過加入了 HelloRetryRequest 和 Cookie 來解決 DoS 攻擊的問題(並且在 TLS 1.3 的 RFC 中提到主要用於非面向連線的通道,也就是 UDP 連線)。相對 TCP 來說,UDP 連線對 DoS 攻擊更加敏感,因此 DTLS 在 1.0 版本就加入了 HelloVerifyRequest 和 Cookie,用於服務端對客戶端的二次校驗,避免 DoS 攻擊。同樣以 DTLS 1.2 舉例(TLS 1.3 和 DTLS 1.3 的流程已經很接近了),相比 TLS 1.2,DTLS 1.2 大部分步驟都是一樣的,只是在服務端多了一步 HelloVerifyRequest,客戶端因此也多了第二次的 ClientHello,如下圖所示:

服務端在首次收到客戶端傳送的 Client Hello 之後,只會生成一個 Cookie,不進行任何其他的操作,並給客戶端傳送 HelloVerifyRequest 訊息,帶上這個 Cookie。只有當客戶端重新發送一次 Client Hello,並帶上服務端傳送的這個 Cookie 後,服務端才會繼續握手過程。

握手訊息格式

DTLS 的握手訊息格式如下所示,可以看到相比 SSL/TLS 的握手協議,DTLS 在訊息型別中多了 HelloVerifyRequest 這種訊息(在握手過程中介紹過),在結構體中多了 message_seq、fragment_offset、fragment_length 三個欄位。SSL/TLS 基於 TCP,因此不需要操心重放、亂序、丟包的問題,可靠傳輸由 TCP 做了保證;而 DTLS 基於 UDP,UDP 是一種盡力而為的協議,因此 DTLS 需要自己處理重放、亂序、丟包的問題。DTLS 在複用大部分 TLS 的基礎上做了一些小改動,在握手訊息中增加了三個欄位 message_seq、fragment_offset、fragment_length 三個欄位,具體的功能在下一節中講述。

enum {     hello_request(0), client_hello(1), server_hello(2),     hello_verify_request(3),                   // New field     certificate(11), server_key_exchange (12),     certificate_request(13), server_hello_done(14),     certificate_verify(15), client_key_exchange(16),     finished(20), (255) } HandshakeType;   struct {     HandshakeType msg_type;     uint24 length;     uint16 message_seq;                        // New field     uint24 fragment_offset;                    // New field     uint24 fragment_length;                    // New field     select (HandshakeType) {       case hello_request: HelloRequest;       case client_hello:  ClientHello;       case server_hello:  ServerHello;       case hello_verify_request: HelloVerifyRequest;// New field       case certificate:Certificate;       case server_key_exchange: ServerKeyExchange;       case certificate_request: CertificateRequest;       case server_hello_done:ServerHelloDone;       case certificate_verify:  CertificateVerify;       case client_key_exchange: ClientKeyExchange;       case finished: Finished;     } body; } Handshake;

每種訊息同樣都會有自己獨立的訊息體,訊息體中的內容就是握手過程中提到的訊息中需要攜帶的一些資訊,大部分的訊息體內容與 SSL/TLS 相同,區別有兩點:

多了 HelloVerifyRequest 訊息,需要攜帶 Cookie,訊息體如下所示:
struct {        ProtocolVersion server_version;        opaque cookie<0..32>;      } HelloVerifyRequest;
ClientHello 多了 Cookie 欄位,因為第二次 ClientHello 需要攜帶 Cookie 資訊:
struct {        ProtocolVersion client_version;        Random random;        SessionID session_id;        opaque cookie<0..32>;                             // New field        CipherSuite cipher_suites<2..2^16-1>;        CompressionMethod compression_methods<1..2^8-1>;      } ClientHello;

與 SSL/TLS 在實現上的區別

在上文的握手過程和握手協議中已經講述了一些 DTLS 與 SSL/TLS 的區別。此外,DTLS 針對重複、亂序、丟包等問題增加了一些防護機制。

握手防護機制

重傳

TCP 天然的重傳機制保證了訊息不會丟失,而 UDP 對此沒有任何保證。因此 DTLS 額外增加了超時重傳機制來確定握手訊息到達,流程如下:

以握手的第一階段舉例,客戶端傳送 Client Hello(不帶 Cookie,區別於握手流程中的第二次 Client Hello)之後,啟動一個定時器,等待服務端返回 HelloVerifyRequest,如果超過了定時器時間客戶端還沒有收到 HelloVerifyRequest,那麼客戶端就會知道要麼是 Client Hello 訊息丟了要麼是 Hello Verify Request 訊息丟了,客戶端就會再次傳送相同的 Client Hello 訊息,即使服務端確實傳送了 Hello Verify Request 還是收到了 Client Hello 訊息,它也知道是需要重傳,並再次傳送 Hello Verify Request 訊息,同樣地,服務端也會啟動定時器來等待嚇一條訊息。

序列號

TCP 訊息中自帶了序列號 seq,並處理了亂序的問題,而 UDP 對亂序問題也沒有任何保證。為了保證握手訊息的有序性,DTLS 在握手報文中增加了 message_seq 欄位便於接收方處理亂序訊息。接收方直接處理屬於當前步驟的訊息,提供一個快取佇列來快取提前到達的訊息。

訊息重放檢測

訊息重放也是 DoS 攻擊的一種,攻擊者可以擷取傳送者傳送的資料,並直接原封不動地發給接收方,來達到欺騙接收方的目的。 DTLS 增加了類似 IPsec AH/ESP 的訊息重放檢測,使用一個 bitmap 滑動視窗來接收訊息,結合訊息本身的序號,bitmap 可以判斷該訊息是否是太老的訊息,是的話則直接拋棄。這個功能在 DTLS 中是可選的,因為某些 UDP 報文的重複只是單純因為網路錯誤。

報文大小限制

TCP 面向位元組流,而 UDP 面向報文。因此 TCP 會自動將報文進行拆分和組裝,無需上層操心;而 UDP 報文如果超過了 MTU(鏈路層的最大傳輸單元) 限制,會在 IP 層被強制分片,使得每一片大小都小於 MTU,接收方 IP 層需要進行資料報的重組,這樣就會多做許多工作,更麻煩的地方在於只要其中一片丟失就會造成重組失敗,造成整個 UDP 報文丟失。 因此 DTLS 直接在 UDP 之上就對握手訊息做了分段,握手報文中的 fragment_offset 和 fragment_length 就是為了這個目的,分別代表這段報文相對訊息起始的偏移量以及這段報文的長度。

Cookie

在上文的握手過程中 TLS 1.2 及之前都沒有嘗試解決 DoS 攻擊的問題,直到 TLS1.3 才透過加入了 HelloRetryRequest 和 Cookie 來解決 DoS 攻擊的問題(並且在 TLS 1.3 的 RFC 中提到主要用於非面向連線的通道,也就是 UDP 連線)。相對 TCP 來說,UDP 連線對 DoS 攻擊更加敏感,因為 TCP 可以使用 SYN Cookie 的機制來防範 DoS 攻擊。 如果在 UDP 上直接使用 TLS 的握手方式,就可能發生以下兩種情況:

攻擊者可以透過傳送一系列握手啟動請求來消耗伺服器上的資源,例如服務端可能會為此分配緩衝區,並且加密過程也是一個非常消耗 CPU 資源的操作;攻擊者可以偽造受害客戶端的 IP 地址向服務端發起 DTLS 握手,迫使服務端傳送 Certificate 訊息給受害客戶端,上文提到過 Certificate 攜帶了很多資訊,包括數字證書等,所以這個訊息會非常大,使得受害客戶端不得不接受大量無用訊息。

因此 DTLS 在 1.0 版本就加入了 HelloVerifyRequest 和 Cookie,用於服務端對客戶端的二次校驗,避免 DoS 攻擊。具體實現方式如下:

當客戶端首次給服務端傳送 Client Hello 時,服務端只會生成一個 Cookie 並透過 HelloVerifyRequest 傳送給客戶端,不會執行分配緩衝區等操作,直到收到帶上相同 Cookie 的 Client Hello 才會繼續握手,可以使得偽造 IP 的攻擊難以實現(使用真實 IP 的 DoS 攻擊無能為力);HelloVerifyRequest 足夠小,即使服務端被攻擊者當槍使來攻擊其他機器,也不會造成大量資料傳送。

加密方式

由於 SSL/TLS 依賴 TCP,所以 SSL/TLS 有如下幾個特性:

SSL/TLS 不能獨立解密單個封包,SSL/TLS 對於封包的認證需要序號作為輸入,在 SSL/TLS 中並未直接傳遞序號,因為 TCP 是可靠的,所以 TLS 的兩端各自維護自身的收發序號;SSL/TLS 的某些加密演算法不是獨立解密的,需要依賴上個封包,典型的就是 RC4 流加密演算法。

由於 UDP 本身的不可靠,為了解決上面提到的第一個問題,DTLS 在每條記錄中顯式攜帶的序號作為解碼的輸入;而對於第二個問題,DTLS 的現狀是不能支援 RC4 等流式加密演算法。

9
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 使用 Jetpack DataStore 進行資料儲存