使用 Go 验证 /etc/shadow 文件中的散列密码

问题描述 投票:0回答:1
我目前在

/etc/shadow

文件中的密码格式为
$6$IcnB6XpT8xjWC$AI9Rq5hqpEP.Juts/TUbHk/OI7sO/S1AA.ihgBjHN12QmT5p44X5or86PsO9/oPBO4cmo0At4XuMC0yCApo87/
sha512
加密密码)。我需要做的是验证密码(用户提供的密码和当前的散列密码)。我正在 
Go
.
上实现这个

我试图实现这一目标的方法是,获取用户提供的密码,使用与

/etc/shadow

中相同的盐对其进行哈希处理,并检查它们是否相似或不同。我怎样才能生成相同的哈希值并验证密码?

下面是我正在做的粗略代码(更新了 Amadan 对编码的评论)

// 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
设置/更改密码。

shell go encryption password-encryption sha512
1个回答
0
投票
SHA512 是一种快速散列。这对密码不利,因为当每次尝试几乎不花费任何成本时,暴力破解变得非常容易。为了减慢速度,

/etc/shadow

 中的内容不仅仅是一个简单的 SHA512 哈希,而是在哈希算法运行数千次的地方应用了 
key stretching。具体的key stretching算法好像是这个。因此,crypto/sha512
 只做了大约需要的 1/5000(在默认情况下)。

幸运的是,有人已经

did努力工作;你可以看到他们的实现here.

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
作为切割点。

© www.soinside.com 2019 - 2024. All rights reserved.