Сверьте пароль с хешированным паролем в файле /etc/shadow с помощью Go

В настоящее время в файле /etc/shadow у меня есть пароль в формате, подобном $6$IcnB6XpT8xjWC$AI9Rq5hqpEP.Juts/TUbHk/OI7sO/S1AA.ihgBjHN12QmT5p44X5or86PsO9/oPBO4cmo0At4XuMC0yCApo87/ (зашифрованный пароль sha512). Что мне нужно сделать, так это подтвердить пароль (предоставленный пользователем пароль и текущий хешированный пароль). Я реализую это на Go.

Как я пытаюсь добиться этого, получаю предоставленный пользователем пароль, хеширую его с той же солью, что и в /etc/shadow, и проверяю, похожи они или разные. Как я могу сгенерировать одно и то же значение хеш-функции и проверить пароль?

Ниже приведен примерный код того, что я делаю (обновлен комментарием Амадана о кодировании).

// this is what is stored on /etc/shadow - a hashed string of "test"
myPwd := "$6$IcnB6XpT8xjWC$AI9Rq5hqpEP.Juts/TUbHk/OI7sO/S1AA.ihgBjHN12QmT5p44X5or86PsO9/oPBO4cmo0At4XuMC0yCApo87/"
// getting the salt
salt := strings.Split(myPwd, "$")[2]


encoding := base64.NewEncoding("./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz").WithPadding(base64.NoPadding)
decodedSalt, err := encoding.DecodeString(salt)

// user provided password
myNewPwd := "test"

newHashedPwd, err := hashPwd512(string(myNewPwd), string(decodedSalt))

// comparision
if (newHashedPwd == myPwd) {
   // password is same, validate it
}

// What I'm expecting from this method is that for the same password stored in the /etc/shadow, 
// and the same salt, it should return (like the one in /etc/shadow file)
// AI9Rq5hqpEP.Juts/TUbHk/OI7sO/S1AA.ihgBjHN12QmT5p44X5or86PsO9/oPBO4cmo0At4XuMC0yCApo87/

func hashPwd512(pwd string, salt string) (string, error) {
    hash := sha512.New()
    hash.Write([]byte(salt))
    hash.Write([]byte(pwd))

    encoding := base64.NewEncoding("./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz").WithPadding(base64.NoPadding)

    hashedPwd := encoding.EncodeToString(hash.Sum(nil))
    return hashedPwd, nil
}

Примечание. Пароль устанавливается/меняется с помощью passwd или chpasswd.

Хотя я не решил вашу проблему полностью, есть несколько вопросов, с которых стоит начать. 1) Ваш код каждый раз возвращает один и тот же результат. 2) /etc/shadow не содержит соли, содержит закодированную соль. Определите encoding := base64.NewEncoding("./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZab‌​cdefghijklmnopqrstuv‌​wxyz").WithPadding(b‌​ase64.NoPadding), а затем decodedSalt, err := encoding.DecodeString(salt), чтобы получить настоящую соль; аналогично, пароль не имеет шестнадцатеричной кодировки, вместо этого используйте ту же кодировку, используя encoding.EncodeToString(hash.Sum(nil)). 3) режима и соли не будет в newHashedPwd.

Amadan 31.03.2023 01:18

@Amadan - Спасибо за помощь. Я получаю ошибку illegal base64 data at input byte 12 сейчас. Это наверное с символами в хешированном пароле? . Кроме того, я получаю другую хэш-строку. Любые дополнительные подсказки? P.S. - Я обновил код на основе вашего предложения.

Subhash Prajapati 31.03.2023 02:51
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
2
215
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

SHA512 — это быстрый хэш. Это плохо для паролей, потому что грубая сила становится очень легкой, когда каждая попытка почти ничего не стоит. Чтобы замедлить его, то, что находится в /etc/shadow, представляет собой не просто хэш SHA512, а скорее растяжение ключа , где алгоритм хеширования запускается тысячи раз. Конкретный алгоритм растяжения клавиш, кажется, вот этот. Таким образом, crypto/sha512 делает только 1/5000 того, что необходимо (в случае по умолчанию).

К счастью, есть люди, которые уже проделали тяжелую работу; вы можете увидеть их реализацию здесь.

package main

import (
    "fmt"
    "strings"

    "github.com/GehirnInc/crypt"
    _ "github.com/GehirnInc/crypt/sha512_crypt"
)

func main() {
    saltedPass := "$6$IcnB6XpT8xjWC$AI9Rq5hqpEP.Juts/TUbHk/OI7sO/S1AA.ihgBjHN12QmT5p44X5or86PsO9/oPBO4cmo0At4XuMC0yCApo87/"
    fmt.Println("Original: ", saltedPass)

    // Make new hash from scratch
    plainPass := "test"
    crypt := crypt.SHA512.New()
    newSaltedPass, err := crypt.Generate([]byte(plainPass), []byte(saltedPass))
    if err != nil {
        panic(err)
    }
    fmt.Println("Generated:", newSaltedPass)

    // Verify a password (correct)
    err = crypt.Verify(saltedPass, []byte(plainPass))
    fmt.Println("Verification error (correct password):  ", err)

    // Verify a password (incorrect)
    badPass := "fail"
    err = crypt.Verify(saltedPass, []byte(badPass))
    fmt.Println("Verification error (incorrect password):", err)
}

Поскольку вам нужна только проверка, достаточно ярлыка Verify (он делает Generate за кулисами для вас):

err = crypt.Verify(saltedPass, []byte(plainPass))
if err == nil {
    fmt.Println("Fly, you fools!")
} else {
    fmt.Println("You shall not pass!")
}

Выход:

Original:  $6$IcnB6XpT8xjWC$AI9Rq5hqpEP.Juts/TUbHk/OI7sO/S1AA.ihgBjHN12QmT5p44X5or86PsO9/oPBO4cmo0At4XuMC0yCApo87/
Generated: $6$IcnB6XpT8xjWC$AI9Rq5hqpEP.Juts/TUbHk/OI7sO/S1AA.ihgBjHN12QmT5p44X5or86PsO9/oPBO4cmo0At4XuMC0yCApo87/
Verification error (correct password):   <nil>
Verification error (incorrect password): hashed value is not the hash of the given password

ПРИМЕЧАНИЕ. Я считаю, что был неправ в комментариях. Сам хэш пароля закодирован такой кодировкой, но соль, похоже, и есть настоящая соль (просто при ее генерации случайным образом используются символы из этой кодировки).

Библиотека также поддерживает другие функции хеширования, но для регистрации каждой из них требуется импорт. Вы также можете видеть, что я не стал выделять соль из saltedPass; это также то, о чем вам не нужно беспокоиться.

Но если вы по какой-то причине хотите изолировать соль, то учтите также, что считать $ с самого начала небезопасно, так как, например, такие записи, как $6$rounds=77777$short$WuQyW2YR.hBNpjjRhpYD/ifIw05xdfeEyQoMxIXbkvr0g, не будут правильно обрабатываться. Вместо этого используйте strings.LastIndex(saltedPass, "$") + 1 в качестве точки разреза.

Другие вопросы по теме