加密連線的方式有很多種,像是使用 HTTP 協定可以透過 HTTPS 之類來加密,運氣不錯的事 SRP 算是容易理解而他的應用也在 OpenSSL 和一些雲端服務都有被使用是很值得了解基本原理的。
首先,我們可以用比較常接觸到的 HTTPS 建立一個簡單的概念,通常我們會使用公鑰(Public Key)加密資訊傳給伺服器,而伺服器也會用秘鑰(Private Key)加密資訊傳給使用者。
在 SRP 也是類似的只是我們的目標是要產生一個加密用的密碼(Session Key)在這過程中伺服器跟使用者都會各自產生一個 Public Key 傳給對方,並且伺服器跟客戶端會基於對方提供的 Public Key 和一些共用的資訊,用不同的計算公式去計算出一個相同的 Session Key 來用做此次連線的加密金鑰,因此每次連線都會產生不同的 Public Key 來達到安全傳輸資料的目的。
我們可以參考 SRP 的設計文件,來一步一步了解原理。
首先是名詞的定義,在文件中定義了很多資訊如下(因為跟數學有關,所以有解釋錯誤的還請指正)
N
- 一個大質數(所有的計算要可以整除)g
- 用來產生 N 的餘數k
- 乘法參數(等於 H(N, g)
)s
- 使用者的 Salt 值I
- 使用者的帳號p
- 使用者的密碼H()
- 一個單項的雜湊函數(例如: SHA1, SHA256 等)^
- 次方(取餘數,有些程式碼會計算完次方後做 % N
的計算就是因為這個關係)u
- 一個隨機的數值組合(在這邊要會是 H(A, B)
)a
, b
- 秘密數(一個隨機數,)A
, B
- 公開數(基於秘數計算出來的)x
- 密鑰(簡單說就是基於 Salt 和各種使用者相關資訊計算出來的值)v
- 驗證密碼(伺服器用來驗算密碼的值)SRP 有很多版本不斷改進,我們會以
SRP6a
這個比較新的版本作為討論。另外前面的「秘密數」跟「公開數」我想不到一個適合的翻譯,簡單說就是一個隨機數(只在這次連線中使用)
知道這一大堆計算會用到的數值後頭應該是很痛的,不過想要實作的話過程其實是相對單純的,只要把數值帶入就可以。
先由「使用者」發起驗證,把帳號跟 A
(使用者的公開數)發給伺服器。
A
的計算是g ^ a
,再發起前會先產生出a
當伺服器收到後,就會產生出 b
然後計算出 B
之後跟 Salt 一起發給客戶端。
B
的計算是kv + g ^ b
稍微複雜,另外 Salt 會存在伺服器上,使用者註冊時會將 Verifier 和 Salt 發送到伺服器,因此 SRP 是不需要傳送明文密碼到伺服器的,也因此才會取名為「Secure Remote Password」
其實就是把 u
計算出來,因為已經把「公開數」交換,所以兩邊都應該能算處一個相同的 H(A, B)
的數值 u
先算出 x
也就是把拿到的 Salt 跟密碼混合算出來,基本上是 H(s, p)
不過在 Unlight 裡面應該是 H(s, I:p)
的形式,會把帳號也放進去。
不過
H(s, I:p)
的做法我們會在 1Password 提供的 Golang SRP 套件提到這是不太安全的 XD
然後算出 S
也就是 Session Key 在客戶端的公式是 (B - kg^x) ^ (a + ux)
伺服器一樣要先有 S
所以用伺服器的公式 (Av^u) ^ b
理論上會要得到一個跟客戶端一樣的數值。
接下來為了驗證雙方的 Session Key 正確,客戶端會計算一個 Matcher 來跟伺服器比對。
K
是再把 Session Key 做一次 Hash 得到的結果(H(S)
)叫做 Strong Session Key
客戶會用公式 H(H(N) xor H(g), H(I), s, A, B, K)
計算 M
(Matcher) 然後發給伺服器,因為計算所需要的資訊都是雙方知道的資訊,所以伺服器會用同樣的方法算出來比對雙方的結果是否相同。
最後會用 H(A, M, K)
計算出一個憑證發回給使用者表示登入成功。
上述都是大數運算要注意語言是否支援,另外還要小心有些情況下會需要對齊 Byte 數(像是 Hash 的結果長度要正確)不然會因為一點小誤差而造成伺服器跟客戶端的結果差異。
另外在使用上要注意的是 Unlight 實作的是論文的版本(也就是上面的版本)但是網路上能找到的套件(Ex. 1Password 的 Golang SRP 套件、Mozilla 的 Node.js SRP 套件)實作的是 RFC5054 所定義的規則,在第一個步驟使用者是要先向伺服器要求 N
, g
, B
和 Salt 等資訊,接著基於 Salt 生成 a
和 A
最後發送回伺服器開始驗證,跟原本論文上的實作是有差異的因此無法直接套用。
我的個人部落格是弦而時習之平常會把自己發現的一些新技巧紀錄在上面,也歡迎大家來逛逛。