Как следует безопасно хранить пароли пользователей в базе данных?

Ответ

Пароли пользователей никогда не должны храниться в открытом виде (plaintext). Единственный безопасный способ — хранить их хеши с использованием криптографически стойких, медленных алгоритмов.

Ключевые принципы:

  1. Используйте медленный алгоритм хеширования. Быстрые алгоритмы (MD5, SHA-256) не подходят, так как они уязвимы для атак перебором (brute-force). Рекомендуемые алгоритмы:

    • Bcrypt: Надежный и широко используемый стандарт.
    • Scrypt: Требует больше памяти, что делает его еще более устойчивым к аппаратным атакам.
    • Argon2: Победитель конкурса Password Hashing Competition, считается самым современным и надежным стандартом.

  2. Используйте соль (Salt). Соль — это случайная строка, которая добавляется к паролю перед хешированием. Это предотвращает атаки с использованием "радужных таблиц" (rainbow tables). Современные библиотеки, такие как bcrypt в Go, генерируют и встраивают соль в хеш автоматически.



  3. Настраивайте "стоимость" (Cost Factor). У медленных алгоритмов есть параметр "стоимости" или "количества раундов", который определяет, насколько ресурсоемким будет процесс хеширования. Его нужно подбирать так, чтобы хеширование занимало приемлемое время (например, 100-500 мс) на вашем оборудовании.


Пример реализации на Go с bcrypt:

package main

import (
    "fmt"
    "golang.org/x/crypto/bcrypt"
)

// HashPassword хеширует пароль с использованием bcrypt
func HashPassword(password string) (string, error) {
    // bcrypt.DefaultCost равен 10. Для продакшена рекомендуется 12-14.
    bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
    return string(bytes), err
}

// CheckPasswordHash сравнивает пароль с хешем
func CheckPasswordHash(password, hash string) bool {
    err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
    return err == nil
}

func main() {
    password := "mySecurePa$w0rd"
    hash, _ := HashPassword(password)

    fmt.Println("Password:", password)
    fmt.Println("Hash:    ", hash)

    match := CheckPasswordHash(password, hash)
    fmt.Println("Match:   ", match) // Выведет: true
}