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

exdqitrt  于 2023-04-07  发布在  Shell
关注(0)|答案(1)|浏览(186)

我目前在/etc/shadow文件中的密码格式为$6$IcnB6XpT8xjWC$AI9Rq5hqpEP.Juts/TUbHk/OI7sO/S1AA.ihgBjHN12QmT5p44X5or86PsO9/oPBO4cmo0At4XuMC0yCApo87/sha512加密密码)。我需要做的是验证密码(用户提供的密码和当前散列密码)。我在Go上实现了这一点。
我尝试实现这一点的方法是,获取用户提供的密码,使用与/etc/shadow中相同的salt对其进行哈希,并检查它们是否相似或不同。我如何生成相同的哈希值并验证密码?
下面是我正在做的粗略代码(更新了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
}

注:密码通过passwdchpasswd设置/更改。

2mbi3lxu

2mbi3lxu1#

SHA 512是一个快速的哈希。这对密码来说是不好的,因为当每次尝试几乎没有任何成本时,暴力破解变得非常容易。为了减慢速度,/etc/shadow中的不仅仅是一个简单的SHA 512哈希,而是key stretching应用于哈希算法运行数千次的地方。具体的密钥拉伸算法似乎是this one。因此,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

注意:我相信我在评论中是不正确的。密码哈希本身是由这样的编码编码的,但盐似乎是实际的盐(只是当它随机生成时,使用该编码的字符)。
这个库还支持其他的哈希函数,但是每个函数都需要一个import来注册。这也是你不需要担心的
但是如果你出于某种原因想要隔离盐,那么也要注意从一开始就计算$并不是一个安全的想法,因为它将无法正确处理像$6$rounds=77777$short$WuQyW2YR.hBNpjjRhpYD/ifIw05xdfeEyQoMxIXbkvr0g这样的条目。

相关问题