如何在Golang中生成唯一的随机字母数字标记?

如何在Golang中生成唯一的随机字母数字标记?,go,token,Go,Token,对于RESTful后端API,我想生成唯一的url令牌,用于对用户进行身份验证 注册生成令牌时提供的唯一数据是电子邮件地址。但是在生成令牌并将其发送给用户之后,我不需要解密收到的令牌来获取电子邮件或其他信息。因此,加密可以是单向的 最初,我使用bcrypt来实现这一点: func GenerateToken(email string) string { hash, err := bcrypt.GenerateFromPassword([]byte(email), bcrypt.Defau

对于RESTful后端API,我想生成唯一的url令牌,用于对用户进行身份验证

注册生成令牌时提供的唯一数据是电子邮件地址。但是在生成令牌并将其发送给用户之后,我不需要解密收到的令牌来获取电子邮件或其他信息。因此,加密可以是单向的

最初,我使用bcrypt来实现这一点:

func GenerateToken(email string) string {
    hash, err := bcrypt.GenerateFromPassword([]byte(email), bcrypt.DefaultCost)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Hash to store:", string(hash))

    return string(hash)

}
但由于令牌作为url参数(如
/api/path/to/{token}
)出现,我无法使用,因为它生成的令牌包含以下内容:

“$2a$10$NebCQ8BD7xOa82nkzRGA9OEh./zhbopcuv98vpokbkkk6ztfuhtqlk”

这将破坏路由

因此,我想知道基于Golang中的电子邮件生成一些独特的16-32字符字母数字标记的最佳方法是什么?

选项1:md5哈希bcrypt输出 主要回答他自己问题的OP道具:)

我认为它将满足您所寻找的所有内容(32个字符长度):

$go运行main.go

散列存储:$2a$10$B23cv7lDpbY3iVvfZ7GYE.E4691OW8I7L6CQXKMZ315FBG4JLZUE

代币:90a514ab93e2c32fdd1072154b26a100


下面是我以前的2个建议,我怀疑你会更好地工作,但可能有助于其他人考虑。< / P>
备选案文2:base64 在过去,我使用base64编码使令牌在加密/散列后更具可移植性。下面是一个工作示例:

package main

import (
    "encoding/base64"
    "fmt"
    "log"

    "golang.org/x/crypto/bcrypt"
)

// GenerateToken returns a unique token based on the provided email string
func GenerateToken(email string) string {
    hash, err := bcrypt.GenerateFromPassword([]byte(email), bcrypt.DefaultCost)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Hash to store:", string(hash))

    return base64.StdEncoding.EncodeToString(hash)
}

func main() {
    fmt.Println("token:", GenerateToken("bob@webserver.com"))
}
$go运行main.go

存储哈希:$2a$10$cbVMU9U665VSqpfwrNZWOeU5cIDOe5iBJ8ZVa2yJCTsnk9MEZHvRq

令牌:jdjdjdewjgnivk1vovu2njvwu3fwzndytlpxt2vvwnnjre9lnwlcsjhavmeyeupdvhnuazlnrvpidljx

正如您所看到的,不幸的是,这不能为您提供16-32个字符的长度。如果你同意长度为80,那么这可能适合你


选项3:url路径/查询转义 我还尝试了url.PathEscape和url.QueryEscape来进行彻底的测试。虽然它们与base64示例存在相同的问题(长度,虽然略短),但至少它们“应该”在路径中工作:

// GenerateToken returns a unique token based on the provided email string
func GenerateToken(email string) string {
    hash, err := bcrypt.GenerateFromPassword([]byte(email), bcrypt.DefaultCost)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Hash to store:", string(hash))

    fmt.Println("url.PathEscape:", url.PathEscape(string(hash)))
    fmt.Println("url.QueryEscape:", url.QueryEscape(string(hash)))

    return base64.StdEncoding.EncodeToString(hash)
}
url.PathEscape:$2a$10$wx1UL6%2F7RD6sFq7Bzpgcc.ibFSW114Tf6A521wRh9rVy8dp%2Fa82x2 url.queryscape:%242a%2410%24wx1UL6%2F7RD6sFq7Bzpgcc.ibFSW114Tf6A521wRh9rVy8dp%2Fa82x2

TL;博士 请不要这样做,它不安全!。使用现有的身份验证库或设计更好的方法

解释 身份验证机制可能很难正确实现

由于这些令牌用于身份验证,因此您不仅希望它们是唯一的,还需要它们是不可用的。攻击者不应能够计算用户身份验证令牌

您当前的实现使用用户的电子邮件地址作为bcrypt的秘密输入。bcrypt被设计为一种安全的密码散列算法,因此运行起来计算成本相当高。因此,您可能不希望在每个请求中都这样做

更重要的是,您的代币不安全。如果我知道你的算法,那么我可以通过知道任何人的电子邮件地址为他们生成一个令牌

此外,使用这种方法,您无法撤销或更改受损令牌,因为它是根据用户的电子邮件地址计算的。这也是一个主要的安全问题

您可以采取几种不同的方法,这取决于您是需要无状态身份验证还是需要撤销令牌的能力

此外,作为一种良好做法,身份验证/会话令牌不应放在URL中,因为它们更容易意外泄漏(例如缓存、可用于代理服务器、意外存储在浏览器历史记录中等)

仅标识符? 如果您不使用令牌进行身份验证,那么只需在用户电子邮件地址上使用哈希函数即可。例如,计算用户电子邮件地址的
MD5
,并使用它唯一标识用户。例如:

func GravatarMD5(email string) string {
    h := md5.New()
    h.Write([]byte(strings.ToLower(email)))
    return hex.EncodeToString(h.Sum(nil))
 }

散列冲突的可能性微乎其微(因此不能保证是唯一的),但在实际实现中,这显然不是问题

正如前面提到的,你做错了,这是非常不安全的

  • 使用加密包生成安全令牌。此令牌完全随机,不与任何电子邮件关联
  • func GenerateSecureToken(长度int)字符串{
    b:=make([]字节,长度)
    如果3;,err:=rand.Read(b);err!=nil{
    返回“”
    }
    返回十六进制编码字符串(b)
    }
    
  • 创建将此令牌映射到用户标识符的数据库表,并在API请求期间对其进行验证

  • 可能是@AlexK的副本。区别在于我希望令牌不仅是随机的,而且是唯一的。您可以对任何随机数据使用URL安全的Base64编码。请参阅导出的
    encoding/base64/URLEncoding
    变量。使用它的输出,您可以安全地将其包含在URL中。如果您可以为每个用户生成唯一的编号,那么您可以使用URL安全版本的base 64。难道不可以简单地对该字符串进行URL编码吗?难道任何人都不可能使用该算法为其他人生成令牌吗?将结果输入MD5以缩短生成的令牌,如:
    hasher:=MD5.New();写入([]字节(令牌));return hex.EncodeToString(hasher.Sum(nil))
    ?好主意,@Karlom-我会试试alsoconsider添加时间跨度有效性的方法当然这是安全的-创建唯一的电子邮件验证令牌只是一种昂贵的方法,不是吗?它不是每次请求都发送的身份验证承载令牌。最初的问题是如何生成令牌来验证用户,而不是创建电子邮件验证令牌。但这非常昂贵,因为您需要存储所有数据……为什么这非常不安全?可预测性?
    func GravatarMD5(email string) string {
        h := md5.New()
        h.Write([]byte(strings.ToLower(email)))
        return hex.EncodeToString(h.Sum(nil))
     }