1

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
    }
}

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.