Beginnerrrrrr
Beginnerrrrrr

Reputation: 595

How to use Base64Url encode json object like JWT header and payload by Swift

I try to create a JWT system.

But I'm facing a problem when I Base64Url encode my header and payload json objects. My output base64UrlString is different from the https://jwt.io/ output string.

Why do I get two different output strings?

And if I paste my output string in https://jwt.io/ encoded area I get the error "Invalid Signature".

If I'm wrong, please help me to fix my code.

jwt.io output string:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJUaXRsZSI6Ik5pY2UiLCJuYW1lIjoiSmltbXkiLCJhZ2UiOjU1fQ.DSdqRFRPM4Hep704s3cvWkpH5FFpnIc82uVUswHbaz4

enter image description here

my output string:

WwogIHsKICAgICJ0eXAiIDogIkpXVCIsCiAgICAiYWxnIiA6ICJIUzI1NiIKICB9Cl0.WwogIHsKICAgICJhZ2UiIDogNTUsCiAgICAibmFtZSIgOiAiSmltbXkiLAogICAgIlRpdGxlIiA6ICJOaWNlIgogIH0KXQ.AhlqiFIcS-ytUKnhazsn7-eYNwgmXfwON7EN2gozRAw

enter image description here

class ViewController: UIViewController {

    let headerJson: [[String: Any]]  = [
                                        [
                                         "alg": "HS256",
                                         "typ": "JWT"
                                        ]
                                       ]
    let payloadJson: [[String: Any]] = [
                                        [
                                         "Title": "Nice",
                                         "name": "Jimmy",
                                         "age": 55
                                        ]
                                       ]

    var base64UrlHeaderString: String = ""
    var base64UrlPayloadString: String = ""

    override func viewDidLoad() {
        super.viewDidLoad()

        let headerData = jsonToData(json: headerJson)
        let headerString = headerData?.base64EncodedString()
        if let headerString = headerString{
            let str = base64ToBase64url(base64: headerString)
            base64UrlHeaderString = str
            print("base64UrlHeaderString : \(base64UrlHeaderString)")
        }

        let payloadData = jsonToData(json: payloadJson)
        let payloadString = payloadData?.base64EncodedString()
        if let payloadString = payloadString{
            let str = base64ToBase64url(base64: payloadString)
            base64UrlPayloadString = str
            print("base64UrlPayloadString : \(base64UrlPayloadString)")
        }

        let totalString: String = base64UrlHeaderString + "." + base64UrlPayloadString

        let signature = totalString.hmac(algorithm: .SHA256, key: "hello")
        print("signature  : \(signature)")

        let finalString: String = base64UrlHeaderString + "." + base64UrlPayloadString + "." + signature
        print("finalString  : \(finalString)")
    }
}

enum HMACAlgorithm {
    case MD5, SHA1, SHA224, SHA256, SHA384, SHA512

    func toCCHmacAlgorithm() -> CCHmacAlgorithm {
        var result: Int = 0
        switch self {
        case .MD5:
            result = kCCHmacAlgMD5
        case .SHA1:
            result = kCCHmacAlgSHA1
        case .SHA224:
            result = kCCHmacAlgSHA224
        case .SHA256:
            result = kCCHmacAlgSHA256
        case .SHA384:
            result = kCCHmacAlgSHA384
        case .SHA512:
            result = kCCHmacAlgSHA512
        }
        return CCHmacAlgorithm(result)
    }

    func digestLength() -> Int {
        var result: CInt = 0
        switch self {
        case .MD5:
            result = CC_MD5_DIGEST_LENGTH
        case .SHA1:
            result = CC_SHA1_DIGEST_LENGTH
        case .SHA224:
            result = CC_SHA224_DIGEST_LENGTH
        case .SHA256:
            result = CC_SHA256_DIGEST_LENGTH
        case .SHA384:
            result = CC_SHA384_DIGEST_LENGTH
        case .SHA512:
            result = CC_SHA512_DIGEST_LENGTH
        }
        return Int(result)
    }
}

func base64ToBase64url(base64: String) -> String {
    let base64url = base64
        .replacingOccurrences(of: "+", with: "-")
        .replacingOccurrences(of: "/", with: "_")
        .replacingOccurrences(of: "=", with: "")
    return base64url
}

func jsonToData(json: Any) -> Data? {
    do {
        return try JSONSerialization.data(withJSONObject: json, options: JSONSerialization.WritingOptions.prettyPrinted)
    } catch let myJSONError {
        print(myJSONError)
    }
    return nil
}

extension String {

    func hmac(algorithm: HMACAlgorithm, key: String) -> String {
        let cKey = key.cString(using: String.Encoding.utf8)
        let cData = self.cString(using: String.Encoding.utf8)
        var result = [CUnsignedChar](repeating: 0, count: Int(algorithm.digestLength()))
        CCHmac(algorithm.toCCHmacAlgorithm(), cKey!, strlen(cKey!), cData!, strlen(cData!), &result)
        let hmacData:NSData = NSData(bytes: result, length: (Int(algorithm.digestLength())))
        let hmacBase64 = hmacData.base64EncodedString(options: .lineLength64Characters)
        let hmacString = base64ToBase64url(base64: String(hmacBase64))
        return hmacString
    }
}

Upvotes: 1

Views: 7797

Answers (1)

jps
jps

Reputation: 22585

Your output string

WwogIHsKICAgICJ0eXAiIDogIkpXVCIsCiAgICAiYWxnIiA6ICJIUzI1NiIKICB9Cl0.WwogIHsKICAgICJhZ2UiIDogNTUsCiAgICAibmFtZSIgOiAiSmltbXkiLAogICAgIlRpdGxlIiA6ICJOaWNlIgogIH0KXQ.AhlqiFIcS-ytUKnhazsn7-eYNwgmXfwON7EN2gozRAw

contains two arrays of JSON objects (plus signature):

[
 {
  "typ": "JWT",
  "alg": "HS256"
 }
]
.
[
 {
  "age": 55,
  "name": "Jimmy",
  "Title": "Nice"
 }
]

the []brackets are used for arrays, and {}for objects, hence you have two arrays containing JSON objects instead of just two JSON objects as desired:

{
  "typ": "JWT",
  "alg": "HS256"
}
.
{
  "age": 55,
  "name": "Jimmy",
  "Title": "Nice"
}

You need to remove one pair of []brackets on both sides and as just declare it as array of strings and not as array of array of strings.

e.g. like this:

let headerJson: [String: Any]  =   [
                                     "alg": "HS256",
                                     "typ": "JWT"
                                   ]

Now you would get a syntactically correct result, but it's still longer than necessary as it contains linebreaks and spaces. Usually the serializer removes all whitespace (spaces, linebreaks), but you use the prettyPrintedoption in your code:

JSONSerialization.data(withJSONObject: json, options: JSONSerialization.WritingOptions.prettyPrinted)

This option should only be used when you want to display the JSON somewhere, for a JWT remove that option:

JSONSerialization.data(withJSONObject: json)

Once you have the result as already shown on your jwt.io screenshot, you first put the key into the key field on the right (verify signature) and then paste your token into the left side. Then you should get a verified signature.

While what you're doing here is certainly good for learning purposes, for more serious use I would recommend one of the packages listed on https://jwt.io/ where you find many JWT packages for many different languages, including Swift. Just scroll down on that page to find the list.

Upvotes: 1

Related Questions