我們已經大概了解 SRP 是怎樣在不傳輸密碼的狀況下讓伺服器跟客戶端驗證對方,不過 Unlight 是怎樣利用產生後的 Session Key 去加密傳輸還看不出來。
前面我們有討論到當玩家建立連線後,會執行 ULServer 的 #post_init
方法,如何切換為加密連線其實就在這裡面的 @crypt
物件
# 接続時
def post_init
# ...
@crypt = Crypt::None.new
# ...
end
我們可以在 src/net/crypt.rb
找到 Crypt::None
的行為
# 暗号化をしない
class None
def encrypt(data)
data
end
def decrypt(data)
data
end
end
簡單說就是什麼都不做,但是這並沒有加密難道 SRP 是做好玩的嗎?
實際上當玩家嘗試連線某個伺服器的時候,會需要經過一個叫做 #negotiation
的動作,在這邊會檢查玩家是否已經登入等等狀態。此時呼叫 #set_session_key
方法進行處理:
# ネゴシエーション(認証済みかを確認)
def negotiation(id)
SERVER_LOG.debug("<UID:#{id}>#{@@class_name}: [nego start]")
begin
@player = Player[id]
# 認証済みか
if @player && @player.login?
# ...
# 暗号化をON
if BOT_SESSION
set_session_key(BOT_SESSION_KEY)
else
set_session_key(@player.session_key)
end
# ネゴシエーションの確認
nego_cert(@nego_crypt ,"are you ok")
else
# ...
end
rescue =>e
SERVER_LOG.fatal("#{@@class_name}: [negotiatin fatal error] #{e}")
end
end
我們在這段程式碼會看到 #set_session_key
動作重新產生了 Crypt 物件並且改用了 XOR 模式來替代原本的 @crypt
物件:
# セッションキーを設定して暗号化をONにする
def set_session_key(sID)
@crypt = Crypt::XOR.new(sID)
end
不過,我們是什麼時候知道 Session Key 存在的?在做 #negotiation
的時候我們會從資料庫抓取玩家的 Session Key 來使用,也就是說在 AuthServer 登入成功的同時也會把這次連線用的 Session Key 存到資料庫來讓其他伺服器可以抓取出來當作加密的金鑰。
因此我們會在 AuthServer (src/protocol/authserver.rb
)裡面登入的最後步驟看到以下的處理:
# クライアントへ確認コマンドを送る
def cert(m)
SERVER_LOG.info("#{@@class_name}: [auth_cert] #{@player.name}")
auth_cert(@@srp.get_cert(@c_pub,m,@strong_key),@player.id)
@player.login(@ip, @strong_key)
if @@online_list.include?(@player.id)
SERVER_LOG.info("<UID:#{@player.id}>#{@@class_name}: [login push out] pushed out")
pushout
end
if @player
regist_connection
end
end
在 @player.login(@ip, @strong_key)
這個動作會把玩家這次登入的 IP 以及 Session Key 存到資料庫。
Strong Key 是基於 Session Key 進行 Hash 計算出來的,我們可以當他是一個加強版的 Session Key
簡單說,一但登入成功後,所有伺服器都可以基於玩家編號來抓取 Session Key,只要使用了錯誤的 Session Key 即使知道玩家編號也無法正確的進行操作,這跟我們在 Web 開發上使用的 Cookie / Session 機制有點不同。不過是不是有點類似 RESTful API 期望的 Stateless (無狀態)設計呢?僅透過 API Token 就可以判斷使用者或是資源擁有者(Resource Owner)
我的個人部落格是弦而時習之平常會把自己發現的一些新技巧紀錄在上面,也歡迎大家來逛逛。