Export SecKey to pkcs8 format - ios

Currently I am generating a RSAKeyPair with the iOS security framework
statusCode = SecKeyGeneratePair(keyPairAttributes as CFDictionary, &newPublicKey, &newPrivateKey)
I then want to export these keys to a PEM format with PKCS8. The standard method apple provides to export keys is this:
let cfData = SecKeyCopyExternalRepresentation(self, &error)
but this data gives me the key in the PKCS1 format. Is there a way to convert the PKCS1 format to PKCS8?
So far all my research left me clueless on what to actually do to convert these formats.

I can't translate into Swift, but here's the nearly-Objective-C:
SecItemImportExportKeyParameters keyParams = {};
keyParams.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
keyParams.passphrase = you_need_a_cfstringref_here;
SecExternalFormat dataFormat = kSecFormatWrappedPKCS8;
OSStatus status = SecItemExport(privateKey, dataFormat, 0, &keyParams, &cfData);

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!

Get EC Private Key PEM string from SecKey Object

I'm trying to get a PEM encoded key from a SecKey on iOS. The EC private key is stored on the Keychain.
I'm getting a Data object from the SecKey object:
if #available(iOS 10.0, *) {
var error: Unmanaged<CFError>?
if let cfdata = SecKeyCopyExternalRepresentation(key, &error) {
return cfdata as Data
}
}
The Data object is in the 04 || X || Y || K format. How do I convert that to a PEM key?
Which curve do you use?
if you use kSecECCurveSecp256r1 and kSecAttrTokenIDSecureEnclave, you can't export the ECPrivateKey! Keys created within a trusted coprocessor cannot he exported (and I believe ones imported can’t either). Plus, you should never transfer private key material. It defeats the entire point of the SEP.
see:
https://developer.apple.com/documentation/security/ksecattrtokenidsecureenclave
https://support.apple.com/en-ng/guide/security/sec59b0b31ff/web

Verify signature of JWT token using iOS swift4

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)

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.

Can I get the modulus or exponent from a SecKeyRef object in Swift?

In Swift, I created a SecKeyRef object by calling SecTrustCopyPublicKey on some raw X509 certificate data. This is what this SecKeyRef object looks like.
Optional(<SecKeyRef algorithm id: 1,
key type: RSAPublicKey,
version: 3, block size: 2048 bits,
exponent: {hex: 10001, decimal: 65537},
modulus: <omitted a bunch of hex data>,
addr: 0xsomeaddresshere>)
Basically, this SecKeyRef object holds a whole bunch of information about the public key, but there seems to be no way to actually convert this SecKeyRef into a string, NSData, or anything else (this is my goal, is just to get a base64 public key).
However, I have a function that I can give a modulus and an exponent, and it will just calculate what the public key is. I've tested it by passing in the data that's logged from the above SecKeyRef.
But somehow I can't access those properties from the SecKeyRef object (I can only see the whole object in the console; for example, I cannot do SecKeyRef.modulus or anything of the sort, it seems).
My question: how can I access SecKeyRef.modulus, or alternatively, convert this SecKeyRef into NSData or something similar? Thanks
Edit
(for more information)
I am creating my SecKeyRef dynamically, through this function I have:
func bytesToPublicKey(certData: NSData) -> SecKeyRef? {
guard let certRef = SecCertificateCreateWithData(nil, certData) else { return nil }
var secTrust: SecTrustRef?
let secTrustStatus = SecTrustCreateWithCertificates(certRef, nil, &secTrust)
if secTrustStatus != errSecSuccess { return nil }
var resultType: SecTrustResultType = UInt32(0) // result will be ignored.
let evaluateStatus = SecTrustEvaluate(secTrust!, &resultType)
if evaluateStatus != errSecSuccess { return nil }
let publicKeyRef = SecTrustCopyPublicKey(secTrust!)
return publicKeyRef
}
What that does is takes the raw byte stream from a certificate (which can be broadcasted from, say, a piece of hardware using PKI), and then turns that into a SecKeyRef.
Edit 2
(comments on existing answers as of 7 January 2015)
This does not work:
let mirror = Mirror(reflecting: mySecKeyObject)
for case let (label?, value) in mirror.children {
print (label, value)
}
This results in this output in the console:
Some <Raw SecKeyRef object>
Not sure what the string "Some" means.
Additionally, mirror.descendant("exponent") (or "modulus") results in nil, even though when printing the raw object in the console, I can clearly see that those properties exist, and that they are in fact populated.
Also, if at all possible, I would like to avoid having to save to the keychain, reading as NSData, and then deleting from the keychain. As stated in the bounty description, if this is the only way possible, please cite an authoritative reference. Thank you for all answers provided so far.
It is indeed possible to extract modulus and exponent using neither keychains nor private API.
There is the (public but undocumented) function SecKeyCopyAttributes which extracts a CFDictionary from a SecKey.
A useful source for attribute keys is SecItemConstants.c
Inspecting the content of this dictionary, we find an entry "v_Data" : <binary>. Its content is DER-encoded ASN for
SEQUENCE {
modulus INTEGER,
publicExponent INTEGER
}
Be aware that integers are padded with a zero byte if they are positive and have a leading 1-bit (so as not to confuse them with a two-complement negative number), so you may find one byte more than you expect. If that happens, just cut it away.
You can implement a parser for this format or, knowing your key size, hard-code the extraction. For 2048 bit keys (and 3-byte exponent), the format turns out to be:
30|82010(a|0) # Sequence of length 0x010(a|0)
02|82010(1|0) # Integer of length 0x010(1|0)
(00)?<modulus>
02|03 # Integer of length 0x03
<exponent>
For a total of 10 + 1? + 256 + 3 = 269 or 270 bytes.
import Foundation
extension String: Error {}
func parsePublicSecKey(publicKey: SecKey) -> (mod: Data, exp: Data) {
let pubAttributes = SecKeyCopyAttributes(publicKey) as! [String: Any]
// Check that this is really an RSA key
guard Int(pubAttributes[kSecAttrKeyType as String] as! String)
== Int(kSecAttrKeyTypeRSA as String) else {
throw "Tried to parse non-RSA key as RSA key"
}
// Check that this is really a public key
guard Int(pubAttributes[kSecAttrKeyClass as String] as! String)
== Int(kSecAttrKeyClassPublic as String)
else {
throw "Tried to parse non-public key as public key"
}
let keySize = pubAttributes[kSecAttrKeySizeInBits as String] as! Int
// Extract values
let pubData = pubAttributes[kSecValueData as String] as! Data
var modulus = pubData.subdata(in: 8..<(pubData.count - 5))
let exponent = pubData.subdata(in: (pubData.count - 3)..<pubData.count)
if modulus.count > keySize / 8 { // --> 257 bytes
modulus.removeFirst(1)
}
return (mod: modulus, exp: exponent)
}
(I ended up writing a full ASN parser, so this code is not tested, beware!)
Note that you can extract details of private keys in very much the same way. Using DER terminology, this is the format of v_Data:
PrivateKey ::= SEQUENCE {
version INTEGER,
modulus INTEGER, -- n
publicExponent INTEGER, -- e
privateExponent INTEGER, -- d
prime1 INTEGER, -- p
prime2 INTEGER, -- q
exponent1 INTEGER, -- d mod (p-1) (dmp1)
exponent2 INTEGER, -- d mod (q-1) (dmq1)
coefficient INTEGER, -- (inverse of q) mod p (coeff)
otherPrimeInfos OtherPrimeInfos OPTIONAL
}
Parsing this by hand is probably ill-advised since any of the integers may have been padded.
Nota bene: The format of the public key is different if the key has been generated on macOS; the structure given above is wrapped like so:
SEQUENCE {
id OBJECTID,
PublicKey BITSTRING
}
The bit-string is DER-encoded ASN of the form above.
Update The answer below might result in your app being rejected due to usage of non-public API's.
The answer lies in the SecRSAKey.h file from Apple's opensource website (Security is part of the code that Apple opensourced). The file is not big, and among other stuff it declares the following two important functions:
CFDataRef SecKeyCopyModulus(SecKeyRef rsaPublicKey);
CFDataRef SecKeyCopyExponent(SecKeyRef rsaPublicKey);
You can add those functions to your bridging header to be able to call them from Swift, also while doing this you can switch from CFDataRef to NSData* as the two types toll-free bridged:
NSData* SecKeyCopyModulus(SecKeyRef rsaPublicKey);
NSData* SecKeyCopyExponent(SecKeyRef rsaPublicKey);
Demo Swift usage:
let key = bytesToPublicKey(keyData)
let modulus = SecKeyCopyModulus(key)
let exponent = SecKeyCopyExponent(key)
print(modulus, exponent)
This is a private API though, and there might be a chance it will no longer be available at some point, however I looked over the versions of Security made public (http://www.opensource.apple.com/source/Security), and looks like the two functions are present in all of them. More, since Security is a critical component of the OS, it's unlikely Apple will do major changes over it.
Tested on iOS 8.1, iOS 9.2, and OSX 10.10.5, and the code works on all three platforms.
I've been down the same path trying to do SSL Public Key Pinning. The API's are pretty much non-existent, and the solution I found was to put it in the Keychain which you can then retrieve as NSData (which can then be Base64 Encoded). It's horrible but the only thing I could find after a day or so of research (without resorting to bundling OpenSSL with my app).
I ported some of my code over to Swift, but I haven't tested it very much so I'm not 100% sure that it works: https://gist.github.com/chedabob/64a4cdc4a1194d815814
It's based off this Obj-C code (which I'm confident works as it's in a production app): https://gist.github.com/chedabob/49eed109a3dfcad4bd41
I found how to get data for a SecKey.
let publicKey: SecKey = ...
let data = SecKeyCopyExternalRepresentation(publicKey, nil)
This seems to work well and I have been able to successfully compare public keys.
This is in Swift 3 (Xcode 8 beta 3)
SecKeyRefis a struct so there is a chance that it can be reflected with Mirror() to retrieve the wanted values.
struct myStruct {
let firstString = "FirstValue"
let secondString = "SecondValue"}
let testStruct = myStruct()
let mirror = Mirror(reflecting: testStruct)
for case let (label?, value) in mirror.children {
print (label, value)
}
/**
Prints:
firstString FirstValue
secondString SecondValue
*/
I've found a single Obj-c re-implementation of the ASN.1 parser in an abandoned project, that appears to work. Problem is, it uses a great deal of pointer tricks that I don't know how to translate into Swift (not even sure some of it is possible). It should be possible to create a swift friendly wrapper around it, since the only input it takes is the NSData.
Everything on the net is using the store and retrieve in the Keychain trick to get to the pub key data, even really popular libs like TrustKit. I found reference in the Apple docs on SecKeyRef to the root cause (I think):
A SecKeyRef object for a key that is stored in a keychain can be
safely cast to a SecKeychainItemRef for manipulation as a keychain
item. On the other hand, if the SecKeyRef is not stored in a keychain,
casting the object to a SecKeychainItemRef and passing it to Keychain
Services functions returns errors.
Since SecCertificateCopyValues isn't available on iOS at this time, you're limited to either parsing the certificate data, or doing the Keychain Item shuffle.
Did you think about using SecCertificateCopyData()? The resulting CFData is toll-Free bridged, I think.
Refer to https://developer.apple.com/library/ios/documentation/Security/Reference/certifkeytrustservices/ to see the relevant documentation of the API.
From How do I encode an unmanaged<SecKey> to base64 to send to another server? :
func convertSecKeyToBase64(inputKey: SecKey) ->String? {
// Add to keychain
let tempTag = "net.example." + NSUUID().UUIDString
let addParameters :[String:AnyObject] = [
String(kSecClass): kSecClassKey,
String(kSecAttrApplicationTag): tempTag,
String(kSecAttrKeyType): kSecAttrKeyTypeRSA,
String(kSecValueRef): inputKey,
String(kSecReturnData):kCFBooleanTrue
]
var result: String?
var keyPtr: AnyObject?
if (SecItemAdd(addParameters, &keyPtr) == noErr) {
let data = keyPtr! as! NSData
result = data.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0))
}
// Remove from Keychain:
SecItemDelete(addParameters)
return result
}
But if you want to avoid adding to keychain, you can use Mirror:
let mirrorKey = Mirror(reflecting: secKey)
let exponent = mirrorKey.descendant("exponent")
let modulus = mirrorKey.descendant("modulus");
[edit: Mirror not working according to Josh]
I wrote this one base on some other's answer in stackoverflow. Currently I am using it in my production but I am happy to use another solution that doesn't require to write into keychain.
- (NSData *)getPublicKeyBitsFromKey:(SecKeyRef)givenKey host:(NSString*)host {
NSString *tag = [NSString stringWithFormat:#"%#.%#",[[NSBundle mainBundle] bundleIdentifier], host];
const char* publicKeyIdentifier = [tag cStringUsingEncoding:NSUTF8StringEncoding];
NSData *publicTag = [[NSData alloc] initWithBytes:publicKeyIdentifier length:strlen(publicKeyIdentifier) * sizeof(char)];
OSStatus sanityCheck = noErr;
// NSData * publicKeyBits = nil;
CFTypeRef publicKeyBits;
NSMutableDictionary * queryPublicKey = [[NSMutableDictionary alloc] init];
// Set the public key query dictionary.
[queryPublicKey setObject:(id)kSecClassKey forKey:(id)kSecClass];
[queryPublicKey setObject:publicTag forKey:(id)kSecAttrApplicationTag];
[queryPublicKey setObject:(id)kSecAttrKeyTypeRSA forKey:(id)kSecAttrKeyType];
[queryPublicKey setObject:[NSNumber numberWithBool:YES] forKey:(id)kSecReturnData];
[queryPublicKey setObject:(__bridge id)givenKey forKey:(__bridge id)kSecValueRef];
// Get the key bits.
NSData *data = nil;
sanityCheck = SecItemCopyMatching((CFDictionaryRef)queryPublicKey, &publicKeyBits);
if (sanityCheck == errSecSuccess) {
data = CFBridgingRelease(publicKeyBits);
//I don't want to leak this information
(void)SecItemDelete((__bridge CFDictionaryRef) queryPublicKey);
}else {
sanityCheck = SecItemAdd((CFDictionaryRef)queryPublicKey, &publicKeyBits);
if (sanityCheck == errSecSuccess)
{
data = CFBridgingRelease(publicKeyBits);
(void)SecItemDelete((__bridge CFDictionaryRef) queryPublicKey);
}
}
return data;
}

Resources