自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

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

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

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

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

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

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

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

2. 密碼存儲(chǔ)方案的演進(jìn):魔高一尺,道高一丈

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

圖片圖片

下面我們按圖中的演進(jìn)順序,對(duì)各階段的密碼存儲(chǔ)方案逐一說明一下。

2.1 起始階段 - 明文存儲(chǔ)

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

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

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

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

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

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

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

2.3 加鹽哈希階段 - 增加隨機(jī)鹽值

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

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

圖片圖片

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

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

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

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

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

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

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

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

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

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

// 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的幾個(gè)數(shù)量級(jí)的倍數(shù)。

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

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

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

3. 加鹽哈希存儲(chǔ)方案的示例

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

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

<!DOCTYPE html>
<html>
<head>
  <title>注冊(cè)</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>確認(rèn)密碼:</label>
  <input type="password" name="confirm-password"/>

  <button type="submit">注冊(cè)</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>

接下來,我們來寫這個(gè)web應(yīng)用的后端:一個(gè)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ù)庫(kù)
 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
 }

 // 注冊(cè)新用戶
 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")

 // 驗(yàn)證登錄
 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
 }
}

// 生成隨機(jī)字符串作為鹽值
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)
}

// 對(duì)密碼進(jìn)行bcrypt哈希并返回哈希值與隨機(jī)鹽值
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ù)庫(kù)獲取用戶哈希后的密碼和鹽值
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)比較清晰,這里提供了兩個(gè)http handler,一個(gè)是signup用于接收用戶注冊(cè)請(qǐng)求,一個(gè)是login,用于接收處理用戶登錄請(qǐng)求。在注冊(cè)請(qǐng)求時(shí),我們生成用戶密碼的帶鹽慢哈希值,與salt一起存入數(shù)據(jù)庫(kù),這里用sqlite代替通用關(guān)系型數(shù)據(jù)庫(kù);在login handler中,我們根據(jù)username讀取數(shù)據(jù)庫(kù)中的salt和hashed_password,然后基于請(qǐng)求中的password與salt重新做一遍hash,將得到的結(jié)果與數(shù)據(jù)庫(kù)中讀取的hashed_password比較,相同則說明用戶輸入的密碼正確。

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

4. 小結(jié)

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

本文詳細(xì)介紹了從簡(jiǎn)單明文、單向哈希到先進(jìn)的加鹽慢哈希的演進(jìn)歷程。我們看到,這是一場(chǎng)與不斷增強(qiáng)的攻擊手段進(jìn)行的應(yīng)對(duì)之爭(zhēng)。隨著硬件計(jì)算能力、并行與云計(jì)算等技術(shù)進(jìn)步,必須加強(qiáng)密碼存儲(chǔ)機(jī)制的強(qiáng)度。當(dāng)前,結(jié)合隨機(jī)鹽、迭代計(jì)算的慢哈??纱蠓岣咂平怆y度,是推薦的密碼存儲(chǔ)安全實(shí)踐。

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

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

5. 參考資料

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

2023-11-20 08:02:49

2023-10-26 08:19:34

2011-12-29 10:22:04

iPad用戶體驗(yàn)設(shè)計(jì)指南

2013-06-18 23:26:36

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

2023-12-25 08:04:42

2020-12-17 09:52:43

WEBWindows ser修改密碼

2022-04-20 10:25:27

Linux用戶密碼命令

2014-03-10 10:01:26

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

2010-04-08 18:21:56

Oracle用戶密碼

2013-08-21 09:58:05

2014-09-19 10:54:47

用戶體驗(yàn)單頁(yè)面

2014-03-14 09:45:18

2020-09-02 11:13:28

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

2018-05-30 08:31:08

2023-08-02 09:17:04

2011-07-27 13:48:30

iPhone 安全

2009-02-27 17:00:25

2018-03-20 14:59:42

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

2009-03-23 09:03:00

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

2009-07-15 10:47:32

Java多態(tài)
點(diǎn)贊
收藏

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