This page looks best with JavaScript enabled

Incomplete Data When Using Base64 to Decode JWT Playload

 ·  ☕ 2 min read

When Base64-decoding a JWT, I found that the JSON data was incomplete. This article mainly introduces the relevant knowledge and solves this problem.

1. Introduction to JWT

JWT passes authentication by setting Authorization: Bearer <token> in the Header.

A JWT Token is a Base64-encoded string joined by dots, something like Header.Payload.Signature, and consists of three parts:

  • Header, which defines the Token type and the encryption algorithm
1
2
3
4
{
  "alg": "HS256",
  "typ": "JWT"
}
  • Payload, the payload information, usually iss (issuer), exp (expiration time), sub (subject), aud (audience), iat (issued at), etc.
1
2
3
4
5
{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}
  • Signature, which signs the Base64-encoded Header and Playload to prevent the information from being tampered with.
1
2
3
4
5
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  your-256-bit-secret
)

jwt.io provides an online tool for parsing Tokens.

2. Base64 Decoding

“encoding/base64” provides four encoding and decoding methods:

  • StdEncoding, standard encoding; when the length is not a multiple of 3, pad with =
  • URLEncoding, URL-safe encoding; replaces the special characters +/ in the string with -_
  • RawStdEncoding, standard encoding; no = padding at the end
  • RawURLEncoding, URL-safe encoding; no = padding at the end

Below, let’s look at the differences between them through a concrete example.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package main

import (
    "encoding/base64"
    "fmt"
)

func coding(msg []byte){
    fmt.Println("Input :", string(msg))

    encoded := base64.StdEncoding.EncodeToString(msg)
    fmt.Println("StdEncoding :", encoded)
    decoded, _ := base64.StdEncoding.DecodeString(encoded)
    fmt.Println("StdEncoding :", string(decoded))

    encoded = base64.URLEncoding.EncodeToString(msg)
    fmt.Println("URLEncoding :", encoded)
    decoded, _ = base64.URLEncoding.DecodeString(encoded)
    fmt.Println("URLEncoding :", string(decoded))

    encoded = base64.RawStdEncoding.EncodeToString(msg)
    fmt.Println("RawStdEncoding :", encoded)
    decoded, _ = base64.RawStdEncoding.DecodeString(encoded)
    fmt.Println("RawStdEncoding :", string(decoded))

    encoded = base64.RawURLEncoding.EncodeToString(msg)
    fmt.Println("RawURLEncoding :", encoded)
    decoded, _ = base64.RawURLEncoding.DecodeString(encoded)
    fmt.Println("RawURLEncoding :", string(decoded))
}

func main() {
    // 补齐
    coding([]byte("https://www.chenshaowen.com/"))
    // URL Safe 编码
    coding([]byte("abc123!?$*&()'-=@~"))

}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
Input : https://www.chenshaowen.com/
StdEncoding : aHR0cHM6Ly93d3cuY2hlbnNoYW93ZW4uY29tLw==
StdEncoding : https://www.chenshaowen.com/
URLEncoding : aHR0cHM6Ly93d3cuY2hlbnNoYW93ZW4uY29tLw==
URLEncoding : https://www.chenshaowen.com/
RawStdEncoding : aHR0cHM6Ly93d3cuY2hlbnNoYW93ZW4uY29tLw
RawStdEncoding : https://www.chenshaowen.com/
RawURLEncoding : aHR0cHM6Ly93d3cuY2hlbnNoYW93ZW4uY29tLw
RawURLEncoding : https://www.chenshaowen.com/
Input : abc123!?$*&()'-=@~
StdEncoding : YWJjMTIzIT8kKiYoKSctPUB+
StdEncoding : abc123!?$*&()'-=@~
URLEncoding : YWJjMTIzIT8kKiYoKSctPUB-
URLEncoding : abc123!?$*&()'-=@~
RawStdEncoding : YWJjMTIzIT8kKiYoKSctPUB+
RawStdEncoding : abc123!?$*&()'-=@~
RawURLEncoding : YWJjMTIzIT8kKiYoKSctPUB-
RawURLEncoding : abc123!?$*&()'-=@~

Judging from the output:

  1. Stdxxx variants apply padding to the Base64 encoding
  2. URLxxx variants transcode the Base64 encoding

Base64 is a public, standard encoding rule, but different library implementations expose different interfaces; only by using the right interface can you get the expected result.

3. The JWT Playload Is Missing a Part

The following code extracts the Playload part and decodes it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package main

import (
    "encoding/base64"
    "fmt"
)

func main() {
    decoded := "eyJ1c2VybmFtZSI6ImFkbWluIiwidWlkIjoiYjhiZTZlZGQtMmM5Mi00NTM1LTliMmEtZGY2MzI2NDc0NDU4IiwiaWF0IjoxNTkxMzU0MDEwLCJpc3MiOiJrdWJlc3BoZXJlIiwibmJmIjoxNTkxMzU0MDEwfQ"
    encoded, _ := base64.StdEncoding.DecodeString(decoded)
    fmt.Println(string(encoded))
}

The result is:

1
{"username":"admin","uid":"b8be6edd-2c92-4535-9b2a-df6326474458","iat":1591354010,"iss":"","nbf":1591354010

Notice that this is not a complete Json object. In the dgrijalva/jwt-go library, you can see the implementation of the EncodeSegment function:

1
2
3
4
// Encode JWT specific base64url encoding with padding stripped
func EncodeSegment(seg []byte) string {
	return strings.TrimRight(base64.URLEncoding.EncodeToString(seg), "=")
}

Clearly, dgrijalva/jwt-go uses RawURLEncoding for encoding.

After adjusting, run the following code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package main

import (
    "encoding/base64"
    "fmt"
)

func main() {
    decoded := "eyJ1c2VybmFtZSI6ImFkbWluIiwidWlkIjoiYjhiZTZlZGQtMmM5Mi00NTM1LTliMmEtZGY2MzI2NDc0NDU4IiwiaWF0IjoxNTkxMzU0MDEwLCJpc3MiOiJrdWJlc3BoZXJlIiwibmJmIjoxNTkxMzU0MDEwfQ"
    encoded, _ := base64.RawURLEncoding.DecodeString(decoded)
    fmt.Println(string(encoded))
}

The correct result is:

1
{"username":"admin","uid":"b8be6edd-2c92-4535-9b2a-df6326474458","iat":1591354010,"iss":"","nbf":1591354010}

Another approach is to use the built-in parser in dgrijalva/jwt-go and provide the complete JWT Token for parsing. Take a look at the code below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
    "github.com/dgrijalva/jwt-go"
    "fmt"
)

func main() {
    decoded := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwidWlkIjoiYjhiZTZlZGQtMmM5Mi00NTM1LTliMmEtZGY2MzI2NDc0NDU4IiwiaWF0IjoxNTkxMzU0MDEwLCJpc3MiOiJrdWJlc3BoZXJlIiwibmJmIjoxNTkxMzU0MDEwfQ.psKkj8vYWm9Crf9jnbB_PNestLNksaS9vuMvQI3C-dU"
    type Claims struct {
        Username string `json:"username"`
        UID      string `json:"uid"`
        jwt.StandardClaims
    }

    claim := Claims{}
    parser := jwt.Parser{}
    parser.ParseUnverified(decoded, &claim)
    fmt.Println(claim.Username)
}

The expected result is:

admin

4. References


微信公众号
WRITTEN BY
微信公众号