Verify signature of JWT token using iOS swift4 - ios

I have the following JWT token which i was able to decode using JWTDecode Cocoapod.
let token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJEZXYyLVNES1Byb2ZpbGVTZXJ2aWNlIiwiaWF0IjoxNTI0MDQxNzI4LCJuYmYiOjE1MjQwNDE3MjgsImV4cCI6MTUyNjYzMzcyOCwianRpIjoiMzA3MGEyYzJiMDNiNDIwMTljNjc1NjU5NGExZTMyZTEiLCJpZGVudGlmaWVyIjoiMzEyNGJjZTYtYTQ3ZS00OTBjLWFjZmItYThjNjAwODc5NDYxIiwiY3VzdG9tZXIiOiIzNDM5IiwiT2JzZXJ2ZWRUeiI6IkFtZXJpY2EvTmV3X1lvcmsiLCJkZXZpY2VzIjoiW1wiYnBjdWZmXypcIixcIndlaWdodHNjYWxlXypcIixcIip3ZWxjaGFsbHluKlwiXSIsInRhcmdldHMiOiJbe1wiVXJsXCI6XCJodHRwczovL2RldjItdml0YWxzc2VydmljZS5oaG1hZXN0cm8ubmV0XCIsXCJTdGF0dXNcIjp0cnVlLFwiSGVhZGVyc1wiOntcIkF1dGhvcml6YXRpb25cIjpcImV5SmhiR2NpT2lKU1V6STFOaUlzSW5SNWNDSTZJa3BYVkNKOS5leUpwYzNNaU9pSkVaWFl5TFZORVMxQnliMlpwYkdWVFpYSjJhV05sSWl3aWFXRjBJam94TlRJME1EUXhOekk0TENKdVltWWlPakUxTWpRd05ERTNNamdzSW1WNGNDSTZNVFV5TmpZek16Y3lPQ3dpYW5ScElqb2lOR1JpTURZMFpHSTFZMkpqTkRrMFpHRmpOalJtWWpCaVl6ZG1aREJrT1dVaUxDSnBaR1Z1ZEdsbWFXVnlJam9pTXpFeU5HSmpaVFl0WVRRM1pTMDBPVEJqTFdGalptSXRZVGhqTmpBd09EYzVORFl4SWl3aVkzVnpkRzl0WlhJaU9pSXpORE01SWl3aVQySnpaWEoyWldSVWVpSTZJa0Z0WlhKcFkyRXZUbVYzWDFsdmNtc2lmUS5WbVAycm5OdzFiRjhJSVBUTnNQNGVCajJfUVJrRWFuYlpFZmt2NEZ2NjMzSFRlaHNSX010N3FMVUV2TERvQVlEQTA5NmxNbUdTZXA1enhLQkpEYkZwR3VWZmxjUTlCaTJ6MlJfY29SdFktcjU3U19NRVlZQVFORW1XZnRBVVVFZm1MTWFGM0piUHg5V0EwbE41V0hCOWhhcHBjT1NMYXRfcmdwX1pUd1VVd1V5WURDRTF4SXZjNWJockJLaXluM01qNTZlSlB0bEtUZElBZ01YZ29WOGRoSGd3XzZGZXo2dlMwZ2J1djdDMmd1TkNRUFJzRmZvcWFxUEZCNzZtU0VLUWtZUWFlQUlnRW1oSG9wRjRoRXVpcmRIR0JaT0tLYTNNejFvRnNqSjQ2OTVsLUcwX3NFbmV6b3FJSXlYZTN2X3ZsUVhlYmN4eHhfSk5vVjBfTmhDdmdcIn19XSJ9.pDp8cDssRcdB5FA_ykm-c0-g_jPEHPbod252d-bQzpo5PgsKTh4CRFrZ8bt6fam26IMOG_oYcXGZw9NUJowJ_qq5txQXJ7NPeX36Qy77-IpFttEDdAKEvwd6Y3j-hA-BUEzBuHUEPQASAfpFX9gY_ZqJsb6rIsqwi-_hh8vgBJdTODl4_n7vdAW2jtrZvp_BTSTDJ1-ZdJ_U0Oq_11_d5YgmU2s3bee_oVlLRs7o7dGEltbcgVThr4NfL8IVdoZ8H9YiUVeL69mh_LZZ1c7zYLZ4XNMyGSspdBVN8HewnNUD5_f9MGjXDanzX2U8Qc4BlsYd8nxZBSL02OfAkM53Uw"
do{
let jwt = try decode(jwt: token3)
print(jwt)
}
This code using the JWTDecode Library decodes the token successfully. Next step is I have to verify the signature of this token.The public and private key strings are available.
let publicKey = "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0NCk1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBcWtuU2dVcVVBZW5kUGR6bU9LSDYNClp1djd1czY4cFlsYWhkc0RHa1BCKzcwd2VSWWZHUUU3Wkl4cWR1WHhwbi80eUhWMDNHZmhKbXFLdktyOTZIWk8NCklkWkp5S3UvQWpkZGFWMGFsTmtwSisxRzRqSlg3RlVhSGplN3pYVk5WOEw2akVpVmpLcUNOMVNkKzc3dmpKNDUNClk5TXNMKzBQOVlGSFRkRVdNUWdjMmUrekRqdjVmc3E0SEpQcUJVU0VybUZRN2puRXVCa1lJemRLaXpFdTluSUwNCmE4a0Q1VVlhT0NUL2xJbEoveTJpbzdMSGJQRmQrTDBTYkREZUtwNGF1WVdsb3dQWkYrWVovKzBxdkRVMml4cUkNCi96bSt3TUlXUFdmanYxOHhEV3hLY2ErN2RBWlJ3bTVGNUxxU2ZGckcxNGhQcjg3RXJyNldXeUtsNkdXMnFhQ0oNCk9RSURBUUFCDQotLS0tLUVORCBQVUJMSUMgS0VZLS0tLS0NCg=="
The privateKey used is
let privateKey = "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQ0KTUlJRXBBSUJBQUtDQVFFQXFrblNnVXFVQWVuZFBkem1PS0g2WnV2N3VzNjhwWWxhaGRzREdrUEIrNzB3ZVJZZg0KR1FFN1pJeHFkdVh4cG4vNHlIVjAzR2ZoSm1xS3ZLcjk2SFpPSWRaSnlLdS9BamRkYVYwYWxOa3BKKzFHNGpKWA0KN0ZVYUhqZTd6WFZOVjhMNmpFaVZqS3FDTjFTZCs3N3ZqSjQ1WTlNc0wrMFA5WUZIVGRFV01RZ2MyZSt6RGp2NQ0KZnNxNEhKUHFCVVNFcm1GUTdqbkV1QmtZSXpkS2l6RXU5bklMYThrRDVVWWFPQ1QvbElsSi95MmlvN0xIYlBGZA0KK0wwU2JERGVLcDRhdVlXbG93UFpGK1laLyswcXZEVTJpeHFJL3ptK3dNSVdQV2ZqdjE4eERXeEtjYSs3ZEFaUg0Kd201RjVMcVNmRnJHMTRoUHI4N0VycjZXV3lLbDZHVzJxYUNKT1FJREFRQUJBb0lCQVFDbDFHQWZzazJ5RTFsMQ0KWGdJQVVwVHoxNGo3NFVuS2RwamwwMk1SRjd6M2RzU1dsbGxRVTJmUVFnR0hxZU9LdmdLNnk4OHl1Q0tFODZvSg0Ka3diU2N5c2hQbm41NW02TExQbFZtdXBBMjcxOWVVN1hCaW1qSnpqWkJuTm40SHlpSTJrMFpaYmxOa0s2dVRkaw0KS2d0RHgrMmhiY3NSSE8yMnFkK1RRek0yS20xV09NVWlja0RLalhsN0hucmgvSU9FTGh5WTRCZk5oODB0ZU9Ibg0KOFpnclJBNTRLcktJSTAvN1lzR3pDdlBRZ3NCQm1haTdVYm83QlFxR3BZWXZPb0g0d2kzQU45c1VnTkdJWXBWMQ0KdE5BcUdxUWlvRkNXZkVxck8xMHRManBveXE2aXl2WnZpa0FkcjhBQitiejRyK0FYMUIzY3ZlbjZEMVBnekN2Yw0KNEdaTHpUaUJBb0dCQU5ncnFuL2g5Z0luN0owbWNJNEJMTWxmMXpIdVJBNHRTK1BOdzBOVXJyS2JsdDhyUjZrUQ0KOGtqT056MjQrMi9pMUJRa0l5eVM4bzdOTnJHNFoxbnVRaERWN2hiL0tUMXEyK2hYRmIvQWdZUXpYWXU5R2g0Ug0KVUZXdEt2bFBpV0plZUI3TksyYTI2TDNtWEJ4MUE5Y1BmcS91OWpSYXhwT3lqWHJxM0phNDUyU3BBb0dCQU1tcA0KL0FVa3hpaHQ4ZVdibFJMY1VvemxLajI2WHRxQXR5cHNpSHB2ayt0WnA5bVJxV2RYeDIzM3hhOFdkTkJCcXA4UQ0KWGFIRzVjZThuS2NaSWt1S2ZvWXBueEt6WE5RenVRSlh1M3dqZ005Z3V4RFUyNU8vMENNNDJyMjZlUEhJeVdNQQ0KSWxUTkVVVnkyQUZWSkUzTk1mZFNvblVqMjJVRVloeEkrVHpXR2tvUkFvR0JBS3dYNXo2ejF6UFVNT3pUQTF3cA0KMTB2aHZ1SURPNjdGcE5zUW5samwrOFk1VTUwTFNadHc0RkhSeWV5YmJhQ2ZSaE5heVozY3hybWs2ZHdHWUZFWg0KK3dLSUxXbWxiV0YxeHVockcrZHlEQ29BOG9JaTQ3MzRMcXBtbUFXdXFrTGp6bUZIR1R4R2RYZHBBditzc0lmdg0Kei8ya0VlR1FPdkt1ZlMvVDloVVAwemN4QW9HQVp5MmtkeEZBblpEYkVlb1BWSzRMUW5GQnNvRjNaSDQwdU96OA0KeXYvcGc2SEVna25IamN0WWl3Z1pTYUxJczREVmhqcStYVFpCZkhjaEExR1Z2V2FubzRjS0QyeGJrMnEvUHRhYQ0KWTBKYTlqOThsbmtCdTArSmMydjBadHhRWXd5akZSY05lYXZPS1dVLzVUYWxzM1RJR3MxWnQydFlKaEFmRG0rNw0KcllleHZiRUNnWUJxN3JqM05YRjdwSjZnQllMSURWOEttTCtTdnFhbS9KQkN6RjFQMnUvYTFabHd6b0Q3ZVhoWg0KUmd0eStrZ3lPU0pwUTFBZTdPUk1VSlFGRmtiY2VCanluQzdnSXFTTnlxV3B4SjJBTUpBL1pwYXE3WHFoVVZYSQ0KWEx2TVE1RGhoY0U4RG1CY09SYkFKaTg4S1JVZVVVYjdHWlBJa3lmRmYvSURFTlB1a01aZHBBPT0NCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tDQo="
The algorithm used is RSA 256. Can someone guide me on how to verify the signature for the token using the public key.

Often, JWT libraries provide functionality to verify a JWT's signature.
Anyway, in case you want to do it yourself: The input to a JWT's sign function is the following concatenation: header + "." + payload. So if you want to verify a signature, you need to do that against that concatenation.
Once you have imported your keys as SecKey, e.g. using SecKeyCreateWithData, you should be able to just use iOS's Security framework to verify the signature somewhat like this:
let parts = token.components(separatedBy: ".")
let header = parts[0]
let payload = parts[1]
let signature = Data(base64URLEncoded: parts[2])!
let signingInput = (header + "." + payload).data(using: .ascii)!
SecKeyVerifySignature(publicKey, .rsaSignatureMessagePKCS1v15SHA256, signingInput as CFData, signature as! CFData, nil)

Accepted answer is good, but you have to remember that base64 string must be correctly encoded when creating Data() object. So complete code is:
let parts = token.components(separatedBy: ".")
let header = parts[0]
let payload = parts[1]
let signature = Data(base64URLEncoded: base64StringWithPadding (base64str:parts[2]))!
let signingInputStr = (header + "." + payload)
let signingInput = signingInputStr.data(using: .ascii)!
SecKeyVerifySignature(publicKey, .rsaSignatureMessagePKCS1v15SHA256, signingInput as CFData, signature as! CFData, nil)
func base64StringWithPadding(base64str: String) -> String {
var newStr = base64str.replacingOccurrences(of: "-", with: "+")
.replacingOccurrences(of: "_", with: "/")
let count = newStr.count % 4
if count > 0 {
let amount = 4 - count
for _ in 0..<amount {
newStr += "="
}
}
return newStr
}
Also few words about public key. When using text form .pem, inside you have to have something like this:
-----BEGIN PUBLIC KEY-----
key data
-----END PUBLIC KEY-----
and BEGIN and END strings have to be removed. Also remember that when your key is containing not valid base64 chars, you may need to skip them. (NSDataBase64DecodingIgnoreUnknownCharacters)
You can generate keys like this: (on Mac, similarly on other platforms)
ssh-keygen -t rsa -b 4096 -m PEM -f jwtRS256.key
Don't add passphrase
openssl rsa -in jwtRS256.key -pubout -outform PEM -out jwtRS256.key.pub
Also, there is no need to use any library to decode base64 strings. This functionality is already on iOS. Do this: Convert base64 to base64Withpadding, create Data(), create String from data using Ascii encoding, done.
(writing this for future self)

Related

Retrieve SecKey from RSA private key string encoded with passphrase in Swift or Objective C

We have an enterprise mobile app that ships with an encrypted private key. We intend to provide users with the passphrase in order to use that private key, which will then allow them to communicate with a backend server. I've previously set this up using Python or C# but cannot figure out how to do this in Swift or Objective C. I've adapted some of this from this stack overflow question: Encrypt using RSA Certificate in Swift
static func getCertificate(from certificate: String?, usingPhrase phrase: String?) -> SecKey? {
guard let certificate = certificate else {
print("Nil string passed in, nil being returned")
return nil
}
let beginning = "-----BEGIN ENCRYPTED PRIVATE KEY-----"
let tail = "-----END ENCRYPTED PRIVATE KEY-----"
let certificateString = certificate.replacingOccurrences(of: beginning, with: "")
.replacingOccurrences(of: tail, with: "")
.replacingOccurrences(of: "\n", with: "")
.replacingOccurrences(of: " ", with: "")
guard let data = Data(base64Encoded: certificateString) else {
print("Unable to cast string to data")
return nil
}
// ** I'm assuming here is where I need to decrypt the key before I can create the certificate **
guard let cert = SecCertificateCreateWithData(nil, data as NSData) else {
print("Unable to cast certificate to SecCertificate")
return nil
}
var secTrust: SecTrust?
// Retrieve a SecTrust using the SecCertificate object. Provide X509 as policy
let status = SecTrustCreateWithCertificates(cert, SecPolicyCreateBasicX509(), &secTrust)
// Check if the trust generation is success
guard status == errSecSuccess else { return nil }
// Retrieve the SecKey using the trust hence generated
guard let trust = secTrust else {
print("Trust policy not created")
return nil
}
// ** I acknowledge that this method creates a public key and I am passing in a private key
// ** I am not sure what method needs to be used instead
guard let secKey = SecTrustCopyPublicKey(trust) else { return nil }
return secKey
}
Ultimately, I need to decrypt the private key using a passphrase. I was planning on using it as a SecKey, but getting the Base64 String representation of it works for me as well. I am comfortable working through an Objective-C or Swift based answer.
EDIT
Here is a sample key for test purposes:
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIj6w/NvIjTXICAggA
MBQGCCqGSIb3DQMHBAi6qjkA0+yxyQSCBMhqdUDLRCLUjbgqe3rbF2lHn6yTsYbk
pfLWHkKT4pvQtaqXJvPZErb3E27va3HXvVDJfJS0/iwsnzIn6p2J9ZtgIGs4OBRU
kUw8lVAhNHTkAw/sj+OHdWexfOL5vKE3DgXqnAVGyhm4CNDXQ/9UDqkmtmHsMOlz
nqbOdWhMS/Uj/Dh41urw3sstpX4wZCHGTFNDL83pDAv7jfAZF/NSQq8ft/BPknMu
HLvYvd3fR4iKqKswcvR5c2q+CLbfEbXbVty6B/JMDSoi3wuh9lezMesIhTUYDSaK
QgkCEtrJ4FsO/tPXtyGvCjKVgvfvn8njQPtphq/gbKBeXpopsFGi19iY8fCkPQSb
Cp6FttMvJPwJvIb/qUZWGu9OWaBhmn0MH+qtXED6yxqXSyQDRYg1Vurfm0azQxUb
zJIy8qgla9GVvoGYpIGDvsQZFzur6le8G6/6c85raI9LZ88Bo8gEsTeQgPMxG1c+
7kRvn/hl/n0Oh8VsPOHjx2N/Y9vcmlyIlCDPIuGiYcNy1ICDv/kaBD9JVJVA5S7X
+MRZG8+EesjTiZseVUwK9OhnE4Jws2UzAk3zfMvzvnAgxue/FcRPUfYiHakNu83z
SFqayIUGz3zD0XLdWBVrh2QPvxW6eP3AFdIpjrPzwq1kDDw19VaAu7mw7JexrOyW
qvCO/VIHXqflL/OCxPT5BUQ9pbxTCEfv5wbWzczKWWub0AhPexDBW7wat3HwUGeb
oeEwnmNxAXxxz/RJeK1zKUAKGWQPI8X4bG/IZFmk1dgeJ8bo9My5b05Zw9d/gw7C
Xi5nZ5sG5ERp3jKLsT2czbr66w4HV4L38mASVtTUeXyySvnz//Ib40FC46Gi7SqP
pcpl1CrDi0UWe/cbQ/qkcaFrgdvIGsuSfZf8amq1FHnB47NUblYmm1WPCqeNtgzY
srAy/aVtF6FvG+uy6sCrP76c9HY1ZvyeO/82t/Sd5jnoq+VCKtarRNjEEfdwNGQp
X/ycspdn+a0XkXthSBvHWcCmQmgAV8Yp5TR0r2PgGqHk3lRq9/yKWy1gRuPSiRpZ
HzOOfZ4DmVELRf5R5+UCVJ5idkKZb2t+R7rl5/9grf8iCeUPngIkxrZvr4b7/mQm
fkmIMSUYT9CVeBprF5f2wLbbAmPpoUnULTnVzrOhZYCZGRQLyGGdX+CELBNxc8Er
dt4deeutCQm+H0d5V09HO9AOAwlESyt9q4CEAcSzSzzMygvWLe04csdcCSV2htAm
n0zDwhqGZ2LI+dUTGw4apOdBuNeveaHBrlp7XhCIOJ35SAWrb8baPizwl4iw5fA0
ucBZzRDAavDhj6XMQSwsOaCfzYfpASqwkm2Zjk3znWS18xpXRxvgqfCHpJRo9M4f
SQlRpT3Nqw5vn8BV+ioBvwxQd/1XsMbjKKwbwk+1wB/E/mHAiIQUQJ6Ec/WqzKqn
biqlBuSGLrS5O8ynu83DERFiatCAkNkl6nCaWtNu2KWtKM52y03BN3MBxS1kU+FI
afb7mN75j1gTZFH6EmujfVfrL/f8aO1dkxHO4IuWb5r7DaY7AByZgo1EKGiSIh3N
rtQVsAQr1/NcO6GVSHQU5egpI/eocvHvrAzsvlE2sqNBKm4NVogXjms7avKIbtA4
+Ro=
-----END ENCRYPTED PRIVATE KEY-----
The passcode for this key is
8720c10d735d7fb218b9e48db942a494
I took the sample encrypted key you provided and pasted it into this excellent ASN.1 decoder. It produces the following result:
Which is actually a very useful summary of the steps required to decrypt the ciphertext using the key you have:
The first two OBJECT IDENTIFIER entries hint at the padding mode used during encryption and the key derivation function used to obtain an encryption key from your "passcode": PKCS #5 padding and PBKDF2 for key derivation. The OCTET STRING and INTEGER values under that are the salt and iteration count for PBKDF2.
The third OBJECT IDENTIFIER indicates the encryption algorithm - TripleDES in CBC mode. Not ideal, but secure enough I guess. The OCTET STRING under that is the 8-byte IV, since DES has an 64 bit block size.
The final OCTET STRING is the ciphertext.
So, in summary, you need to:
Find a way to get at the values you need e.g. salt, iteration count, IV and ciphertext - assuming the algorithms used for these keys stay the same. Sometimes you can get away with jumping around in the raw data and slicing these values out - depending on your needs, you might need to use an ASN.1 decoder.
When it comes time to decrypt - pull out the salt and iteration count (first OCTET STRING and INTEGER). Pull out the IV (second OCTET STRING) and the ciphertext (last OCTET STRING).
Apply PBKDF2 using the specified salt and iteration count to your passcode, the one you provide in the question. You'll need to extract 192 bits of key material from this, since we're using TripleDES.
Decrypt the ciphertext using the key material from the last step in CBC mode using the IV you extracted. Use PCKS#5 padding, which is usually the default.
Done!

Swift String and base64Encode Confusion

I have problem with encoding data. My token has 1228 characters and
let data = Data(base64Encoded: tokenString)!
works fine, but when i add to token some information and my token has 1263 characters Data(base64Encoded:) returns nil.
Problem in my opinion is in the string length or Data(base64Encoded:).
Does Data(base64Encoded:) have any restrictions on the length of characters?
Please give me some info about this problem.
Not sure how you encode the modified token, but looks like the encoded Base64 string doesn't include padding characters. Appending = to the modified token seems to fix the decoding issue:
// original token -> 57 bytes
let data1 = Data(base64Encoded: "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyMTF9")
// modified token -> nil
let data2 = Data(base64Encoded: "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyMX0")
// modified token with '=' for padding -> 56 bytes
let data2Fixed = Data(base64Encoded: "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyMX0=")
// decodes correctly: "{"sub":"1234567890","name":"John Doe","iat":15162390221}"
let string = String(data: data2Fixed!, encoding: .utf8)
So to solve it properly you probably need to look into the encoder. Hope that helps!

How to encode and decode Base64 and Base64Url in Flutter / Dart

I want to encode the following string in Base64Url in Flutter and decode it in on a Dart server.
"username:password"
How do I do that? And how do I do it in Base64?
The dart:convert library contains an encoder and decoder for Base64 and Base64Url. However, they encode and decode Lists of integers, so for strings you also need to encode and decode in UTF-8. Rather than doing these two encodings separately, you can combine them with fuse.
You need to have the following import:
import 'dart:convert';
Base64
String credentials = "username:password";
Codec<String, String> stringToBase64 = utf8.fuse(base64);
String encoded = stringToBase64.encode(credentials); // dXNlcm5hbWU6cGFzc3dvcmQ=
String decoded = stringToBase64.decode(encoded); // username:password
Note that this is equivalent to:
String encoded = base64.encode(utf8.encode(credentials)); // dXNlcm5hbWU6cGFzc3dvcmQ=
String decoded = utf8.decode(base64.decode(encoded)); // username:password
Base64Url
String credentials = "username:password";
Codec<String, String> stringToBase64Url = utf8.fuse(base64Url);
String encoded = stringToBase64Url.encode(credentials); // dXNlcm5hbWU6cGFzc3dvcmQ=
String decoded = stringToBase64Url.decode(encoded); // username:password
Again, this is equivalent to:
String encoded = base64Url.encode(utf8.encode(credentials)); // dXNlcm5hbWU6cGFzc3dvcmQ=
String decoded = utf8.decode(base64Url.decode(encoded)); // username:password
See also
RCF 4648
String based data encoding: Base64 vs Base64url

Swift 3 export SecKey to String

I am developing an iOS app using swift 3.
I need to export an SecKey (which is the user RSA publickey reference) to a string (e.g base64) in order to share it through a generated QRCode.
It also has to work the other way since the other user that scans the QRCode, will be able to rebuild a SecKey reference from the string extracted from the QRCode.
I found few tutorials but I don't understand exactly what I need to extract from the SecKey reference, and I don't know how to convert it to a String.
Export Key (iOS 10 only)
var error:Unmanaged<CFError>?
if let cfdata = SecKeyCopyExternalRepresentation(publicKey!, &error) {
let data:Data = cfdata as Data
let b64Key = data.base64EncodedString()
}
See https://stackoverflow.com/a/30662270/5276890 and https://stackoverflow.com/a/27935528/5276890 for longer ways which probably support iOS < 10.
Reimport Key
guard let data2 = Data.init(base64Encoded: b64Key) else {
return
}
let keyDict:[NSObject:NSObject] = [
kSecAttrKeyType: kSecAttrKeyTypeRSA,
kSecAttrKeyClass: kSecAttrKeyClassPublic,
kSecAttrKeySizeInBits: NSNumber(value: 512),
kSecReturnPersistentRef: true as NSObject
]
guard let publicKey = SecKeyCreateWithData(data2 as CFData, keyDict as CFDictionary, nil) else {
return
}
Note: This generates a base64 key and not a certificate. A lot of code samples online deal with how to generate a public key from a certificate using SecCertificateCreateWithData
Also: 512 bit is fast to generate but worthless. Pick a longer and secure value once you're satisfied with the results.
I got valid results back when importing the key I generated and exported, so I assume it works, but I did not try to encrypt and decrypt with it.

How to generate an RSA asymmetric Key Pair in Swift for IOS?

I need a way to generate an RSA asymmetrical key pair in Swift. I don't need to store it in the keychain or anything. I just need to generate a key pair and shove both keys into String variables.
The keys do need to be compatible with PHP on the other end. I will use symmetrical encryption to secure the private key and store it on the phone. I'll be sending the public key to a web service that is implemented in PHP, and the web service will store the public key in a database.
That public key will be used later by the web service to encrypt values like one-time passwords and other sensitive values destined for the IOS app. I'll implement a similar scheme for small pieces of data flowing from the IOS app to the web service.
The only documented API declaration I could find for generating a key pair in Swift is shown on developer.apple.com:
func SecKeyGeneratePairAsync(_ parameters: CFDictionary!,
_ deliveryQueue: dispatch_queue_t!,
_ result: SecKeyGeneratePairBlock!)
I tried to figure out how to use this, but XCode doesn't like the underscores, and I'm not sure what I am actually supposed to do with this or how to use it.
Even if XCode would accept it, I am not sure how I would call the function and what values to pass it, etc. I included "import Security" at the top, so that's not the problem.
It's ridiculous that this should be so hard. All I want to do is generate an asymmetric key pair.
It's a piece of cake to do this in PHP or .NET or Java or any other language, but I can't find any clear documentation on it for Swift. I have to use Swift for this app. I don't want to use OpenSSL because it's deprecated.
I am using Swift because I bloody hate objective C. Swift is the reason I am finally jumping into IOS development.
I don't know how to integrate an Objective C class with Swift, and I'd really rather have a pure Swift solution if there is one. If not, I'd appreciate some pointers on how to integrate an Objective C solution and make it work.
The above snippet is the only function call Apple provides, and naturally it's incomplete, doesn't make sense, and doesn't work.
There is a good example of how to do this in Swift in the CertificateSigningRequestSwift_Test project in GitHub. Using a single call to SecKeyCreateRandomKey() one can generate the public/private key pair both at the same time and they will be saved in the keychain.
let tagPublic = "com.example.public"
let tagPrivate = "com.example.private"
let publicKeyParameters: [String: AnyObject] = [
String(kSecAttrIsPermanent): kCFBooleanTrue,
String(kSecAttrApplicationTag): tagPublic as AnyObject,
String(kSecAttrAccessible): kSecAttrAccessibleAlways
]
var privateKeyParameters: [String: AnyObject] = [
String(kSecAttrIsPermanent): kCFBooleanTrue,
String(kSecAttrApplicationTag): tagPrivate as AnyObject,
String(kSecAttrAccessible): kSecAttrAccessibleAlways
]
//Define what type of keys to be generated here
var parameters: [String: AnyObject] = [
String(kSecAttrKeyType): kSecAttrKeyTypeRSA,
String(kSecAttrKeySizeInBits): 2048,
String(kSecReturnRef): kCFBooleanTrue,
kSecPublicKeyAttrs as String: publicKeyParameters as AnyObject,
kSecPrivateKeyAttrs as String: privateKeyParameters as AnyObject,
]
//Use Apple Security Framework to generate keys, save them to application keychain
var error: Unmanaged<CFError>?
let privateKey = SecKeyCreateRandomKey(parameters as CFDictionary, &error)
if privateKey == nil{
print("Error creating keys occurred: \(error!.takeRetainedValue() as Error), keys weren't created")
return
}
//Get generated public key
let query: [String: AnyObject] = [
String(kSecClass): kSecClassKey,
String(kSecAttrKeyType): kSecAttrKeyTypeRSA,
String(kSecAttrApplicationTag): tagPublic as AnyObject,
String(kSecReturnRef): kCFBooleanTrue
]
var publicKeyReturn:AnyObject?
let result = SecItemCopyMatching(query as CFDictionary, &publicKeyReturn)
if result != errSecSuccess{
print("Error getting publicKey from keychain occurred: \(result)")
return
}
let publicKey = publicKeyReturn as! SecKey?
//Set block size
let keyBlockSize = SecKeyGetBlockSize(self.publicKey!)
//Ask keychain to provide the publicKey in bits
let query: [String: AnyObject] = [
String(kSecClass): kSecClassKey,
String(kSecAttrKeyType): keyAlgorithm.secKeyAttrType,
String(kSecAttrApplicationTag): tagPublic as AnyObject,
String(kSecReturnData): kCFBooleanTrue
]
var tempPublicKeyBits:AnyObject?
_ = SecItemCopyMatching(query as CFDictionary, &tempPublicKeyBits)
guard let publicKeyBits = tempPublicKeyBits as? Data else {
return
}
Heimdall seems to be what you're looking for. It's easy to use, can create RSA keypairs, encrypt, decrypt, sign and verify.
It uses the iOS/OS X keychain for storing the keys, so the keys are stored in a secure way.
From the GitHub Readme:
if let heimdall = Heimdall(tagPrefix: "com.example") {
let testString = "This is a test string"
// Encryption/Decryption
if let encryptedString = heimdall.encrypt(testString) {
println(encryptedString) // "cQzaQCQLhAWqkDyPoHnPrpsVh..."
if let decryptedString = heimdall.decrypt(encryptedString) {
println(decryptedString) // "This is a test string"
}
}
// Signatures/Verification
if let signature = heimdall.sign(testString) {
println(signature) // "fMVOFj6SQ7h+cZTEXZxkpgaDsMrki..."
var verified = heimdall.verify(testString, signatureBase64: signature)
println(verified) // True
// If someone meddles with the message and the signature becomes invalid
verified = heimdall.verify(testString + "injected false message",
signatureBase64: signature)
println(verified) // False
}
}

Resources