我目前在/etc/shadow文件中拥有的是类似$6$IcnB6XpT8xjWC$AI9Rq5hqpEP.Juts/TUbHk/OI7sO/S1AA.ihgBjHN12QmT5p44X5or86PsO9/oPBO4cmo0At4XuMC0yCApo87/(sha512加密密码)格式的密码.我需要做的是验证密码(用户提供的密码和当前哈希密码).我要在Go上实现这一点.

我试图实现这一点的方法是,获得用户提供的密码,使用与/etc/shadow中相同的盐对其进行散列,并判断它们是否相似或不同.如何生成相同的哈希值并验证密码?

下面是我正在做的粗略代码(更新了Amadan关于编码的 comments )

// 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.

推荐答案

SHA512是一种快速哈希.这对密码不好,因为当每次try 几乎不花费任何成本时,暴力破解变得非常容易.为了减慢速度,/etc/shadow中的内容不仅仅是一个简单的SHA512散列,而是在散列算法运行数千次的地方应用了key stretching.具体的按键拉伸算法似乎是this one.因此,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

注:我相信我的 comments 是错误的.密码散列本身是通过这种编码进行编码的,但SALT似乎是实际的SALT(只是当它是随机生成时,使用的是该编码中的字符).

该库还支持其他散列函数,但您确实需要 for each 散列函数导入才能注册它.你还可以看到,我没有费心把盐从saltedPass中分离出来;这也是你不需要担心的.

但如果出于某种原因,您确实想要隔离盐,那么还要注意,从一开始计算$并不是一个安全的 idea ,因为它将无法正确处理像$6$rounds=77777$short$WuQyW2YR.hBNpjjRhpYD/ifIw05xdfeEyQoMxIXbkvr0g这样的条目.取而代之的是使用strings.LastIndex(saltedPass, "$") + 1作为切入点.

Go相关问答推荐

在Golang中,@LATEST和@UPGRADE特殊查询有什么不同?

为什么(编码器).EncodeElement忽略";,innerxml";标记?

golang 的持久隐蔽服务

使用!NOT运算符的Golang文本/模板多个条件

在Go中旋转矩阵

切片的下限和上限

如何将任何类型的数据值传递到 Golang 中的 GRPC Protobuf struct ?

在两个单独的速率受限端点之间同步请求

Neptune 在连接到启用 IAM 的 Neptune 实例时抛出握手错误错误

如何使用管理员权限启动 powershell 进程并重定向标准输入 (os.exec)

如何使用 fyne Go 使用 canvas.NewText() 使文本可滚动

从 Makefile 运行时权限被拒绝

对所有标志进行 ORing 的简短方法

Golang Getrlimit 返回与 ulimit 不同的值

try 运行 docker-compose up -d 时出现错误

在 go 中将运行命令的标准输出发送到其标准输入

退格字符在围棋操场中不起作用

不能使用 *T 类型的变量作为参数类型

golangci-lint 的 GitHub 操作失败,无法加载 fmt

Golang 中的无实体函数