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
(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
.
(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.