19

I'm trying to create a JSON Web Token (JWT) using command line utilities on MacOS and hitting a snag with the signing portion.

I was greatly inspired by this gist: https://gist.github.com/indrayam/dd47bf6eef849a57c07016c0036f5207

For my JWT I have Header:

{"alg":"HS256","typ":"JWT"}

Payload:

{"email":"jordan@example.com"}

And my hmac secret is:

bigsecretisveryhardtoguessbysneakypeopleright

Or in base64:

Ymlnc2VjcmV0aXN2ZXJ5aGFyZHRvZ3Vlc3NieXNuZWFreXBlb3BsZXJpZ2h0Cg==

I was using the following site to validate: https://jwt.io/

I find that if I enter all of that into the site using the base64 version of my secret, it generates the following JWT that successfully verifies against the site I'm testing:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvcmRhbkBleGFtcGxlLmNvbSJ9.C3MVjfmnul8dLNIgiv6Dt3jSefD07Y0QtDrOZ5oYSXo

In bash I tried this with:

jwt_header=$(echo -n '{"alg":"HS256","typ":"JWT"}' | base64 | sed s/\+/-/g | sed 's/\//_/g' | sed -E s/=+$//)

payload=$(echo -n '{"email":"jordan@example.com"}' | base64 | sed s/\+/-/g |sed 's/\//_/g' |  sed -E s/=+$//)

hmac_signature=$(echo -n "${jwt_header}.${payload}" | openssl dgst -sha256 -hmac "${key}" -binary | openssl base64 -e -A | sed s/\+/-/g | sed 's/\//_/g' | sed -E s/=+$//)

jwt="${jwt_header}.${payload}.${hmac_signature}"

which produced the following:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpyZWVkQGV4dG9sZS5jb20ifQ.o426f0XDnsUwActVt14Cr3X3IUqPwfv6yaN5nRaZhew

Which is not accepted as valid by the site I'm posting to. So I'm unsure what I am doing wrong in the openssl command that is not getting a valid HS256 signature.

Jordan Reed
  • 526
  • 1
  • 3
  • 15

1 Answers1

18

I was able to recreate the JWT from https://jwt.io/

In your example, there was a hidden newline on the user secret. So in the below, I also add on that newline, purely to recreate the desired output. Also the email address in your payload was not consistent, so for below I have used jordan@example.com.

I took a slightly different approach to the hmac step. I converted the user secret to hex bytes and used that as the key (using the hexkey option for the HMAC).

# Construct the header
jwt_header=$(echo -n '{"alg":"HS256","typ":"JWT"}' | base64 | sed s/\+/-/g | sed 's/\//_/g' | sed -E s/=+$//)

# ans: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

# Construct the payload
payload=$(echo -n '{"email":"jordan@example.com"}' | base64 | sed s/\+/-/g |sed 's/\//_/g' |  sed -E s/=+$//)

# ans: eyJlbWFpbCI6ImpvcmRhbkBleGFtcGxlLmNvbSJ9

# Store the raw user secret (with example of newline at end)
secret=$'bigsecretisveryhardtoguessbysneakypeopleright\n'

# Note, because the secret may have newline, need to reference using form $"" 
echo -n "$secret"

# Convert secret to hex (not base64)
hexsecret=$(echo -n "$secret" | xxd -p | paste -sd "")

# ans: 62696773656372657469737665727968617264746f67756573736279736e65616b7970656f706c6572696768740a

# For debug, also display secret in base64 (for input into https://jwt.io/)
echo -n "$secret" | base64

# ans: Ymlnc2VjcmV0aXN2ZXJ5aGFyZHRvZ3Vlc3NieXNuZWFreXBlb3BsZXJpZ2h0Cg==

# Calculate hmac signature -- note option to pass in the key as hex bytes
hmac_signature=$(echo -n "${jwt_header}.${payload}" |  openssl dgst -sha256 -mac HMAC -macopt hexkey:$hexsecret -binary | base64  | sed s/\+/-/g | sed 's/\//_/g' | sed -E s/=+$//)

# Create the full token
jwt="${jwt_header}.${payload}.${hmac_signature}"

# ans: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvcmRhbkBleGFtcGxlLmNvbSJ9.C3MVjfmnul8dLNIgiv6Dt3jSefD07Y0QtDrOZ5oYSXo
Joe
  • 41,484
  • 20
  • 104
  • 125
Darren Smith
  • 2,261
  • 16
  • 16
  • 6
    For some reason `paste -sd ""` was giving me an error `paste: no delimiters specified` I used this instead: `tr -d '\n'` and worked like a charm. – Jordan Reed Nov 26 '19 at 23:29
  • When I followed these instructions, the signature didn't pass verification when run on `https://jwt.io/`. To compare with their tool, I also tried to choose a different secret (without a \n) and verify using the jwt.io site's signature generation (HS256), but it still didn't match up with the output from this hmac_signature command. – GM Lucid Sep 09 '22 at 07:30
  • @GMLucid ... what I tried was to visit jwt.io, then (1st) enter the plain-text secret into the lower-right-hand box (leave base64 unticked), then (2nd) paste the entire token into the left-hand side. Better to do this test with a secret that doesn't end with a newline. I managed to get the Signature Verified tick-mark. – Darren Smith Sep 09 '22 at 12:44