Creating and exporting cryptograph Asymmetric Keys in Swift

Subscribe to my newsletter and never miss my upcoming articles

Hello fellow tech developers, Leo here.

This week we'll explore some old apple framework to create and export asymmetric keys. This is particularly important because you must need to secure communicate with some server and asymmetric keys are a common and safe way to do that.

If you don't know what asymmetric keys you need to check this first, and after continue reading.

No more talking, let's code.

The problem

I need to receive some information from other server that only my app could read it.

Create Private Key

First thing is to create a private key.

       let attributes: CFDictionary =
            [kSecAttrKeyType as String: kSecAttrKeyTypeRSA, // 1
             kSecAttrKeySizeInBits as String: 2048, // 2
             kSecPrivateKeyAttrs as String:
                 [kSecAttrIsPermanent as String: true, // 3
                  kSecAttrApplicationTag as String: tag ] // 4
            ] as CFDictionary

        var error: Unmanaged<CFError>?

        do {
            guard SecKeyCreateRandomKey(attributes, &error) != nil else { // 5
                throw error!.takeRetainedValue() as Error
            }
            print("key created")
            return true
        } catch {
            print(error.localizedDescription)
            return false
        }

All the code above is counterintuitive so let's explain what's going on. At (1) we are specifying the key type, in this case is a RSA) key and the (2) is just the key size, it's important to remember that the key size shouldn't be small because this weakens the whole purpose of cryptography things. With the actual 2020 computational power a 2048 key would take around 300 trillion years to break, so we can surely say it's safe.

The (3) and (4) are attributes of the private we're creating. On the (3) mark we are telling the method to guard the key create in the keychain this way the private key is strongly hardware protected. And the (4) we are giving a tag to later we can retrieve it.

The (5) SecKeyCreateRandomKey is func that do the magic for us. This function returns a SecKey, if you want to guard anywhere else, you should get the return of it, in this case we are storing it in the Apple's keychain.

In Swift we don't need to generate the public key at the moment we create the private key because we can create just when we need it.

Get the private key from keychain

To get the private key from the keychain it's like getting any other item of it. We first need to create a query and use the SecItemCopyMatching to retrieve the secKey like this:

        let query: CFDictionary = [kSecClass as String: kSecClassKey, //1
                                   kSecAttrApplicationTag as String: tag, // 2
                                   kSecAttrKeyType as String: kSecAttrKeyTypeRSA, //3
                                   kSecReturnRef as String: true] as CFDictionary //4

        var item: CFTypeRef?
        let status = SecItemCopyMatching(query, &item) //5
        guard status == errSecSuccess else {
            print("keychain don't have private key")
            return nil
        }

       privateKey = item as! SecKey //6

The (1,2,3,4) marks are the properties of the query that we are searching in the keychain. The (1) is the class of the item and it's a key. The (2) is the tag we set in the moment we created the private key. (3) mark is the keyType and the (4) we're telling to keychain to return a reference of that object to us.

Generate public key

To generate the public is somehow "easy" but it's important you to pay attention to the details.

        guard let publicKey = SecKeyCopyPublicKey(privateKey), //1
            let data = SecKeyCopyExternalRepresentation(publicKey, nil) else { //2
            return nil
        } 

        guard SecKeyIsAlgorithmSupported(publicKey, .encrypt, .rsaEncryptionPKCS1) //3
          else {
            print("not supported cryptography")
            return nil
        }

The (1) mark is creating a SecKey object, to use inside Swift's code and it's not exportable. Therefore we need the (2) to instruction to create an exportable copy of the key that we can export ( for example for a php server). It's important always export in a format data you won't lose data, example exporting transforming the Data to Base64 format you can do this:

        let finalData = data as Data //4

        return finalData.base64EncodedString()

A simples base64 would be fine to achieve that.

The complete example

The below shows how to create asymmetric keys and also how to perform an encrypt and decrypt operation with them.

        let textToEncrypt = "Swift is awesome"
        print("Crypto - text to encrypt - \(textToEncrypt)")
        let tag = "exampleTag"
        let algorithm: SecKeyAlgorithm = .rsaEncryptionOAEPSHA512


        let attributes: CFDictionary =
             [kSecAttrKeyType as String: kSecAttrKeyTypeRSA, // 1
              kSecAttrKeySizeInBits as String: 2048, // 2
              kSecPrivateKeyAttrs as String:
                  [kSecAttrIsPermanent as String: true, // 3
                   kSecAttrApplicationTag as String: tag] // 4
             ] as CFDictionary

         var error: Unmanaged<CFError>?

         do {
             guard SecKeyCreateRandomKey(attributes, &error) != nil else { // 5
                 throw error!.takeRetainedValue() as Error
             }
             print("key created")
         } catch {
             print(error.localizedDescription)
         }

        let query: CFDictionary = [kSecClass as String: kSecClassKey, //1
                                   kSecAttrApplicationTag as String: tag, // 2
                                   kSecAttrKeyType as String: kSecAttrKeyTypeRSA, //3
                                   kSecReturnRef as String: true] as CFDictionary //4

        var item: CFTypeRef?
        let status = SecItemCopyMatching(query, &item) //5
        guard status == errSecSuccess else {
            print("keychain don't have private key")
            return
        }

       let privateKey = item as! SecKey //6

        guard let publicKey = SecKeyCopyPublicKey(privateKey), //1
            let publicKeyExportable = SecKeyCopyExternalRepresentation(publicKey, nil) else { //2
            return
        }

        //check if the public key encrypt data
        guard SecKeyIsAlgorithmSupported(publicKey, .encrypt, algorithm) //3
          else {
            print("not supported cryptography")
            return
        }

        let publicKeyBase64Encoded = (publicKeyExportable as Data).base64EncodedString()
        print("Crypto - public key export = \(publicKeyBase64Encoded)")

        // the keys created we can now perform a example cryptograph operation

        let textToEncryptData = textToEncrypt.data(using: .utf8)!

        guard let cipherText = SecKeyCreateEncryptedData(publicKey,
                                                         algorithm,
                                                         textToEncryptData as CFData,
                                                         &error) as Data? else {
                                                            return
        }

        print("Crypto - encrypted text \(cipherText.base64EncodedString())")

        // check if the private key can decrypt
        guard SecKeyIsAlgorithmSupported(privateKey, .decrypt, algorithm) else {
            return
        }

        //check if the text size is compatible with the key size
        guard cipherText.count == SecKeyGetBlockSize(privateKey) else {
            return
        }

        //perform decrypt, the return is Data
        guard let clearTextData = SecKeyCreateDecryptedData(privateKey,
                                                        algorithm,
                                                        cipherText as CFData,
                                                        &error) as Data? else {
                                                            return
        }

        guard let clearText = String(data: clearTextData, encoding: .utf8) else { return }
        print("Crypto - decrypted text [\(clearText)]")

Very important

If your server is having problems reading your public key, you must read about public key formats like DER/PEM/etc. Java and PHP actually default read in DER format but Swift don't create public key data in that format, so you WILL have to do export to DER format. Just search for that.

Conclusion

Today we learned how to create RSA asymmetric keys to encrypt, decrypt and a method to export the public to send to others servers. I hope you enjoy as much I enjoyed writing this and any thoughts or feedbacks are always welcome.

Thanks for the reading and... That's all folks.

credit: image

No Comments Yet