使用 Go 生成适合 `/etc/shadow` 的散列密码字符串

Using Go to generate hashed password strings suitable for `/etc/shadow`

我面前的任务是获取用户提供的纯文本字符串(即密码),并将其转换为可以插入 /etc/shadow 中的东西作为散列密码字符串,这样然后用户可以使用最初提供的密码登录以生成哈希。这是我们在系统管理员世界中一遍又一遍地(重新)解决的非常普遍的事情。有无数的命令行实用程序可以执行此操作。

然而,在这种特定情况下,我的限制是我需要一个可以在多个上下文中使用的纯 Go 解决方案(cli 工具、api 等)。我的第一次尝试只是使用 bcrypt 库。乍一看,它似乎具备我需要的属性。它是纯粹的 Go,使用起来非常简单(奖励),并且它生成的输出看起来和我想要的一样……但它没有用。使用此库产生的输出不能(例如)作为用户密码粘贴到 /etc/shadow 中,然后用户能够使用原始密码成功登录。

我只是想知道是否有人 运行 在他们的日常工作中遇到这个需求并解决了它,谁愿意分享他们的经验和(gasp ) 代码?我主要想知道是否有人会推荐一个针对这种近似用例的库? (我的 google fu 可能只有 'rona')。

我在这里分享这个是因为我尝试的第一件事没有奏效,而且自从我公开询问后,我觉得也公开分享解决方案是公平的。这是 a 解决方案,针对我的情况。我并不是在宣称它是 解决方案,或者没有 更好 解决方案(请 post 那些!)。对于我的具体情况,这是我想出的……[编辑:请注意,在我撰写本文时,用户 Marc 在上面的评论中建议查看该库。谢谢你,Marc,我的 fu 明显感觉好多了]

如前所述,我的第一次尝试只是使用 bcrypt 库,但没有成功。仔细检查后,我发现 bcrypt 仅以一种格式输出(即使用一种哈希算法),这显然与 Linux 中的密码系统不兼容(至少不是我的特定发行版)。因此,虽然输出大体看起来应该是这样,但细节是:

bcrypt 给我这样的东西:$sdfUILYhjd.HEdhjsdfgjhfdgjh.HEWjhndcjv

在第一个字段 (</code>) 中,我的 Linux 发行版显然不支持类型 <code> (?)(广泛支持的是 , , (即md5、sha256 和 sha512))。我似乎没有办法在 bcrypt go 库中指定不同的算法。于是,我又投了一下其他approaches/solutions。我到达的是:

package main

import (
    "fmt"
    "math/rand"
    "os"
    "time"
    "github.com/tredoe/osutil/user/crypt/sha512_crypt"
)

func encryptPassword(userPassword string) string {
    // Generate a random string for use in the salt
    const charset = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
    seededRand := rand.New(rand.NewSource(time.Now().UnixNano()))
    s := make([]byte, 8)
    for i := range s {
        s[i] = charset[seededRand.Intn(len(charset))]
    }
    salt := []byte(fmt.Sprintf("$%s", s))
    // use salt to hash user-supplied password
    c := sha512_crypt.New()
    hash, err := c.Generate([]byte(userPassword), salt)
    if err != nil {
        fmt.Printf("error hashing user's supplied password: %s\n", err)
        os.Exit(1)
    }
    return string(hash)
}

这个函数 returns 看起来像这样的字符串:

$tZeuYPZ3mj70WOprJj5ytFFzC8gUFYk7eymQvaR4lDg5C0WzwBAMupRAan7BaC6EAbL9Eiyi2GZR6PQIQQa.y6kZLqh6

您可以直接将其粘贴到 /etc/shadow 或作为散列密码(kickstartcloud-init 等)提供给安装程序,然后继续您的工作。在我的例子中,我正在为一个应用程序编写一个库函数,我可以从命令行实用程序或 api 服务调用该应用程序,后者又将该散列字符串作为参数提供给 cloud-init。

HTH 以后可能会发现这个的其他人。