偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

通過實例理解Web應(yīng)用用戶密碼存儲方案

開發(fā) 前端
用戶密碼的安全存儲是保障Web應(yīng)用與用戶數(shù)據(jù)安全的基石。簡單的密碼存儲實踐如明文和弱哈希算法存在巨大隱患,而隨著計算能力提升,任何weak password都可被輕松破解。為有效保護用戶,Web應(yīng)用必須采取更可靠的密碼存儲方案。

在上一篇文章《通過實例理解Go Web身份認證的幾種方式》[1]中,我們了解了Web應(yīng)用的多種身份驗證方式。但無論哪種方式,用戶初次訪問Web應(yīng)用的注冊流程和登錄流程是不可避免的,而基于用戶名密碼的注冊流程依舊是當今主流。注冊后,Web應(yīng)用后端是如何保存用戶密碼的呢?歷史上都有哪些存儲方案?當今的主流存儲方案又是什么呢?在這篇文章中,我們就來說說Web應(yīng)用的各種密碼存儲方案的優(yōu)缺點,并通過實例來理解一下當前的主流存儲方案。

1. Web應(yīng)用用戶密碼存儲的重要性

用戶密碼是訪問Web應(yīng)用的關(guān)鍵,它直接關(guān)乎到用戶賬號和應(yīng)用數(shù)據(jù)的安全。

如果用戶密碼被泄露或破解,將導致嚴重后果。后果最輕的算是某個用戶或某少數(shù)用戶的賬號被盜用了,用戶將失去對賬號的控制。盜用賬號后,攻擊者可以獲取該用戶的私密信息,或進行額外的攻擊;如果用戶在多個應(yīng)用重復使用同一密碼,那么后果將進一步嚴重,用戶的一系列賬號都將受到安全威脅;更為嚴重的是Web應(yīng)用存儲用戶賬號信息的數(shù)據(jù)庫被攻破(俗稱“脫庫”),攻擊者會拿到存儲的全部用戶賬號信息等,如果用戶密碼存儲不當,攻擊者可以很容易破譯所有用戶的密碼,并基于這些密碼信息做進一步的攻擊。

由此可見,Web應(yīng)用必須非常重視用戶密碼的存儲安全。在當前弱密碼和頻繁密碼泄露成為常態(tài)的背景下,Web應(yīng)用開發(fā)者有責任使用安全的密碼存儲方案,盡力保護用戶信息安全,即便在被脫庫的最糟糕情況下,也不讓攻擊者輕易破解出用戶的密碼,這也關(guān)系到應(yīng)用和企業(yè)的信譽。

2. 密碼存儲方案的演進:魔高一尺,道高一丈

Web應(yīng)用用戶密碼存儲方案的演進歷史可以分為以下幾個階段,如圖所示:

圖片圖片

下面我們按圖中的演進順序,對各階段的密碼存儲方案逐一說明一下。

2.1 起始階段 - 明文存儲

早期的Web應(yīng)用為了實現(xiàn)簡單,采用了最簡單“粗暴”的用戶密碼存儲方式:明文存儲,即直接把用戶的密碼以純文本形式存儲在數(shù)據(jù)庫中。

顯然這種方式的最大優(yōu)點就是實現(xiàn)簡單,驗證登錄時直接比對明文密碼。但這種方式最大的缺點就是極其不安全,密碼一旦泄露就失去了全部保密性。但當時人們的安全意識較弱,該方案被廣泛使用。

2.2 弱哈希算法階段 - MD5和SHA1

隨著時間的推移,CPU和GPU性能的提升使得字典破解和窮舉攻擊更加可行有效,大量密碼被泄露的事件引起人們對密碼安全的重視,人們更多地認識到明文存儲密碼的危險性。同時,Web應(yīng)用的發(fā)展也從追求功能和便利,轉(zhuǎn)變?yōu)樵谝子眯耘c安全性之間求平衡。政府和行業(yè)協(xié)會也開始指定密碼存儲的最新安全要求的規(guī)范和政策,密碼學等相關(guān)技術(shù)的快速發(fā)展也為更安全的密碼存儲提供了前提和支持。

于是人們開始使用MD5、SHA1等單向哈希算法對密碼進行處理,只存儲密碼的哈希值。雖然增加了一定的密碼存儲的復雜性,但其最大的優(yōu)點就是在一定程度上放置了明文存儲的密碼泄露問題。

不過,隨著大量使用MD5和SHA-1的應(yīng)用遭到破解,這些哈希算法的脆弱性暴露無遺。同時彩虹表攻擊的出現(xiàn),讓破解者只需要預計算密碼哈希表就可以快速破解以弱哈希存儲的密碼。

于是技術(shù)社區(qū)以及安全規(guī)范都開始提倡和推薦采用更安全的密碼存儲方案,即采用加鹽方案。

2.3 加鹽哈希階段 - 增加隨機鹽值

加鹽哈希就是在計算密碼的哈希值時,在密碼字符串前/后面添加一個稱為“鹽(salt)”的隨機字符串,這個隨機字符串稱為鹽值,它的作用是增加哈希后密碼的隨機性。

加鹽哈希的步驟大致如下圖:

圖片圖片

在用戶注冊階段,系統(tǒng)根據(jù)用戶輸入的密碼生成在數(shù)據(jù)庫中的哈希密碼值:

  • 系統(tǒng)首先隨機生成一個足夠長的隨機字符串作為鹽值,可以使用密碼學安全的隨機數(shù)生成算法;
  • 將鹽值與用戶輸入的原始密碼字符串拼接在一起(鹽值放在密碼的前后均可);
  • 對連接后的字符串計算哈希值,可以使用MD5、SHA-1、SHA256、SHA-512等哈希算法;由于也被證實MD5、SHA-1存在弱點,可以被碰撞攻擊,建議至少使用SHA256算法;
  • 將鹽值和哈希值一起存儲在數(shù)據(jù)庫中(可以向圖中那樣將hashed_password和salt通過:分隔符組合為一個字段后再存儲在數(shù)據(jù)庫中)。

驗證登錄時,系統(tǒng)根據(jù)用戶名取出鹽值,然后將用戶輸入的密碼與鹽值組合計算哈希值,與存儲的原始哈希值比較,相同則驗證成功。

在密碼哈希前加入隨機字符串(即“鹽(salt)”)可以大幅增加了破解難度,同時不同用戶如采用相同密碼,也可以通過不同的鹽在哈希后得到不同的哈希值,這可以有效地防止預計算表的攻擊。

不過隨著硬件算力的飛速提高,比如GPU、專用ASIC芯片以及云計算資源等,密碼破解效率進一步提高,甚至普通人也可利用現(xiàn)成的破解工具和云資源進行密碼破解,攻擊者門檻大幅降低,簡單加鹽也已出現(xiàn)不能有效對抗硬件加速破解的情況。

于是人們開始考慮使用一些新哈希算法,這些算法可以大幅提高攻擊者付出的時間和資源消耗成本,增加密碼破解難度,這就是下面我們要說的慢哈希算法。

2.4 慢哈希算法階段 - Argon2、Bcrypt、Scrypt和PBKDF2

Argon2[2]、Bcrypt[3]、Scrypt[4]和PBKDF2[5]是目前主流的慢哈希算法,它們與SHA256等快速哈希算法的主要差異點如下:

  • 計算速度更慢,需要消耗更多CPU和內(nèi)存資源,從而對抗硬件加速攻擊;
  • 使用更復雜的算法,組合密碼學原語,增加破解難度;
  • 可以配置資源消耗參數(shù),調(diào)整安全強度;
  • 特定優(yōu)化使并行計算困難;
  • 經(jīng)過長時間的密碼學分析,仍然安全可靠。

從這些特點可以知道:這些慢哈希算法更適合密碼哈希的原因是可以大幅增加攻擊者密碼破解的成本,如果這么說大家印象還不夠深刻,我們就來量化對比一下,下面是以SHA256和Scrypt兩個算法為例做的一個簡單的benchmark測試:

// web-app-password-storage/benchmark/benchmark_test.go

package main
  
import (
    "crypto/sha256"
    "testing"

    "golang.org/x/crypto/scrypt"
)

func BenchmarkSHA256(b *testing.B) {
    b.ReportAllocs()
    data := []byte("hello world")
    b.ResetTimer()

    for i := 0; i < b.N; i++ {
        sha256.Sum256(data)
    }
}

func BenchmarkScrypt(b *testing.B) {
    b.ReportAllocs()
    const keyLen = 32
    data := []byte("hello world")
    b.ResetTimer()

    for i := 0; i < b.N; i++ {
        scrypt.Key(data, data, 16384, 8, 1, keyLen)
    }
}

我們看看輸出的benchmark結(jié)果是什么樣的:

$go test -bench .
goos: darwin
goarch: amd64
pkg: demo
... ...
BenchmarkSHA256-8     6097324        195.3 ns/op        0 B/op        0 allocs/op
BenchmarkScrypt-8          26   41812138 ns/op 16781836 B/op       22 allocs/op
PASS
ok   demo 2.533s

我們看到無論是cpu消耗還是內(nèi)存開銷,Scrypt算法都是SHA256的幾個數(shù)量級的倍數(shù)。

加鹽的慢哈希也是目前的主流的用戶密碼存儲方案,那有讀者會問:這四個算法選擇哪個更佳呢?說實話要想對這個四個算法做個全面的對比,需要很強的密碼學專業(yè)知識,這里直接給結(jié)論(當然也是來自網(wǎng)絡(luò)資料):建議使用Scrypt或Argon2系列的算法,它們倆可提供更高的抗ASIC和并行計算能力,Bcrypt由于簡單高效和成熟,目前也仍十分流行。

不過,慢哈希算法在給攻擊者帶來時間和資源成本等困難的同時,也給服務(wù)端正常的身份認證帶來一定的性能開銷,不過大多數(shù)開發(fā)者認為這種設(shè)計取舍是值得的。

下面我們就基于慢哈希算法結(jié)合加鹽,用實例說明一下一個Web應(yīng)用的用戶注冊與登錄過程中,密碼是如何被存儲和用來驗證用戶身份的。

3. 加鹽哈希存儲方案的示例

在這個示例中,我們建立兩個html文件:一個是signup.html,用于模擬用戶注冊;一個是login.html,用于模擬用戶登錄:

// web-app-password-storage/signup.html

<!DOCTYPE html>
<html>
<head>
  <title>注冊</title>
</head>
<body>

<form actinotallow="http://localhost:8080/signup" method="post">

  <label>用戶名:</label>
  <input type="text" name="username"/>

  <label>密碼:</label>
  <input type="password" name="password"/>

  <label>確認密碼:</label>
  <input type="password" name="confirm-password"/>

  <button type="submit">注冊</button>

</form>

</body>
</html>


// web-app-password-storage/login.html

<!DOCTYPE html>
<html>
<head>
  <title>登錄</title>
</head>
<body>

<form actinotallow="http://localhost:8080/login" method="post">

  <label>用戶名:</label>
  <input type="text" name="username"/>

  <label>密碼:</label>
  <input type="password" name="password"/>

  <button type="submit">登錄</button>

</form>

</body>
</html>

接下來,我們來寫這個web應(yīng)用的后端:一個http server:

// web-app-password-storage/server/main.go

package main

import (
 "database/sql"
 "encoding/base64"
 "math/rand"
 "net/http"
 "strings"
 "time"

 "golang.org/x/crypto/scrypt"
 _ "modernc.org/sqlite"
)

var db *sql.DB

func main() {
 // 連接SQLite數(shù)據(jù)庫
 var err error
 db, err = sql.Open("sqlite", "./users.db")
 if err != nil {
  panic(err)
 }
 defer db.Close()

 // 創(chuàng)建用戶表
 sqltable := `
  CREATE TABLE IF NOT EXISTS users (
   id INTEGER PRIMARY KEY AUTOINCREMENT,
   username TEXT,
   hashedpass TEXT
  );
 `
 _, err = db.Exec(sqltable)
 if err != nil {
  panic(err)
 }

 http.HandleFunc("/login", login)
 http.HandleFunc("/signup", signup)
 http.ListenAndServe(":8080", nil)
}

func signup(w http.ResponseWriter, r *http.Request) {
 username := r.FormValue("username")
 password := r.FormValue("password")
 cpassword := r.FormValue("confirm-password")

 if password != cpassword {
  http.Error(w, "password and confirmation password do not match", http.StatusBadRequest)
  return
 }

 // 注冊新用戶
 salt := generateSalt(16)
 hashedPassword := hashPassword(password, salt)
 stmt, err := db.Prepare("INSERT INTO users(username, hashedpass) values(?, ?)")
 if err != nil {
  panic(err)
 }
 _, err = stmt.Exec(username, hashedPassword+":"+salt)
 if err != nil {
  panic(err)
 }
 w.Write([]byte("signup ok!"))
}

func login(w http.ResponseWriter, r *http.Request) {
 username := r.FormValue("username")
 password := r.FormValue("password")

 // 驗證登錄
 storedHashedPassword, salt := getHashedPasswordForUser(db, username)
 hashedLoginPassword := hashPassword(password, salt)
 if hashedLoginPassword == storedHashedPassword {
  w.Write([]byte("Welcome!"))
 } else {
  http.Error(w, "Invalid username or password", http.StatusUnauthorized) // 401
 }
}

// 生成隨機字符串作為鹽值
func generateSalt(n int) string {
 rand.Seed(time.Now().UnixNano())
 letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
 b := make([]rune, n)
 for i := range b {
  b[i] = letters[rand.Intn(len(letters))]
 }
 return string(b)
}

// 對密碼進行bcrypt哈希并返回哈希值與隨機鹽值
func hashPassword(password, salt string) string {
 dk, err := scrypt.Key([]byte(password), []byte(salt), 1<<15, 8, 1, 32)
 if err != nil {
  panic(err)
 }
 return base64.StdEncoding.EncodeToString(dk)
}

// 從數(shù)據(jù)庫獲取用戶哈希后的密碼和鹽值
func getHashedPasswordForUser(db *sql.DB, username string) (string, string) {
 var hashedPass string
 row := db.QueryRow("SELECT hashedpass FROM users WHERE username=?", username)
 if err := row.Scan(&hashedPass); err != nil {
  panic(err)
 }
 split := strings.Split(hashedPass, ":")
 return split[0], split[1]
}

示例的結(jié)構(gòu)比較清晰,這里提供了兩個http handler,一個是signup用于接收用戶注冊請求,一個是login,用于接收處理用戶登錄請求。在注冊請求時,我們生成用戶密碼的帶鹽慢哈希值,與salt一起存入數(shù)據(jù)庫,這里用sqlite代替通用關(guān)系型數(shù)據(jù)庫;在login handler中,我們根據(jù)username讀取數(shù)據(jù)庫中的salt和hashed_password,然后基于請求中的password與salt重新做一遍hash,將得到的結(jié)果與數(shù)據(jù)庫中讀取的hashed_password比較,相同則說明用戶輸入的密碼正確。

Go官方維護的golang.org/x/crypto為我們提供了高質(zhì)量的scrypt包,當然crypto下也有bcrypt、argon2和pbkdf2的實現(xiàn),感興趣的童鞋可以自行研究。

4. 小結(jié)

用戶密碼的安全存儲是保障Web應(yīng)用與用戶數(shù)據(jù)安全的基石。簡單的密碼存儲實踐如明文和弱哈希算法存在巨大隱患,而隨著計算能力提升,任何weak password都可被輕松破解。為有效保護用戶,Web應(yīng)用必須采取更可靠的密碼存儲方案。

本文詳細介紹了從簡單明文、單向哈希到先進的加鹽慢哈希的演進歷程。我們看到,這是一場與不斷增強的攻擊手段進行的應(yīng)對之爭。隨著硬件計算能力、并行與云計算等技術(shù)進步,必須加強密碼存儲機制的強度。當前,結(jié)合隨機鹽、迭代計算的慢哈希可大幅提高破解難度,是推薦的密碼存儲安全實踐。

當然,密碼安全需要持續(xù)關(guān)注新興攻擊手段,并及時采納更強大的算法。這不僅是技術(shù)問題,也需要整個社區(qū)的共同努力,通過提高意識和最佳實踐來保護用戶。

本文示例所涉及的Go源碼可以在這里[6]下載。

5. 參考資料

  • 《API安全實戰(zhàn)》[7] - https://book.douban.com/subject/36039150/
  • 《API安全技術(shù)與實戰(zhàn)》[8] - https://book.douban.com/subject/35429043/
  • 保密:系統(tǒng)如何保證敏感數(shù)據(jù)無法被內(nèi)外部人員竊取濫用?[9] - https://time.geekbang.org/column/article/334293
責任編輯:武曉燕 來源: TonyBai
相關(guān)推薦

2023-11-20 08:02:49

2023-10-26 08:19:34

2011-12-29 10:22:04

iPad用戶體驗設(shè)計指南

2013-06-18 23:26:36

移動應(yīng)用用戶使用成本控制管理移動互聯(lián)網(wǎng)

2020-12-17 09:52:43

WEBWindows ser修改密碼

2023-12-25 08:04:42

2014-03-10 10:01:26

應(yīng)用用戶粘度Mixpanel

2022-04-20 10:25:27

Linux用戶密碼命令

2010-04-08 18:21:56

Oracle用戶密碼

2014-09-19 10:54:47

用戶體驗單頁面

2014-03-14 09:45:18

2013-08-21 09:58:05

2009-03-23 09:03:00

網(wǎng)絡(luò)電話方案應(yīng)用

2020-09-02 11:13:28

密碼網(wǎng)絡(luò)攻擊網(wǎng)絡(luò)安全

2018-05-30 08:31:08

2011-07-27 13:48:30

iPhone 安全

2023-08-02 09:17:04

2009-02-27 17:00:25

2018-03-20 14:59:42

移動應(yīng)用程序大數(shù)據(jù)分析

2010-04-16 13:16:11

OracleJavaP
點贊
收藏

51CTO技術(shù)棧公眾號