Golang實現Python Django2密碼算法pbkdf2_sha256(無損遷移Django項目)

Golang實現Python Django2密碼算法pbkdf2_sha256

0. 說明

系統環境

go version go1.14.3 darwin/amd64

因爲項目需要從Python Django框架重構爲Golang項目,爲了保證用戶數據不丟失,所以密碼算法使用與Django框架相同的pbkdf2_sha256加密算法。
以下代碼根據GitHub開源項目github.com/alexandrevicenzi/unchained/修改而成,有興趣的可以翻閱項目代碼。

1. 簡單的加密算法分析

直接拿出一個經過pbkdf2_sha256加密後的密鑰

pbkdf2_sha256$180000$9v62SVRUKPTf$igFByaMbYXhOh7p375LDdo2GYrUV9RbqdTaR6jbhrKg=

密鑰一共分爲4個部分,分別是加密算法名稱加密迭代次數鹽(salt)base64編碼,每一個部分都由美元$符號分割,由此我們解析上面密鑰的數據。

  • 加密算法名稱:pbkdf2_sha256
  • 加密迭代次數:180000
  • 鹽:9v62SVRUKPTf
  • Base64編碼:igFByaMbYXhOh7p375LDdo2GYrUV9RbqdTaR6jbhrKg=

由於pbkdf2_sha256屬於單向加密算法(即無法通過密鑰反推原始密碼),所以我們要校驗密碼需要用同樣的方式來加密,最終將兩個加密密鑰對比,如果相同則認爲密碼正確,整體流程如下所示。

pbkdf2_sha256算法
相同
不相同
密碼
加密後密碼
數據庫存儲的密鑰
成功
失敗
對比

2. 加密

需要使用加密算法庫golang.org/x/crypto/pbkdf2,控制檯輸入如下命令:

go get golang.org/x/crypto/pbkdf2

加密過程我們需要三個參數,分別是原始密碼鹽(Salt)加密迭代次數,其中我們可以隨機生成(注意:不能包含美元$符號,不然會被誤認爲是分隔符),加密迭代次數可以隨意設置,一般情況下越大則越難被破譯,但開銷也越大,Django選用180000次,這裏不深入討論。

func PasswordEncode(password string, salt string, iterations int) (string, error) {
    // 一共三個參數,分別是原始密碼、鹽、迭代次數
    
    // 如果沒有設置鹽,則使用12位的隨機字符串
	if strings.TrimSpace(salt) == "" {
		salt = CreateRandomString(12)
	}
	
	// 確保鹽不包含美元$符號
	if strings.Contains(salt, "$") {
		return "", errors.New("salt contains dollar sign ($)")
	}

    // 如果迭代次數小於等於0,則設置爲180000
	if iterations <= 0 {
		iterations = 180000
	}

    // pbkdf2加密 <--- 關鍵
	hash := pbkdf2.Key([]byte(password), []byte(salt), iterations, sha256.Size, sha256.New)
	
	// base64編碼成爲固定長度的字符串
	b64Hash := base64.StdEncoding.EncodeToString(hash)
	
	// 最終字符串拼接成pbkdf2_sha256密鑰格式
	return fmt.Sprintf("%s$%d$%s$%s", "pbkdf2_sha256", iterations, salt, b64Hash), nil
}

// 隨機字符串生成函數(不深入討論)
func CreateRandomString(len int) string {
	var container string
	var str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
	b := bytes.NewBufferString(str)
	length := b.Len()
	bigInt := big.NewInt(int64(length))
	for i := 0; i < len; i++ {
		randomInt, _ := rand.Int(rand.Reader, bigInt)
		container += string(str[randomInt.Int64()])
	}
	return container
}

3. 校驗

我們只需要通過將原始密碼重新使用按照相同的迭代次數加密就能獲得對應的密鑰,將我們得到的密鑰和數據庫中的密鑰對比,如果相同則認爲是正確的密碼,如果不相同則認爲是不正確的密碼。

func PasswordVerify(password string, encoded string) (bool, error) {
    // 輸入兩個參數,分別是原始密碼、需要校驗的密鑰(數據庫中存儲的密碼)
    // 輸出校驗結果(布爾值)、錯誤
    
    // 先根據美元$符號分割密鑰爲4個子字符串
	s := strings.Split(encoded, "$")

    // 如果分割結果不是4個子字符串,則認爲不是pbkdf2_sha256算法的結果密鑰,跳出錯誤
	if len(s) != 4 {
		return false, errors.New("hashed password components mismatch")
	}

    // 分割子字符串的結果分別爲算法名、迭代次數、鹽和base64編碼 
    // ---> 這裏可以獲得加密用的鹽
	algorithm, iterations, salt := s[0], s[1], s[2]

    // 如果密鑰算法名不是pbkdf2_sha256算法,跳出錯誤
	if algorithm != "pbkdf2_sha256" {
		return false, errors.New("algorithm mismatch")
	}

    // 將迭代次數轉換成int數據類型 -->這裏可以獲得加密用的迭代次數
	i, err := strconv.Atoi(iterations)
	if err != nil {
		return false, errors.New("unreadable component in hashed password")
	}

    // 將原始密碼用上面獲取的鹽、迭代次數進行加密
	newEncoded, err := PasswordEncode(password, salt, i)
	if err != nil {
		return false, err
	}

    // 最終用hmac.Equal函數判斷兩個密鑰是否相同
	return hmac.Equal([]byte(newEncoded), []byte(encoded)), nil
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章