In Swift, I created a SecKeyRef object by calling SecTrustCopyPublicKey on some raw X509 certificate data. This is what this SecKeyRef
object looks like.
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;
}
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)
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.
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]
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.
SecKeyRef
is 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
*/