寫在前面:根據obfs4官方文檔翻譯,其中還有一些內容沒理解,后續慢慢補,歡迎大家討論交流
obfs4是什么?
這是一個看起來沒有混淆的協議,它融合了Philipp Winter的ScrambleSuit協議中的想法和概念。選擇obfs命名主要是因為它更短,就協議來說,obfs4比obfs2 / obfs3更接近ScrambleSuit。
ScrambleSuit和obfs4之間的顯着差異:
- 握手總是進行全密鑰交換(沒有會話票務握手)
- 握手采用Tor項目中使用Elligator 2映射進行模糊處理的公鑰的ntor握手。
- 鏈路層加密使用NaCl密碼箱(Poly1305 / XSalsa20)。
另外,obfs4proxy還可以充當obfs2 / 3客戶端和橋接器,以簡化向新協議的過渡。
為啥不擴充 ScrambleSuit?
這是我的協議,如果我願意,我會混淆。
由於對握手過程進行了很多更改,因此將ScrambleSuit擴展為編寫支持兩種握手變體的服務器實現而不是非常緩慢是不重要的是沒有意義的。
Since a lot of the changes are to the handshaking process, it didn't make sense to extend ScrambleSuit as writing a server implementation that supported both handshake variants without being obscenely slow is non-trivial.
介紹
這是TCP協議層的混淆,目的是防止第三方根據消息內容得知正在使用的協議。
與obfs3不同,雖然也是主要圍繞為現有身份驗證協議(如SSH或者TLS)提供一個層模糊處理,但obfs4嘗試提供身份驗證和數據完整性。
與obfs3和ScrambleSuit一樣,該協議有兩個階段:在第一階段,雙方都建立密鑰。 在第二,雙方交換強加密的流量。
動機
ScrambleSuit的開發旨在改進obfs3協議,以對抗主動攻擊者和偽裝流簽名(流特征?)。
像現有的obfs3協議一樣,ScrambleSuit使用UniformDH進行加密握手,但模冪運算嚴重影響性能。 此外,密鑰交換未經過身份驗證,因此如果他們知道客戶端/網橋共享密鑰的話,主動攻擊者就可以實施中間人攻擊。
obfs4試圖通過使用基於Tor項目的ntor握手的身份驗證的密鑰交換機制來解決這些缺點。 線路上所傳輸的Curve25519公鑰是通過Elligator 2映射完成混淆的。
威脅模型
obfs4是在obfs2威脅模型的基礎上修改而來:
obfs4對抗關注obfs4協議的被動深度包檢測設備。如果沒有獲得服務器的節點ID(Node ID)和身份公鑰,這些機器應該無法驗證obfs4協議的存在。
obfs4對抗試圖探測obfs4服務器的主動攻擊者。如果沒有獲取服務器的節點ID和身份公鑰,此類計算機應該無法驗證obfs4服務器是否存在。
obfs4對抗獲得服務器節點ID和身份公鑰的主動攻擊者。 如果沒有獲取服務器的身份私鑰,此類計算機應該無法模擬服務器。
obfs4針對某些非內容協議指紋提供保護,特別是數據包大小和可選的數據包時序。
obfs4提供底層流量的完整性和機密性,以及服務器的身份驗證。
符號和術語
所有Curve25519密鑰和Elligator 2 實現都以小端序傳輸,以便於與當前的Curve25519和Elligator 2實現集成。 所有其他數字字段以大端序(網絡字節順序)值傳輸。
HMAC-SHA256-128(k,s)s為HMAC-SHA256摘要,k為密鑰,截斷長度為128位。
x | y是x和y的串聯。
byte是8位的字節。
密鑰建立階段
作為配置的一部分,所有的obfs4服務器都有一個20字節的節點ID(NODEID)和Curve25519密鑰對(B,b),用於使客戶端知道給定的服務器並驗證服務器。
服務器通過帶外機制將身份密鑰(B)和NODEID的公共組件分發給客戶端。
- 握手數據被填充到隨機長度來模糊初始的流簽名(流特征?)。 使用的常數如下:
-
MaximumHandshakeLength = 8192
- 握手請求或響應的最大大小(包括填充)
-
MarkLength = 16
- HMAC-SHA256-128摘要中M_C / M_S的長度
-
MACLength = 16
- HMAC-SHA256-128摘要中MAC_C/MAC_S的長度
-
RepresentativeLength = 32
- Elligator 2 表示的 Curve25519 公鑰的長度.
-
AuthLength = 32
- ntor認證標記(HMAC-SHA256)的長度.
-
InlineSeedFrameLength = 45
- 未填充的TYPE_PRNG_SEED幀的長度。
-
ServerHandshakeLength = 96
- 握手響應中非填充數據的長度。
- RepresentativeLength + AuthLength + MarkLength + MACLength
-
ServerMaxPadLength = 8096
- 握手響應中的最大填充量。
- MaximumHandshakeLength - ServerHandshakeLength
-
ServerMinPadLength = InlineSeedFrameLength
- 握手響應中的最小填充量。
-
ClientHandshakeLength = 64
- 握手請求中非填充數據的長度。
- RepresentativeLength + MarkLength + MACLength
-
ClientMinPadLength = 85
- 握手請求中的最小填充量。
- (ServerHandshakeLength + ServerMinPadLength) - ClientHandshakeLength
-
ClientMaxPadLength = 8128
- 握手請求中的最大填充量。
- MaximumHandshakeLength - ClientHandshakeLength
-
客戶端握手過程如下:
-
客戶端生成臨時的的Curve25519密鑰對
X,x和代表公共組件X'的Elligator 2。 -
客戶端向服務器發送握手請求
clientRequest = X' | P_C | M_C | MAC_C
X' = Elligator 2 representative of X (32 bytes) P_C = Random padding [ClientMinPadLength, ClientMaxPadLength] bytes M_C = HMAC-SHA256-128(B | NODEID, X') E = String representation of the number of hours since the UNIXepoch MAC_C = HMAC-SHA256-128(B | NODEID, X' | P_C | M_C | E)- 客戶端從服務器接收serverResponse
- 客戶端從serverResponse計算M_S並使用它來定位serverResponse中的MAC_S。 然后計算MAC_S並將其與從服務器接收的值進行比較。 如果找不到M_S或MAC_S值不匹配,客戶端必須斷開連接。
- 客戶端通過Elligator 2對應關系反向從Y'導出Y.
- 客戶端完成ntor握手的客戶端,導出256位共享密鑰(KEY_SEED)和身份驗證標記(AUTH)。 然后,客戶端將AUTH的派生值與serverResponse中包含的值進行比較。 如果AUTH值不匹配,客戶端必須斷開連接。
-
-
服務器握手過程如下:
- 服務器從客戶端接收clientRequest。
- 服務器從clientRequest計算M_C並使用它在clientRequest中定位MAC_C。 然后計算MAC_C並將其與從客戶端接收的值進行比較。 如果找不到M_C或MAC_C值不匹配,服務器必須停止處理來自客戶端的數據。
實現必須計算並比較MAC_C的多個值與“E = {E-1,E,E + 1}”以解決客戶端和服務器之間的時鍾偏差。
在此時發生故障的情況下,實現應該延遲從客戶端丟棄TCP連接一個隨機間隔,以使主動探測更加困難。 - 服務器通過Elligator 2映射反向從X'派生X.
- 服務器生成臨時的Curve25519密鑰對Y,y和代表公共組件Y'的Elligator 2。
- 服務器完成ntor握手的服務器端,導出256位共享密鑰(KEY_SEED)和身份驗證標記(AUTH)。
- 服務器向客戶端發送握手響應
serverResponse = Y' | AUTH | P_S | M_S | MAC_S
Y' = Elligator 2 Representative of Y (32 bytes) AUTH = The ntor authentication tag (32 bytes) P_S = Random padding [ServerMinPadLength, ServerMaxPadLength] bytes M_S = HMAC-SHA256-128(B | NODEID, Y') E' = E from the client request MAC_S = HMAC-SHA256-128(B | NODEID, Y' | AUTH | P_S | M_S | E')在每一方完成握手時,它們具有256位共享密鑰KEY_SEED,然后通過ntor密鑰派生函數產生用於加密/驗證數據的144字節密鑰材料。
密鑰使用如下:
Bytes 000:031 - Server to Client 256 bit NaCl secretbox key. Bytes 032:047 - Server to Client 128 bit NaCl secretbox nonce prefix. Bytes 048:063 - Server to Client 128 bit SipHash-2-4 key. Bytes 064:071 - Server to Client 64 bit SipHash-2-4 OFB IV. Bytes 072:103 - Client to Server 256 bit NaCl secretbox key. Bytes 104:119 - Client to Server 128 bit NaCl secretbox nonce prefix. Bytes 120:135 - Client to Server 128 bit SipHash-2-4 key. Bytes 136:143 - Client to Server 64 bit SipHash-2-4 OFB IV.
數據傳輸階段
一旦雙方完成握手,他們將分解的應用程序數據傳輸到“數據包”,然后在NaCl crypto_secretbox_xsalsa20poly1305 幀中加密和驗證。

幀長度是指后續的secretbox的長度。 為了避免在流中發送可識別的長度字段,通過在OFB模式中對從SipHash-2-4導出的掩碼進行異或來對幀長度進行混淆。

由於接收方具有SipHash-2-4密鑰和IV,因此通過導出用於表示長度的掩碼並對截斷的摘要進行異或來獲得密碼箱的長度來解碼長度。
有效載荷長度指的是幀的有效載荷部分的長度,並且不包括填充。 有效載荷長度可能為0,在這種情況下,所有剩余數據都經過驗證和解密,但被忽略。
允許的最大幀長度為1448字節,每個幀允許傳輸最多1427個字節的有用有效載荷。
NaCl secretbox(Poly1305 / XSalsa20)格式為:
uint8_t [24]前綴(固定)
uint64_t counter(Big endian)
計數器初始化為1,並在每幀上遞增。 由於協議被設計為在可靠介質上使用,因此會話的兩側都知道前綴和初始計數器值,因此不通過線路傳輸隨機數。 計數器必須不包裝,並且會話必須在發送2 ^ 64幀之前終止。
如果解密密碼箱失敗(由於標簽不匹配),必須斷開連接。
type字段(如果有)用於表示每個數據包中包含的有效負載類型。
TYPE_PAYLOAD (0x00):整個有效負載將被視為應用程序數據。
TYPE_PRNG_SEED (0x01):整個有效載荷將被視為協議多態性PRNG的種子。 格式是24字節。
實現應該為了向前兼容性而忽略未知的數據包類型,盡管每個幀仍然必須經過身份驗證和解密。
協議多態性
實現必須實現協議多態性來混淆obfs4流特征。 實現應遵循ScrambleSuit的實現(參見“ScrambleSuit協議規范”,第4節)。 與ScrambleSuit一樣,實現可以省略到達間隔時間混淆作為性能權衡。
作為優化,如果它始終在serverResponse主體之后立即發送幀,可以將TYPE_PRNG_SEED幀視為serverResponse的一部分。 如果這樣做,則TYPE_PRNG_SEED幀必須具有0字節的填充,並且必須在ServerMinPadLength為0的情況下生成P_S(P_S由[0,8096]字節的隨機數據組成)。 然而,ClientMinPadLength的計算沒有改變(P_C仍包含[85,8128]字節的隨機數據)。
