Q:

Manually calculating JWT signature never outputs the real signature

I've been reading a lot of questions on stackOverflow and jwt's docs.
Right now from what I understand this is what I should do to calculate a token:
header =
{
  "alg": "HS256",
  "typ": "JWT"
}

payload =
{
  "sub": "1234567890",
  "name": "JohnDoe",
  "iat": 1516239022
}
secret = "test123"

Remove unnecessary spaces and breaklines from header and payload and then encoding both to base64url.

base64urlEncode(header)
// output: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
base64urlEncode(payload)
// output: eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG5Eb2UiLCJpYXQiOjE1MTYyMzkwMjJ9

Same output as on jwt.io, perfect.

Calculate the sha256 hmac using "test123" as secret.

sha256_hmac("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG5Eb2UiLCJpYXQiOjE1MTYyMzkwMjJ9", "test123)
// output: 3b59324118bcd59a5435194120c2cfcb7cf295f25a79149b79145696329ffb95

Convert the hash to string and then base64url encode it.

I use hex to string converter for this part, then I encode it using base64urlEncode and I get the following output:
O1kyQRjCvMOVwppUNRlBIMOCw4_Di3zDssKVw7JaeRTCm3kUVsKWMsKfw7vClQ
Output from jwt.io
O1kyQRi81ZpUNRlBIMLPy3zylfJaeRSbeRRWljKf-5U
But if I go to this page From Hex, to Base64 I get the correct output:
O1kyQRi81ZpUNRlBIMLPy3zylfJaeRSbeRRWljKf-5U
So what am I doing wrong? Why converting the hex to string and then Encoding it outputs a different result?
In case the online hex to string conversion is wrong, how can I convert this hex to string (so then I can encode it) on c++ without using any libray. Am I correct if I convert each byte (2 characters because hex = 4 bits) to ASCII character and then encode?
Thanks in advance.

A:

Your hmac step is correct, does have the right output bytes (as commented).  The conversion problem you have is caused by non-display chars in the temporary string (the raw bytes were not correctly copied pasted from first webpage to second).
To reproduce the exact output at each stage, you can use these commands below.
In terms of C++, you should try to operate on the raw bytes, rather than on the hex string.  Take the raw bytes and run them through a base64 URL-safe encoder.  Or, as in the example below, take the raw bytes, run them through a plain base64 encoder, and then fix the generated base64 string to be URL safe.

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 '{"sub":"1234567890","name":"JohnDoe","iat":1516239022}' | base64 | sed s/\+/-/g |sed 's/\//_/g' |  sed -E s/=+$//)

# ans: eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG5Eb2UiLCJpYXQiOjE1MTYyMzkwMjJ9

Raw password

secret="test123"

Convert secret to hex (not base64)

hexsecret=$(echo -n "$secret" | xxd -p | tr -d '\n')

# ans: 74657374313233

Perform hmac, and capture the raw bytes (caution, this is a non printable string)

hmac_signature_rawbytes=$(echo -n "${jwt_header}.${payload}" |  openssl dgst -sha256 -mac HMAC -macopt hexkey:$hexsecret -binary)

Dump the raw bytes as hex, for illustration only (matches OP output)

echo -n ${hmac_signature_rawbytes} | xxd -p | tr -d '\n'

#ans: 3b59324118bcd59a5435194120c2cfcb7cf295f25a79149b79145696329ffb95

For JWT signature, convert raw bytes to base64uri encoding

hmac_signature=$(echo -n ${hmac_signature_rawbytes} | base64  | sed s/\+/-/g | sed 's/\//_/g' | sed -E s/=+$//)

#ans: O1kyQRi81ZpUNRlBIMLPy3zylfJaeRSbeRRWljKf-5U

Create the full token

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

# ans: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG5Eb2UiLCJpYXQiOjE1MTYyMzkwMjJ9.O1kyQRi81ZpUNRlBIMLPy3zylfJaeRSbeRRWljKf-5U

