Working in Apple Swift for iOS. I have to generate this for the backend as it's a secure app.
I'm new to security and certificates and have been searching for a day now with no results.
How can I generate base64 url-encoded X.509 format 2048-bit RSA public key with swift?
Any help is highly appreciated.
There's a library for handling public-private key pairs in Swift that I recently created called Heimdall, which allows you to easily export the X.509 formatted Base64 string of the public key.
To comply with SO rules, I will also include the implementation in this answer (so that it is self-explanatory)
public func X509PublicKey() -> NSString? {
// Fetch the key, so that key = NSData of the public key
let result = NSMutableData()
let encodingLength: Int = {
if key.length + 1 < 128 {
return 1
} else {
return ((key.length + 1) / 256) + 2
}
}()
let OID: [CUnsignedChar] = [0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00]
var builder: [CUnsignedChar] = []
// ASN.1 SEQUENCE
builder.append(0x30)
// Overall size, made of OID + bitstring encoding + actual key
let size = OID.count + 2 + encodingLength + key.length
let encodedSize = encodeLength(size)
builder.extend(encodedSize)
result.appendBytes(builder, length: builder.count)
result.appendBytes(OID, length: OID.count)
builder.removeAll(keepCapacity: false)
builder.append(0x03)
builder.extend(encodeLength(key.length + 1))
builder.append(0x00)
result.appendBytes(builder, length: builder.count)
// Actual key bytes
result.appendData(key)
// Convert to Base64 and make safe for URLs
var string = result.base64EncodedStringWithOptions(.allZeros)
string = string.stringByReplacingOccurrencesOfString("/", withString: "_")
string = string.stringByReplacingOccurrencesOfString("+", withString: "-")
return string
}
public func encodeLength(length: Int) -> [CUnsignedChar] {
if length < 128 {
return [CUnsignedChar(length)];
}
var i = (length / 256) + 1
var len = length
var result: [CUnsignedChar] = [CUnsignedChar(i + 0x80)]
for (var j = 0; j < i; j++) {
result.insert(CUnsignedChar(len & 0xFF), atIndex: 1)
len = len >> 8
}
return result
}
I have removed the data fetching code, refer to either source of Heimdall or Jeff Hay's answer
If the public key is already in your keychain, you can look up the public key and return the data as base64 with something similar to the following:
// Create dictionary to specify attributes for the key we're
// searching for. Swift will automatically bridge native values
// to to right types for the SecItemCopyMatching() call.
var queryAttrs = [NSString:AnyObject]()
queryAttrs[kSecClass] = kSecClassKey
queryAttrs[kSecAttrApplicationTag] = publicKeyTag
queryAttrs[kSecAttrKeyType] = kSecAttrKeyTypeRSA
queryAttrs[kSecReturnData] = true
var publicKeyBits = Unmanaged<AnyObject>?()
SecItemCopyMatching(queryAttrs, &publicKeyBits)
// Work around a compiler bug with Unmanaged<AnyObject> types
// the following two lines should simply be
// let publicKeyData : NSData = publicKeyRef!.takeRetainedValue() as NSData
let opaqueBits = publicKeyBits?.toOpaque()
let publicKeyData = Unmanaged<NSData>.fromOpaque(opaqueBits).takeUnretainedValue()
let publicKeyBase64 = publicKeyData.base64EncodedData(NSDataBase64EncodingOptions.Encoding64CharacterLineLength)
If you need to generate the keypair and store it in the keychain, use something along these lines:
// Create dictionaries to specify key attributes. Swift will
// automatically bridge native values to to right types for the
// SecKeyGeneratePair() call.
var pairAttrs = [NSString:AnyObject]()
var privateAttrs = [NSString:AnyObject]()
var publicAttrs = [NSString:AnyObject]()
privateAttrs[kSecAttrIsPermanent] = true
privateAttrs[kSecAttrApplicationTag] = privateKeyTag
publicAttrs[kSecAttrIsPermanent] = true
publicAttrs[kSecAttrApplicationTag] = publicKeyTag
pairAttrs[kSecAttrKeyType] = kSecAttrKeyTypeRSA
pairAttrs[kSecAttrKeySizeInBits] = 2048
pairAttrs[(kSecPrivateKeyAttrs.takeUnretainedValue() as! String)] = privateAttrs
pairAttrs[(kSecPublicKeyAttrs.takeUnretainedValue() as! String)] = publicAttrs
var publicKeyPtr = Unmanaged<SecKey>?()
var privateKeyPtr = Unmanaged<SecKey>?()
let status = SecKeyGeneratePair(pairAttrs, &publicKeyPtr, &privateKeyPtr)
Note: publicKeyTag
and privateKeyTag
are strings used to identify the key in the keystore. Apple recommends reverse-dns notation (com.company.key.xxx), but as long as they are unique, all should be good.
Similar question with answer: Generate key pair on iphone and print to log as NSString
Although the answer there is in Objective-C, Apple reference shows that the functions (especially the most important one, SecKeyGeneratePair
) can also be called directly from Swift (as long as you can do the type conversions from all those UnsafeMutablePointers etc).