jwt-secret
This is a utility for internal use to generate a base64-encoded cryptographically secure JWT signing secret using crypto/rand.
Usage
Run go run github.com/DeanPDX/jwt-secret@latest
and you will see the following:
Which signing method are you using?
1. HS256
2. HS384
3. HS512
:
Enter a value based on which signing algorithm you're using and you will get an appropriate key.
Why?
See this issue for the genesis of this utility. Specifically the quote from RFC 7518 about key size:
Hash-based Message Authentication Codes (HMACs) enable one to use a
secret plus a cryptographic hash function to generate a MAC. This
can be used to demonstrate that whoever generated the MAC was in
possession of the MAC key. The algorithm for implementing and
validating HMACs is provided in RFC 2104 [RFC2104].
A key of the same size as the hash output (for instance, 256 bits for
"HS256") or larger MUST be used with this algorithm. (This
requirement is based on Section 5.3.4 (Security Effect of the HMAC
Key) of NIST SP 800-117 [NIST.800-107], which states that the
effective security strength is the minimum of the security strength
of the key and two times the size of the internal hash value.)
Adding to this: optimum key length is equal to block size (not greater). From The Wikipedia Page pseudocode:
// Keys longer than blockSize are shortened by hashing them
if (length(key) > blockSize) then
key = hash(key)
// Keys shorter than blockSize are padded to blockSize by padding with zeros on the right
if (length(key) < blockSize) then
return Pad(key, blockSize) // Pad key with zeros to make it blockSize bytes long
So, by matching signing secret to block size, we avoid padding with non-random numbers (zeroes) and we avoid hashing our key.
Why Base64 Encoded?
From the golang-jwt/jwt FAQ:
We often get asked why the HMAC signing method only supports []byte and not string. This is intentionally and there are different reasons for doing so. First, we aim to use the key type that the underlying cryptographic operation in the Go library uses (this also applies to the other signing methods). In case of HMAC, this is hmac.New and it uses []byte as the type to represent a key.
Second, using string as a key type to represent a symmetric key can lead to unwanted situations. It gives the impression that this is something 'human readable' (like a password), but it is not. A symmetric key should contain as much entropy as possible and therefore include characters from the whole character set (even 'unreadable' ones) and ideally be generated by a cryptographic random source, such as rand.Read. Signing tokens with a cryptographically weak key will compromise the security of the tokens and in effect everything that depends on it, e.g., user authentication.
If you have trouble handling a []byte key in our setup, e.g., because you are reading it from your environment variables on your cluster or similar, you can always use base64 encoding to have the key as a "string" type outside of your program and then use base64.Encoding.DecodeString to decode the base64 string into the []byte slice that the signing method needs.
In many projects, I am exposing my signing secrets to my APIs via secret managers as environment variables. So it makes sense to have them be base64-encoded strings. A quick cheatsheet on how to get a base64-encoded string as a byte array from an environment variable:
signingSecret, err := base64.StdEncoding.DecodeString(os.Getenv("MY_SIGNING_SECRET"))
if err != nil {
// Handle error
}
// signingSecret is ready to use