Reputation: 595
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
my output string:
WwogIHsKICAgICJ0eXAiIDogIkpXVCIsCiAgICAiYWxnIiA6ICJIUzI1NiIKICB9Cl0.WwogIHsKICAgICJhZ2UiIDogNTUsCiAgICAibmFtZSIgOiAiSmltbXkiLAogICAgIlRpdGxlIiA6ICJOaWNlIgogIH0KXQ.AhlqiFIcS-ytUKnhazsn7-eYNwgmXfwON7EN2gozRAw
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
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 prettyPrinted
option 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