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

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!

Related

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.

Convert base64-encoded string to normal string in swift 3

I want to convert following base64-encoded String in Swift 3:
dfYcSGpvBqyzvkAXkdbHDA==
to its equivalant String:
uöHjo¬³¾#‘ÖÇ
Following websites do the job very fine:
http://www.motobit.com/util/base64-decoder-encoder.asp
http://www.utilities-online.info/base64/#.WG-FwrFh2Rs
So does the PHP's function base64_decode. The documentation of this function says:
Returns FALSE if input contains character from outside the base64
alphabet.
But I am unable to do the same in Swift 3. Following code doesn't do the job too:
func convertBase64ToNormalString(base64String:String)->String!{
let decodedData = Data(base64Encoded: base64String, options: Data.Base64DecodingOptions())
let bytes = decodedData?.bytes
return String(bytes: bytes, encoding: NSUTF8StringEncoding)
}
Here is the contextual information about why I need to convert the base64 string into an string:
My Php developer wants me to send all APIs params values encrypted with AES algorithm. For that, I am using this lib.
He has given me AES key in Hex format (I mentioned in my last question) and iv in base64 (given above) and he instructed me decode this base64 key before using because he was also doing the same in his PHP code. Here is his PHP code of encryption and decryption:
function encryptParamAES($plaintext, $encryptionEnabled = true) {
if (! $encryptionEnabled) {
return $plaintext;
}
// --- ENCRYPTION ---
// Constants =========================================================
// the key should be random binary, use scrypt, bcrypt or PBKDF2 to
// convert a string into a key
// key is specified using hexadecimal
$key = pack ( 'H*', "dcb04a9e103a5cd8b53763051cef09bc66abe029fdebae5e1d417e2ffc2a07a4" );
// create a random IV to use with CBC encoding
$iv_size = 16;
$iv = base64_decode ( "dfYcSGpvBqyzvkAXkdbHDA==" );
// End Of constants ===================================================
// echo "IV: " . base64_encode ( $iv ) . "\n<br>";
// echo "IV Size: " . $iv_size . "\n<br>";
// show key size use either 16, 24 or 32 byte keys for AES-128, 192
// and 256 respectively
// $key_size = strlen ( $key );
// echo "Key size: " . $key_size . "\n<br><br>";
// creates a cipher text compatible with AES (Rijndael block size = 128)
// to keep the text confidential
// only suitable for encoded input that never ends with value 00h
// (because of default zero padding)
$ciphertext = mcrypt_encrypt ( MCRYPT_RIJNDAEL_128, $key, $plaintext, MCRYPT_MODE_CBC, $iv );
// prepend the IV for it to be available for decryption
$ciphertext = $iv . $ciphertext;
// encode the resulting cipher text so it can be represented by a string
$ciphertext_base64 = base64_encode ( $ciphertext );
return $ciphertext_base64;
}
function decryptParamAES($ciphertext_base64, $encryptionEnabled = true) {
if (! $encryptionEnabled) {
return $ciphertext_base64;
}
// --- DECRYPTION ---
// Constants =========================================================
// the key should be random binary, use scrypt, bcrypt or PBKDF2 to
// convert a string into a key
// key is specified using hexadecimal
$key = pack ( 'H*', "dcb04a9e103a5cd8b53763051cef09bc66abe029fdebae5e1d417e2ffc2a07a4" );
// create a random IV to use with CBC encoding
$iv_size = 16;
$iv = base64_decode ( "dfYcSGpvBqyzvkAXkdbHDA==" );
// End Of constants ===================================================
$ciphertext_dec = base64_decode ( $ciphertext_base64 );
// retrieves the IV, iv_size should be created using mcrypt_get_iv_size()
$iv_dec = substr ( $ciphertext_dec, 0, $iv_size );
// retrieves the cipher text (everything except the $iv_size in the front)
$ciphertext_dec = substr ( $ciphertext_dec, $iv_size );
// may remove 00h valued characters from end of plain text
$plaintext_dec = mcrypt_decrypt ( MCRYPT_RIJNDAEL_128, $key, $ciphertext_dec, MCRYPT_MODE_CBC, $iv_dec );
return rtrim ( $plaintext_dec );
}
I just saw this PHP code and wondered, why he is not using $iv as mcrypt_decrypt function's last param!! Will update you on it. But still question remains the same, PHP function base64_decode doesn't return FALSE for the above base64 string!
I tested this function myself by terminal command: php test.php. Here test.php contains following code:
<?php
$iv = base64_decode ( "dfYcSGpvBqyzvkAXkdbHDA==" );
echo $iv;
?>
And the output was: u?Hjo???#???
Looking at your revised question, you're trying to take this base-64 string, and using it as the iv in your AES algorithm. I can understand why you are wondering how to convert that resulting Data into a String, but you should not do that. Yes, there's a rendition of AES that expects the iv as a string. But there's another rendition that expects an Array<UInt8>. So, just like MartinR said in his answer to your other question, build an array of UInt8 instead, like so:
let iv = Array(Data(base64Encoded: "dfYcSGpvBqyzvkAXkdbHDA==")!)
That resulting iv is an Array<UInt8> (also known as [UInt8]). You can use that with your AES function.
My original discussion about converting Data objects to UTF8 strings is below. But the key message is that you shouldn't try to do so. Just build your array of UInt8 and use that with your library's AES function.
Looking at your other question (Convert hex-encoded String to String in Swift 3), you revealed in comments that you were dealing with an AES key. I'm suspicious that we're dealing with a similar issue here (though that was 32 bytes of data, and here we have 16 bytes).
Bottom line, I'd suggest you completely drop this "how to I get a string representation of the data captured in this base-64 string" line of inquiry. If it's an encryption key (or some token or whatever), don't bother trying to represent it as a string. (This is the raison d'être of base-64, to create transmittable string representations of data that isn't a string.)
I'd suggest you step back and describe the broader problem that you are trying to solve. Stop trying to create strings out of these binary payloads. In your code snippet, you successfully create a Data from the base-64 string. The real question, I think, is not "how do I now get a string from that?", but rather "what do I do this Data?"
We can't answer that question without more context about where you got this data and what it is for.
By the way, my original answer to your question is below.
The problem is that base-64 string translates to 16 bytes of data whose hexadecimal representation is
75f61c48 6a6f06ac b3be4017 91d6c70c
But that payload is not valid UTF8 string. The third byte, 1c is not consistent with UTF8 string. If you look at the definition of UTF8, it says that if a byte is in the range of f6–fb, which the second byte is, that the character consists of that and the following two bytes, both of which should be in the range of 21–7e or a0–ff, which 1c is not.
So, this simply is not a valid UTF8 string. Clearly those sites you're using are not gracefully detecting/handling invalid UTF8 strings.
Perhaps the data in this base-64 string was converted from a string using a different encoding. Or, perhaps it was not originally a string at all. Not all binary payloads have clean string representations. Frankly, this is why we use base-64 representations in the first place, to come up with a text representation of a blob of data that is not a string.
If you provide more information about the source of the data contained in this base-64 string, we might be able to advise you further.

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