iT邦幫忙

2025 iThome 鐵人賽

DAY 9
0
Software Development

Go Clean Architecture API 開發全攻略系列 第 9

身份驗證詳解 (一):安全的密碼雜湊 (Hashing) 與處理

  • 分享至 

  • xImage
  •  

如何處理使用者的密碼,是衡量一個後端開發者專業程度和安全意識的試金石。
一旦處理不當,導致使用者密碼洩漏,將會對使用者和公司帶來災難性的後果。
本篇文章,我們將學習密碼儲存的黃金標準。

安全鐵律第一條:絕對、絕對、絕對不要用明文儲存密碼!

錯誤的示範:為何 MD5 和 SHA 不安全?

有些開發者可能會想:「我把密碼加密一下再存總可以了吧?」於是他們使用了像 MD5 或 SHA256 這樣的快速雜湊演算法。這是一個極其危險的誤區。

  • 雜湊(Hashing) vs. 加密(Encryption):加密是可逆的,有金鑰就可以解密回原文。雜湊是單向的,理論上無法從結果反推出原文。儲存密碼,我們需要的是單向的雜湊。

  • 快速雜湊演算法的漏洞:像 MD5 和 SHA 這類演算法,被設計出來的目的是追求「快」,以便快速校驗檔案的完整性。但這個「快」字,在密碼學上卻是致命的弱點。攻擊者可以利用這個特性,建立一個「彩虹表(Rainbow Table)」。他們可以預先計算好上億個常用密碼的雜湊值,存成一個巨大的查詢表。一旦他們拿到了你資料庫裡的密碼雜湊,只需在彩虹表裡一查,就能瞬間反推出大量使用者的原始密碼。

原文: ji394su3
md5: 7b96d9d359699d09c0ec43d521689ee5
sha1: 3fe25f46739b6ae8fa930d8a8acaeb692675fe4f

適當的做法:使用 bcrypt

為了安全地儲存密碼,我們需要一個慢速的、加鹽的雜湊演算法。

bcrypt 就是專為此目的而設計的。

bcrypt 有三個核心特性,完美地解決了上述問題:

  1. 慢(Slow):這是 bcrypt 最重要的特性。它被故意設計得非常消耗計算資源。這意味著,即使攻擊者拿到了你的資料庫,想對一條密碼進行暴力破解,也需要花費極長的時間,使得攻擊在經濟上和時間上都變得不可行。

  2. 加鹽(Salted):這是 bcrypt 對抗彩虹表的法寶。在對密碼進行雜湊前,bcrypt 會為每一位使用者自動生成一個獨一無二的隨機字串,稱為「鹽(Salt)」。它實際進行的操作是 hash(password + salt)。這樣一來,即使兩個使用者使用了完全相同的密碼(例如 123456),由於他們的「鹽」不同,最終儲存在資料庫中的雜湊值也將是截然不同的。彩虹表瞬間失效。

  3. 可調整的成本因子(Cost Factor)bcrypt 允許我們設定一個「成本因子」,這個數字決定了計算的複雜度。隨著未來電腦算力的提升,我們可以簡單地透過調高這個成本因子,來繼續保證雜湊的「慢速」,以應對更強大的硬體。

實作:PasswordService

現在,讓我們動手來實作一個專門處理密碼的服務。

  1. 使用 bcrypt 函式庫:Go 的官方擴充套件加密庫中已經包含了 bcrypt 的實作。

  2. 建立服務檔案

    // internal/service/password/password.go
    
     // 提供密碼雜湊功能
    func (s *Service) Hash(password string) (string, error) {
        hashedBytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
        if err != nil {
            return "", err
        }
        return string(hashedBytes), nil
    }
    
    // 提供密碼比對功能
    func (s *Service) Compare(hashed, password string) error {
        return bcrypt.CompareHashAndPassword([]byte(hashed), []byte(password))
    }
    

dependency Injection

internal/application/service.go

type Service struct {
	Password *password.Service
}

func NewService(app *Application) *Service {
	return &Service{
		Password: password.NewService(),
	}
}

internal/application/application.go

func New(cfg *config.Config) (*Application, error) {
  ... // 省略其他初始化程式碼
	app := &Application{
    ...
  }

	app.Service = NewService(app)
	return app, nil
}

總結

在本篇文章中,我們學習了安全儲存密碼的關鍵原則,並使用 bcrypt 演算法實作了一個健壯的 PasswordService
這為我們整個身份驗證系統打下了最堅實、最安全的基礎。
也完成了我們之前 usecase 所需要的 password Interface。

現在,我們已經能夠安全地「驗證」使用者的身份了。
在下一篇文章中,我們將解決下一個問題:驗證成功後,如何發給使用者一個安全的「通行證」,讓他們可以在後續的請求中證明自己的身份。
這個通行證,就是 JWT (JSON Web Token)
也就是我們之前 usecase 所需要的 token Interface。

以上程式碼的完整內容可以到 Github 觀看


上一篇
Usecase 層的職責:編排你的核心業務邏輯
系列文
Go Clean Architecture API 開發全攻略9
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言