I have a pem file with a public key that was creted by a server and I need to import this key to SecKey to use in my iOS app.
I have already added the file to the resources folder and I can read it from the Bundle.main.
I am trying to read the file and then get the SecKey from its data using SecKeyCreateWithData.
I have tried reading the data as NSData and using it as the parameters for SecKeyCreateWithData, but I was getting nil as the result.
I have then read that it is necessary to strip the header and tails strings "-----BEGIN PUBLIC KEY-----" and "-----END PUBLIC KEY-----" and then encode the string to NSData and use it for creating the key. But I was still not successful with that.
I have also read that files generated in Windows could have extra end of line chars, so I trying to remove them as well, but I am still getting nil as a result.
Can anyone please give me some advice on how I should import the public key?
This is the code I have at the moment:
if let filepath = Bundle.main.path(forResource: "PublicKey", ofType: "pem") {
do {
var contents = try String(contentsOfFile: filepath)
print(contents.debugDescription)
// remove the header string
let offset = String("-----BEGIN PUBLIC KEY-----").count
let index = contents.index(contents.startIndex, offsetBy: offset+1)
contents = String(contents.suffix(from: index))
// remove end of line chars
contents = contents.replacingOccurrences(of: "\n", with: "")
// remove the tail string
let tailWord = "-----END PUBLIC KEY-----"
if let lowerBound = contents.range(of: tailWord)?.lowerBound {
contents = String(contents.prefix(upTo: lowerBound))
}
print(contents.debugDescription)
let data = NSData(base64Encoded: contents,
options:NSData.Base64DecodingOptions.ignoreUnknownCharacters)!
var publicKey: SecKey?
let attributes = [kSecAttrKeyType: kSecAttrKeyTypeRSA] as CFDictionary
let error: UnsafeMutablePointer<Unmanaged<CFError>?>? = nil
publicKey = SecKeyCreateWithData(data, attributes, error)
print(error.debugDescription)
print(publicKey.debugDescription)
} catch {
print(error)
}
Be aware that this is only a problem on iOS; on macOS there are APIs that can import PEMs directly (see
<Security/SecImportExport.h>
). Alas, these are not present on iOS. I encourage you to file an
enhancement request for better APIs here.
Before you go down this path I’m going to suggest a much easier alternative, namely, to have your server-side code wrap the public key in a certificate. Extracting a public key from a certificate is very easy, and it’s much nicer than what I’m going to suggest below.
Moreover, doing this wrapping on the server is also relatively easy, because the server has access to full-feature security toolkit.
Importing a PEM public key on iOS involves fours steps:
Stripping the PEM header and footer.
Decoding the Base64, which gives you a DER-encoded ASN.1
structure (per RFC 3280).SubjectPublicKeyInfo
Unwrapping the
structure to get the underlyingSubjectPublicKeyInfo
bits (for RSA this will be ansubjectPublicKey
, for EC it’s a raw EC key).RSAPublicKey
Passing that to
.SecKeyCreateWithData
This is all fairly straightforward with the exception of step 3. You can do step 3 in two ways:
Using a fully-features ASN.1 library (A)
Hacking on the bytes directly (B)
Obviously A would be better, but iOS does not provide such a library so you have to either write or acquire this code. B, on the other hand, is kinda ugly but might work for your needs.
Now let’s look at B in detail. Here’s a typical public key PEM:
$ cat PublicKey.pem
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArih9Y8pSAu71t00Tpl4q
xGNoKNPo60q1dGWP3LuaTlfRyVu9WcKXK8MlVeU17Yb4+iQKyHylLRFBZiTV4F9Q
lvDuewX/S8S51Rb4AIGl2uHdJu/bKQbNAd8gJ4DdupTJD3poYm06QBJzc33uX5Pd
Ak2UlDThpGc7Ee5fsh+dv/1hTwMfHzB0uu0t1IrCDroNX3gIeqglCsvQIoVgXwDD
oF3***8dy7GaTYmE3JiiAhmx7PFPLrQCoRhjcgs3JiiHfH2mDlhgzTjSEa5+TRs+
R56j7WJK80AwsoIrnShuB+JCY/kFp+LGkpk7Tpd06H2bOzxzrCBxItFOKzGvEdWp
uQIDAQAB
-----END PUBLIC KEY-----
If you decode the Base64 you get this:
$ hexdump -v SubjectPublicKeyInfo.der
0000000 30 82 01 22 30 0d 06 09 2a 86 48 86 f7 0d 01 01
0000010 01 05 00 03 82 01 0f 00 30 82 01 0a 02 82 01 01
0000020 00 ae 28 7d 63 ca 52 02 ee f5 b7 4d 13 a6 5e 2a
0000030 c4 63 68 28 d3 e8 eb 4a b5 74 65 8f dc bb 9a 4e
0000040 57 d1 c9 5b bd 59 c2 97 2b c3 25 55 e5 35 ed 86
0000050 f8 fa 24 0a c8 7c a5 2d 11 41 66 24 d5 e0 5f 50
0000060 96 f0 ee 7b 05 ff 4b c4 b9 d5 16 f8 00 81 a5 da
0000070 e1 dd 26 ef db 29 06 cd 01 df 20 27 80 dd ba 94
0000080 c9 0f 7a 68 62 6d 3a 40 12 73 73 7d ee 5f 93 dd
0000090 02 4d 94 94 34 e1 a4 67 3b 11 ee 5f b2 1f 9d bf
00000a0 fd 61 4f 03 1f 1f 30 74 ba ed 2d d4 8a c2 0e ba
00000b0 0d 5f 78 08 7a a8 25 0a cb d0 22 85 60 5f 00 c3
00000c0 a0 5d df b8 2f 1d cb b1 9a 4d 89 84 dc 98 a2 02
00000d0 19 b1 ec f1 4f 2e b4 02 a1 18 63 72 0b 37 26 28
00000e0 87 7c 7d a6 0e 58 60 cd 38 d2 11 ae 7e 4d 1b 3e
00000f0 47 9e a3 ed 62 4a f3 40 30 b2 82 2b 9d 28 6e 07
0000100 e2 42 63 f9 05 a7 e2 c6 92 99 3b 4e 97 74 e8 7d
0000110 9b 3b 3c 73 ac 20 71 22 d1 4e 2b 31 af 11 d5 a9
0000120 b9 02 03 01 00 01
0000126
Which you can dump with dumpasn1 to get this:
$ dumpasn1 -a SubjectPublicKeyInfo.der
0 290: SEQUENCE {
4 13: SEQUENCE {
6 9: OBJECT IDENTIFIER rsaEncryption (1 2 840 113549 1 1 1)
17 0: NULL
: }
19 271: BIT STRING, encapsulates {
24 266: SEQUENCE {
28 257: INTEGER
: 00 AE 28 7D 63 CA 52 02 EE F5 B7 4D 13 A6 5E 2A
: C4 63 68 28 D3 E8 EB 4A B5 74 65 8F DC BB 9A 4E
: 57 D1 C9 5B BD 59 C2 97 2B C3 25 55 E5 35 ED 86
: F8 FA 24 0A C8 7C A5 2D 11 41 66 24 D5 E0 5F 50
: 96 F0 EE 7B 05 FF 4B C4 B9 D5 16 F8 00 81 A5 DA
: E1 DD 26 EF DB 29 06 CD 01 DF 20 27 80 DD BA 94
: C9 0F 7A 68 62 6D 3A 40 12 73 73 7D EE 5F 93 DD
: 02 4D 94 94 34 E1 A4 67 3B 11 EE 5F B2 1F 9D BF
: FD 61 4F 03 1F 1F 30 74 BA ED 2D D4 8A C2 0E BA
: 0D 5F 78 08 7A A8 25 0A CB D0 22 85 60 5F 00 C3
: A0 5D DF B8 2F 1D CB B1 9A 4D 89 84 DC 98 A2 02
: 19 B1 EC F1 4F 2E B4 02 A1 18 63 72 0B 37 26 28
: 87 7C 7D A6 0E 58 60 CD 38 D2 11 AE 7E 4D 1B 3E
: 47 9E A3 ED 62 4A F3 40 30 B2 82 2B 9D 28 6E 07
: E2 42 63 F9 05 A7 E2 C6 92 99 3B 4E 97 74 E8 7D
: 9B 3B 3C 73 AC 20 71 22 D1 4E 2B 31 AF 11 D5 A9
: B9
289 3: INTEGER 65537
: }
: }
: }
0 warnings, 0 errors.
This is a
SubjectPublicKeyInfo
structure and, within that, starting on line 8, is an encapsulated
RSAPublicKey
structure. Happily,
dumpasn1
provides us with the offset of that element, namely 24. If we remove the first 24 bytes from
SubjectPublicKeyInfo.der
we get this:
$ hexdump -v RSAPublicKey.der
0000000 30 82 01 0a 02 82 01 01 00 ae 28 7d 63 ca 52 02
0000010 ee f5 b7 4d 13 a6 5e 2a c4 63 68 28 d3 e8 eb 4a
0000020 b5 74 65 8f dc bb 9a 4e 57 d1 c9 5b bd 59 c2 97
0000030 2b c3 25 55 e5 35 ed 86 f8 fa 24 0a c8 7c a5 2d
0000040 11 41 66 24 d5 e0 5f 50 96 f0 ee 7b 05 ff 4b c4
0000050 b9 d5 16 f8 00 81 a5 da e1 dd 26 ef db 29 06 cd
0000060 01 df 20 27 80 dd ba 94 c9 0f 7a 68 62 6d 3a 40
0000070 12 73 73 7d ee 5f 93 dd 02 4d 94 94 34 e1 a4 67
0000080 3b 11 ee 5f b2 1f 9d bf fd 61 4f 03 1f 1f 30 74
0000090 ba ed 2d d4 8a c2 0e ba 0d 5f 78 08 7a a8 25 0a
00000a0 cb d0 22 85 60 5f 00 c3 a0 5d df b8 2f 1d cb b1
00000b0 9a 4d 89 84 dc 98 a2 02 19 b1 ec f1 4f 2e b4 02
00000c0 a1 18 63 72 0b 37 26 28 87 7c 7d a6 0e 58 60 cd
00000d0 38 d2 11 ae 7e 4d 1b 3e 47 9e a3 ed 62 4a f3 40
00000e0 30 b2 82 2b 9d 28 6e 07 e2 42 63 f9 05 a7 e2 c6
00000f0 92 99 3b 4e 97 74 e8 7d 9b 3b 3c 73 ac 20 71 22
0000100 d1 4e 2b 31 af 11 d5 a9 b9 02 03 01 00 01
000010e
$
Which dumps as this:
$ dumpasn1 -a RSAPublicKey.der
0 266: SEQUENCE {
4 257: INTEGER
: 00 AE 28 7D 63 CA 52 02 EE F5 B7 4D 13 A6 5E 2A
: C4 63 68 28 D3 E8 EB 4A B5 74 65 8F DC BB 9A 4E
: 57 D1 C9 5B BD 59 C2 97 2B C3 25 55 E5 35 ED 86
: F8 FA 24 0A C8 7C A5 2D 11 41 66 24 D5 E0 5F 50
: 96 F0 EE 7B 05 FF 4B C4 B9 D5 16 F8 00 81 A5 DA
: E1 DD 26 EF DB 29 06 CD 01 DF 20 27 80 DD BA 94
: C9 0F 7A 68 62 6D 3A 40 12 73 73 7D EE 5F 93 DD
: 02 4D 94 94 34 E1 A4 67 3B 11 EE 5F B2 1F 9D BF
: FD 61 4F 03 1F 1F 30 74 BA ED 2D D4 8A C2 0E BA
: 0D 5F 78 08 7A A8 25 0A CB D0 22 85 60 5F 00 C3
: A0 5D DF B8 2F 1D CB B1 9A 4D 89 84 DC 98 A2 02
: 19 B1 EC F1 4F 2E B4 02 A1 18 63 72 0B 37 26 28
: 87 7C 7D A6 0E 58 60 CD 38 D2 11 AE 7E 4D 1B 3E
: 47 9E A3 ED 62 4A F3 40 30 B2 82 2B 9D 28 6E 07
: E2 42 63 F9 05 A7 E2 C6 92 99 3B 4E 97 74 E8 7D
: 9B 3B 3C 73 AC 20 71 22 D1 4E 2B 31 AF 11 D5 A9
: B9
265 3: INTEGER 65537
: }
0 warnings, 0 errors.
This is what you need to pass to
SecKeyCreateWithData
when importing an RSA key.
If you want to attempt B you’ll have to grab some sample public keys from your server and dump them as shown above. Hopefully they’ll all include the
RSAPublicKey
at a fixed offset, in which case you can just remove that many bytes from the front of your data before passing it to
SecKeyCreateWithData
.
It is possible, however, that the public keys returned by your server don’t always have the encapsulated
RSAPublicKey
at a fixed offset (the
SubjectPublicKeyInfo
structure supports optional algorithm parameters that can be populated in weird ways) and, in that case, you’ll need to get more sophisticated about parsing this data (possibly switching to option A).
Finally, the above should illustrate why I recommend that you import a certificate rather than a public key!
Share and Enjoy
—
Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware
let myEmail = "eskimo" + "1" + "@apple.com"