NKN公鏈源碼學習(1):webgui的加密通信

背景

NKN的web-gui即dashboard模塊,在通信過程中,有參數的HTTP包都會進過一次AES對稱加密。

像這樣

POST /api/wallet/open HTTP/1.1
Host: 192.168.1.100:30000
Content-Length: 63
Accept: application/json, text/plain, */*
Unix: 1588221168
Origin: http://192.168.1.100:30000
Content-Type: application/json;charset=UTF-8
Cookie: i18n_redirected=en; session=MTU4ODIyMTE2M3xRY2VaUnlSclQ1V3lOUllOSzAzSHZnMWhaZkJPbWpELUVjSGZNcTdBVWpHNnZLa1lZakF6b08xbXlHUklLanVCWU9fWkJueThRY0VnVU1mWjBrbHNmWjVjTFY1bnAzZVBJZUFtM1l0ay02WjZtTkNmaTRTR1pCNkgtcWVZN2ZRd1B4TGZ8FlyZ4mfYPDAvX_64WpEYZxPLBMNqm4Zq9LwtHU3v0AI=
Connection: close

{"data":"3ddc98eb25e7f572a0294533187e0d24b0e71c76897f300ec8d6"}

這裏加密的data內容,其實是wallet的密碼,即webgui的登陸密碼。
閱讀源碼後總結了一下加密密鑰的協商和使用的實現方法。

解密函數

我們只研究接收者邏輯

DecryptData

func DecryptData(context *gin.Context, hasSeed bool) string {
	...
	seed := ""
	wallet, exists := context.Get("wallet")
	if exists && hasSeed {
		passwordKeyHash := wallet.(*vault.WalletImpl).Data.PasswordHash
		seedByte := sha256.Sum256([]byte(passwordKeyHash))
		seed = BytesToHexString(seedByte[:])
	}

	tick := time.Now().Unix()
	padding := int64(serviceConfig.UnixRange)
	session := sessions.Default(context)
	token := session.Get("token")
	...

	for i := tick - padding; i < tick+padding; i++ {
		seedHash := BytesToHexString(HmacSha256([]byte(seed), []byte(token.(string)+strconv.FormatInt(i, 10))))
		jsonData, err := AesDecrypt(body.Data, seedHash)
		if err != nil {
			continue
		}
		var data map[string]interface{}
		err = json.Unmarshal([]byte(jsonData), &data)
		if err != nil {
			continue
		}
		return jsonData
	}
	...
	return ""
}

解釋一下代碼

記 AES解密密鑰爲decrypt_key
如果函數傳入了hasSeed爲true,
decrypt_key = passwordHash + token
如果hasSeed爲false
decrypt_key = token

可見這個token是從session中取出的,所以token值是與服務器同步的,客戶端不可控。
客戶端在加密的時候要使用"正確的"token

token

服務器的token 每60秒更新一次,是一個uuid。
token的定義和更新代碼如下:

app.Use(func(context *gin.Context) {
		session := sessions.Default(context)

		now := time.Now().Unix()
		if serviceConfig.TokenExp == 0 || serviceConfig.TokenExp+serviceConfig.TokenExpSec < now {
			token := uuid.NewUUID().String()
			serviceConfig.Token = token
			serviceConfig.TokenExp = now + serviceConfig.TokenExpSec

			session.Set("token", token)
			session.Save()
		}

		if session.Get("token") == nil {
			session.Set("token", serviceConfig.Token)
			session.Save()
		}

		context.Next()
	})

只要在規定時間範圍內使用token加密,就可以在服務器端進行解密
範圍是 token的時間戳 加減padding

padding := int64(serviceConfig.UnixRange)

PasswordHash

解密密鑰的另一個部分是PasswordHash
這個在設計的時候被設計成 secret token
解密密鑰的祕密性就體現在PasswordHash的不可知

對於敏感接口的密文,需要PasswordHash和token都正確才能解密

總結

在NKN中 token被理解爲可公開的部分密鑰,PasswordHash爲不可公開的部分。
兩部分組合才能解密。

但問題在於,wallet/open接口默認hasSeed爲空,解密不需要PasswordHash。
也就是說wallet/open接口發送的密文,本質上是可以破解的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章