Generate base64 url-encoded X.509 format 2048-bit RSA public key with Swift?

大城市里の小女人 提交于 2019-12-01 15:11:43
Henri Normak

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

Jeff Hay

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.

Jaanus

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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!