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

后端 未结 3 2021
梦如初夏
梦如初夏 2021-01-17 10:29

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 da

相关标签:
3条回答
  • 2021-01-17 11:05

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

    0 讨论(0)
  • 2021-01-17 11:11

    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

    0 讨论(0)
  • 2021-01-17 11:11

    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.

    0 讨论(0)
提交回复
热议问题