For a RESTful backend API, I want to generate unique url tokens to be used to authenticate users.
The unique data provided at registration to generate tokens are email addresses. But after generating tokens and sending that to the users, I don't need to decrypt received tokens to get email or other information. So the encryption can be one-way.
Initially I used bcrypt to do so:
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)
}
But since the tokens come as a url parameter (like /api/path/to/{token}
) I can not use bcrypt because it generates tokens containing /
like this:
"$2a$10$NebCQ8BD7xOa82nkzRGA9OEh./zhBOPcuV98vpOKBKK6ZTFuHtqlK"
which will break the routing.
So I'm wondering what is the best way to generate some unique 16-32 character alphanumeric tokens based on emails in Golang?
Option 1: md5 hash the bcrypt output
Props to OP for mostly answering his own question :)
I think it'll satisfy everything you're looking for (32-character length):
Below are 2 of my previous suggestions that I doubt will work better for you, but could be helpful for others to consider.
Option 2: base64
In the past, I've used base64 encoding to make tokens more portable after encryption/hashing. Here's a working example:
As you can see, this unfortunately doesn't provide you with a 16-32 character length. If you're okay with the length being 80 long, then this might work for you.
Option 3: url path/query escapes
I also tried url.PathEscape and url.QueryEscape to be thorough. While they have the same problem as the base64 example (length, though a bit shorter), at least they "should" work in the path:
TL;DR
Please don't do this, it's not secure!. Use an existing authentication library or design a better approach.
Explaination
Authentication mechanisms can be tricky to implement properly.
Since these tokens are for authentication purposes, you don't just want them to be unique, you also need them to be unguessable. An attacker should not be able to calculate a users authentication token.
Your current implementation uses the users email address as the secret input for bcrypt. bcrypt was designed as a secure password hashing algorithm and is hence quite computationally expensive to run. Hence you probably don't want to be doing this in every request.
More importantly, your tokens are not secure. If I know your algorithm then I can generate a token for anyone by simply knowing their email address!
Also, with this approach, you cannot revoke or change a compromised token as it is calculated from the users email address. This is also a major security concern.
There are a few different approaches you could take, depending on whether you need stateless authentication or the ability to revoke tokens.
Additionally, as a matter as good practice, authentication/session tokens should not be placed in a URL as it is much easier for these to accidentally leak (e.g. cached, available to proxy servers, accidentally stored in browser history etc).
Identifiers Only?
If you aren't using your tokens for authentication then simply use a hash function on a users email address. For example, Gravatar that calculate the
MD5
of a the users lowercase email address and use this to uniquely identify a user. For example:There is an infinitesimal chance of a hash collision (and hence not guaranteed to be unique) but obviously in a real life implementation this hasn't been an issue.
As it was already mentioned you are doing it wrong and this is super insecure.