1

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.

Santiago Alvarez
  • 167
  • 1
  • 12

1 Answers1

1

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.

Bram
  • 2,718
  • 1
  • 22
  • 43
  • Thanks for your reply. I tried it but its giving me `error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0).` – Santiago Alvarez Dec 29 '20 at 13:17
  • I updated the answer to include error output feedback. Please let me know if and what the output on either of the functions is. – Bram Dec 29 '20 at 15:27
  • Still the same problem. Should i convert the derData string to a bytes array? The error comes from the SecKeyCreateWithData i think the `derData as! CFData`is what is causing the error but i dont know how to fix it – Santiago Alvarez Dec 29 '20 at 16:29
  • Try again with the new addition of PEM conversion. I highly recommend you reading up on what the encoding is and how it works for both PEM and DER. The updated answer is likely not very scalable, but feel free to give this a go – Bram Dec 29 '20 at 16:49
  • Still wont work. I really appreciatte your help but i think i'll try some other encryption method. I cant believe its so difficult to do somethiing so simple. Thanks. – Santiago Alvarez Dec 29 '20 at 17:03
  • Read carefully and think closely. It’s likely that the header and footer in the example are not the same as the header and footer in your PEM string, so they’re not removed. Try altering my example by removing “RSA” for instance. There is so much info and example code in front of you, you should be able to figure it out really. You issue now lies in PEM to DER encoding. Read up some posts about that too or try to understand what the difference between PEM and DER is. Look closely what needs to happen and try again, but think closely – Bram Dec 29 '20 at 17:15
  • Yes, all works fine as I know what I'm doing. PEM headers can differ from library to library. Read your code, understand your error. You're missing the details. Programming is about investigating and solving issues. The issue you started with is absolutely, 100% solved. Your next issue is PEM -> DER encoding. Again, plenty of SO articles are about this, as more people don't understand what it is and how it works. Read those, but more importantly, understand those. I've updated my answer once again. Hope it helps – Bram Dec 29 '20 at 18:14