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