根據本人調試ss的經驗,寫出本文,以便讓自己不會忘記ss協議的實現細節。這里ss用縮寫,是因為寫全了過不了審核,懂的自然懂。
整體流程
- 瀏覽器配置好socks5代理,瀏覽器訪問目標服務器的時候,請求就會轉發到sslocal
- sslocal收到socks5請求,解析出請求對象,將請求對象最前面加上ss頭部(見詳細說明->ss頭部),再生成一個32字節的鹽(也稱為IV),再用aes-256-gcm進行加密,將鹽和加密后的數據一起發送給ssserver
- ssserver收到sslocal傳輸的數據,先讀取出32字節的鹽,再用aes-256-gcm進行解密,再從解密完的數據中取出ss頭部,根據ss頭部的指示連接目標服務器,接着把剩余的數據發送給目標服務器
- 目標服務器處理請求,將結果返回給ssserver
- ssserver收到結果,生成一個32字節的鹽,再用aes-256-gcm進行加密,將鹽和加密后的數據一起發送給sslocal
- sslocal收到數據,取出32字節的鹽,再用aes-256-gcm進行解密,將解密好的數據通過socks5協議返回給瀏覽器
- 瀏覽器收到數據,展示頁面結果
詳細說明
基本概念
password
ss的密碼,這個在配置ss連接的時候會用到
method
aes-256-gcm,這是加密方式,此外還有aes-128-gcm、aes-192-gcm、chacha20-ietf-poly1305、xchacha20-ietf-poly1305,因為時間有限我只研究了aes-256-gcm
key
密鑰,根據password和method生成
method決定key的長度,例如aes-256-gcm就是32字節
第一個16字節 = md5(password)
第二個16字節 = md5(md5(password) + password)
參考代碼:CryptServiceImpl#getKey
salt
鹽,也稱為IV,在aes-256-gcm中為32字節
subkey
子密鑰,根據key和salt生成,aes-256-gcm是32字節
具體怎么生成的,我也不是特別清楚,好像跟HKDF(SHA1)有關
參考代碼:CryptServiceImpl#genSubkey
tag
標簽,aes-256-gcm中每次加密都會生成一個16字節的tag,具體干嘛用的我也不知道
nonce
不知道怎么稱呼它,在aes-256-gcm中它是12字節byte數組,每次加密都會使最前面的byte加1,前面的byte滿了會讓下一個byte加1
aes-256-gcm加密
每一次請求,都先會產生一個隨機的32字節salt
根據password和method,可以產生key
根據key和salt,可以產生subkey
根據method、nonce、subkey,可以將明文變成密文
下面是具體加密流程
數據按最大0x3fff字節分塊,每一塊將長度明文加密,生成一個tag,nonce遞增,數據內容明文加密,生成一個tag,nonce遞增。一直這么處理,直到數據全部加密完畢
最后的結構如下所示:
注意:每一次請求只會有一個salt,不管本次請求數據有多長salt都是不會變的。不同的請求salt是不同的。
aes-256-gcm解密
每一次請求,在aes-256-gcm中都先取出頭部32字節salt
根據password和method,可以產生key
根據key和salt,可以產生subkey
根據method、nonce、subkey,可以將密文變為明文
具體解密流程,就是先取2+16字節,解密解析出長度,再取長度+16字節,解密解析出內容
將所有內容拼起來就是解密后的結果
ss頭部
ss頭部與加密解密無關,因為正常的數據中只有請求數據,而沒有請求服務器的地址,所以在請求數據的頭部加了1+n+2個字節表示請求服務器的地址
ss頭部格式如下圖所示
ss頭部與正常請求數據結合在一起形成明文,和加密解密中的salt一樣,ss頭部每次請求也只有一個
總結
一圖勝萬言
代碼地址
鳴謝
感謝github上面的相關代碼,這里我就不貼了,貼了過不了審核