sw0ngle
sw0ngle

Reputation: 23

Getting incorrect HMAC SHA256 signature compared to example JWT

I am attempting at following the [RFC for JSON Web Signatures]1 but getting some problems when following the example.

I get everything up until the end, where I am unable to generate the same signature. Here is the example Python 3.8 code:

import hmac
import hashlib
import base64
signing_input = b"eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ"
key = b"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"
signature = hmac.digest(key, signing_input, digest=hashlib.sha256)

print(base64.urlsafe_b64encode(signature))
# Output: b'ZekyXWlxvuCN9H8cuDrZfaRa3pMJhHpv6QKFdUqXbLc='
# Expected: b'dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk'

Additionally I have tried a few online tools that handle HMAC-SHA256 but I am getting the same output as my Python script provides. Any ideas on where I'm going wrong? [1]: https://www.rfc-editor.org/rfc/rfc7515#appendix-A.1

Upvotes: 2

Views: 1548

Answers (1)

Miles Budnek
Miles Budnek

Reputation: 30494

You're using the wrong key. The RFC shows the key in the JSON Web Key format using the JSON Web Algorithm "oct". This means that the key is a base64url encoded sequence of bytes. You need to decode it before using it if you want your results to match.

Note that python's urlsafe_b64decode and urlsafe_b64encode do not exactly implement the base64url encoding used by JWT and friends. The python functions expect/produce padding characters, which the base64url encoding used by JWT specifies should be removed.

Putting this all together:

import hmac
import hashlib
import base64

signing_input = b"eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ"
key = b"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"

# Decode the key.  Pad it with '=' characters to a length divisible by 4 
# as expected by urlsafe_b64decode
if len(key) % 4 == 2:
    key += b'=='
elif len(key) % 4 == 3:
    key += b'='

key = base64.urlsafe_b64decode(key)

signature = hmac.digest(key, signing_input, digest=hashlib.sha256)
signature = base64.urlsafe_b64encode(signature)

# Strip off any '=' characters urlsafe_b64encode added to pad the key to
# a length divisible by 4
signature = signature.rstrip(b'=')
print(signature)
# Prints: b'dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk'

Upvotes: 4

Related Questions