Santiago Alvarez
Santiago Alvarez

Reputation: 306

Swift Load RSA Public Key from String (MacOS)

i was wondering how could i load a RSA Public Key from a String and encrypt another string with it. The public key is something like:

let key = """ -----BEGIN PUBLIC KEY----- blah blah blah -----END PUBLIC KEY-----"""

I would prefer to do this without an outside library but if it makes things easier im willing to use them. Thanks in advance.

Upvotes: 1

Views: 2462

Answers (1)

Bram
Bram

Reputation: 3264

The key data you have is PEM encoded. However, Apple supports DER encoding for their security API. So first we'll have to transform your key data to the correct format. The example below is created with a random RSA key exporting the public key to PEM format. PEM headers can differ from library to library, so be sure you remove the tags, before continuing to the DER transformation.

var pem = "-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQEAs6AofVx+UAXcVjnIU0Z5SAGO/LPlTunA9zi7jNDcIrZTR8ULHTrm\naSAg/ycNR1/wUeac617RrFeQuoPSWjhZPRJrMa3faVMCqTgV2AmaPgKnPWBrY2ir\nGhnCnIAvD3sitCEKultjCstrTA71Jo/BuVaj6BVgaA/Qn3U9mQ+4JiEFiTxy4kOF\nes1/WwTLjRQYVf42oG350bTKw9F0MklTTZdiZKCQtc3op86A7VscFhwusY0CaZfB\nlRDnTgTMoUhZJpKSLZae93NVFSJY1sUANPZg8TzujqhRKt0g5HR/Ud61icvBbcx8\n+a3NzmuwPylvp5m6hz/l14Y7UZ8UT5deywIDAQAB\n-----END RSA PUBLIC KEY-----\n"

// Remove headers and footers from the PEM, leaving us with DER encoded data split by new lines
[
    "-----BEGIN RSA PUBLIC KEY-----", "-----END RSA PUBLIC KEY-----",
    "-----BEGIN PUBLIC KEY-----", "-----END PUBLIC KEY-----"
].forEach { pem = pem.replacingOccurrences(of: $0, with: "") }

// Construct DER data from the remaining PEM data
let der = Data(base64Encoded: pem, options: .ignoreUnknownCharacters)!

Now that we have our DER encoded data, it's time to construct our key. Firstly, create the attributes describing the key, after that create the key from the DER data. Note here how der is of type Data not of type String. Generally speaking, crypto operations occur on Data. The security API however uses CFData, but one can easily exchange them (as CFData or as Data). The same goes for the attribute dictionary.

// Key generation attributes
let attributes: [String: Any] = [
    String(kSecAttrKeyType): kSecAttrKeyTypeRSA,
    String(kSecAttrKeyClass): kSecAttrKeyClassPublic,
    String(kSecAttrKeySizeInBits): der.count * 8
]

// For simplicity I force unwrap here. The nil parameter can be used to extract an error
let key = SecKeyCreateWithData(der as CFData, attributes as CFDictionary, nil)!

Now that we have our key, we can use it to encrypt. Be aware however that RSA cannot encrypt huge amounts of data (not technically true, you could create chunks of data and do it that way, but RSA is not intended for this. If you want to do such thing, read up on key exchange and symmetric encryption. RSA is not intended for that behaviour). Also note that OAEP (as used in the example) has a random factor to it. Meaning the cipher text output will differ each time you run this code. This doesn't mean it's not working, it's simply a property OAEP has. I'd also like to point out that PKCS1 padding should be avoided when able in favour of OAEP.

// An example message to encrypt
let plainText = "This is my secret".data(using: .utf8)!
    
// Perform the actual encryption
// Again force unwrapping for simplicity
let cipherText = SecKeyCreateEncryptedData(key, .rsaEncryptionOAEPSHA256, plainText as CFData, nil)! as Data

In the example above .rsaEncryptionOAEPSHA256 is used. This is one of the available encryption algorithms for RSA:

  • .rsaEncryptionRaw
  • .rsaEncryptionPKCS1
  • .rsaEncryptionOAEPSHA1
  • .rsaEncryptionOAEPSHA224
  • .rsaEncryptionOAEPSHA256
  • .rsaEncryptionOAEPSHA384
  • .rsaEncryptionOAEPSHA512

I highly recommend using one of the OAEP variants, but that is up to you. I hope this helps. The SecKeyCreateEncryptedData is available for macOS since 10.12. You can read more about it here.

Upvotes: 2

Related Questions