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





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 в качестве точки разреза.
Хотя я не решил вашу проблему полностью, есть несколько вопросов, с которых стоит начать. 1) Ваш код каждый раз возвращает один и тот же результат. 2)
/etc/shadowне содержит соли, содержит закодированную соль. Определитеencoding := base64.NewEncoding("./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz").WithPadding(base64.NoPadding), а затемdecodedSalt, err := encoding.DecodeString(salt), чтобы получить настоящую соль; аналогично, пароль не имеет шестнадцатеричной кодировки, вместо этого используйте ту же кодировку, используяencoding.EncodeToString(hash.Sum(nil)). 3) режима и соли не будет вnewHashedPwd.