Basic openssl RSA encrypt/decrypt example in Cocoa

Caveat – I’m building an OSX app for Maverick – this may work for iOS but I haven’t tried.

In a recent project I’ve had to build a small Cocoa app that decrypt a packaged document.

The document packaging is basically provided by a RSA public/private key encryption.

The encryption is generated by a website and I had zero issue doing this in PHP.. actually was insanely simple.

Then I had to do it in the reader app.. an osx cocoa app.

This was a lot more difficult. In fairness, I’m way more tech-savy with PHP than Cocoa but it’s still worth mentioning that I had never done this in either languages before but PHP has a well documented, simple wrapper around openssl.

In cocoa however, I hit multiple walls.

  1. There’s a lot of developer “noise” around this. Apple removed direct support for openssl recently and is pushing people to use their CommonCrypto service, which doesn’t include RSA, bummer.

  2. The “documentation” provided by openssl is… something. Not sure what level of insanity is required to take all this in, but that was hard. There’s pretty much zero guides on most topics too.

  3. There’s a lot of forum posts about people running into issues at different stages but often the thread doesn’t complete (common forum issue).

So the steps required were:

  1. BYO openssl

  2. Generate some certificates for testing.

  3. Make sense of the openssl documentation

  4. Build a test that can encrypt a string and decrypt it back to itself.

BYO openssl

This was actually relatively easy, there’s a Cocoa Pod for it.

Once you’ve created your project, add a Podfile with:

pod 'OpenSSL', '1.0.1'

then run pod install and follow the important instruction about opening via the created workspace (.xcworkspace file)

Generate some certificates for testing.

Generate private 2048 bit certificate

openssl genrsa -out private.pem 2048

Extract Public part

openssl rsa -in private.pem -pubout > public.pem

If you want a password protected private key (more on this later):

openssl genrsa -des3 -out private.pem 2048

Make sense of the openssl documentation

Ha ha.. hmmm.. can’t say I have totally grasped that.

Basically, the core of the documentation is here: http://www.openssl.org/docs/crypto/crypto.html

You can check out the sub-pages and weep some more.

I looked online for ages, opened X dozens of windows, with Y more dozens of tabs.

The breakthrough was the first library I could find that was simple enough make sense of, the NuCrypto library, which was created by the same person as the Nu language (never heard of it before, but do check it out http://programming.nu/). That library actually has a reasonably simple set of RSA related method.

Build a test

Extracting the vital informations from the NuCrypto library, I ended up with the following sample code.

**Note: ** For the purpose of my app, the certificate will be embedded as a c string in the app. There are openssl method to read from a file if you want to go on a hunt for them, or you can just read the file into a c string buffer and carry on with the example.

Here’s our key constant – just generated examples

char *public_key = "-----BEGIN PUBLIC KEY-----\n"
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwMu7BZF451FjUXYNr323\n"
"aeeaCW2a7s6eHHs8Gz5qgQ/zDegub6is3jwdTZJyGcRcN1DxKQsLcOa3F18KSiCk\n"
"yzIWjNV4YH7GdV7Ke2qLjcQUs7wktGUKyPYJmDWGYv/QN0Sbbol9IbeLjSBHUt16\n"
"xBex5IIpQqDtBy0RZvAMdUUB1rezKka0bC+b5CmE4ysIRFyFiweSlGsSdkaS9q1l\n"
"d+c/V4LMxljNbhdpfpiniWAD3lm9+mDJzToOiqz+nH9SHs4ClEThBAScI00xJH36\n"
"3mDvY0x6HVDyCsueC9jtfZKnI2uwM2tbUU4iDkCaIYm6VE6h1qs5AkrxH1o6K2lC\n"
"kQIDAQAB\n"
"-----END PUBLIC KEY-----\n";

char *private_key = "-----BEGIN RSA PRIVATE KEY-----\n"
"MIIEowIBAAKCAQEAwMu7BZF451FjUXYNr323aeeaCW2a7s6eHHs8Gz5qgQ/zDegu\n"
"b6is3jwdTZJyGcRcN1DxKQsLcOa3F18KSiCkyzIWjNV4YH7GdV7Ke2qLjcQUs7wk\n"
"tGUKyPYJmDWGYv/QN0Sbbol9IbeLjSBHUt16xBex5IIpQqDtBy0RZvAMdUUB1rez\n"
"Kka0bC+b5CmE4ysIRFyFiweSlGsSdkaS9q1ld+c/V4LMxljNbhdpfpiniWAD3lm9\n"
"+mDJzToOiqz+nH9SHs4ClEThBAScI00xJH363mDvY0x6HVDyCsueC9jtfZKnI2uw\n"
"M2tbUU4iDkCaIYm6VE6h1qs5AkrxH1o6K2lCkQIDAQABAoIBAChqzWNGcu0zb7nF\n"
"IOtYVJocFnvBgYhswlLANwKTHCrAWDjjItD/sHXKbm4ztD3Yn2htTJFJInXhuCJr\n"
"JzIRE9sRPg76NYktKpeybope9LCcmaZwW9WBlTg59Br3pZude14KwPb0Vco6u0Oz\n"
"r6AclD8FpKJ98v5n1Cj79rj4u/PdTXZP+Fmz8y0KAgM1s39rtiaAHKGCcyfb1awf\n"
"pCsYL0IvmU1Z00sPe5dzSqLY4HcIyT2kIMqnC0c0HTPtU6A5GI7dPMQsJy+ZEsWo\n"
"kR4YdymmN3C11gdd8kTpExdm1Ick5GfgUfc5hYYTBUO4n8bWJFJLxY6MtjdRfWHc\n"
"SBg4hw0CgYEA3vYaOzk8gUgUVkhnAsyoRNPkMKOH+EbOWuAssK3lQ2U8gvryJngz\n"
"KaQEGnluHvwK69BkJQQ5+PTMKhvhDZ4Ur9t6K7iZ3io3XLafH+jZk8alxYtZen00\n"
"Z38z2VQ8gjYHjfXGKNs1YGcfb+uJ5a8YMGbNjYTdGkIeWL0DLdrYH78CgYEA3V1R\n"
"fTPCY93kxOfYEnRvsO7HY6/4aESAMthROABd9IbYzmsA2Jkcs9Cns3MvWoLpbUY5\n"
"c36WDn9pZOg8vF0dday9Gr/ZrisEv7MgFl0FloyNsnGviHHFfoLPbOjEPUGXcRy2\n"
"1350nFJ2L0e9XcHgvPSjkmwcLbGgkrtWgjJoMa8CgYB0URPSPcPw9jeV4+PJtBc9\n"
"AQYU0duHjPjus/Dco3vtswzkkCJwK1kVqjlxzlPC2l6gM3FrVk8gMCWq+ixovEWy\n"
"kN+lm4K6Qm/rcGKHdSS9UW7+JfqiSltiexwDj0yZ6bH7P3MHsYShLGtcKhcguj32\n"
"Ukt+PwhSQJgwVzsnWvpRZQKBgQCDFrIdLLufHFZPbOR9+UnzQ1P8asb2KCqq8YMX\n"
"YNBC8GAPzToRCor+yT+mez29oezN81ouVPZT24v0X7sn6RR7DTJnVtl31K3ZQCBu\n"
"XePjRZTb6YsDiCxmQNzJKAaeJ+ug5lo4vwAbWpH2actwbFHEVDNRkIgXXysx+ZK/\n"
"Q06ErQKBgHzXwrSRWppsQGdxSrU1Ynwg0bIirfi2N8zyHgFutQzdkDXY5N0gRG7a\n"
"Xz8GFJecE8Goz8Mw2NigtBC4EystXievCwR3EztDyU5PgvEQV7d+0GLKtCG6QFqC\n"
"gZKlwzSf9rLhfXYCrWgqg7ZXsiaADQePw+fU2dudERxmg3gokBFL\n"
"-----END RSA PRIVATE KEY-----\n"

(this apparently – I’ve learned today too – is the most appropriate way to do a multiline c string)

Loading keys

To load both keys into a valid RSA object, you need to go through a BIO object (“an I/O abstraction” according to the openssl documentation).

For the public key, you can load it like this:

BIO *bio = BIO_new_mem_buf((void*)public_key, (int)strlen(public_key));
RSA *rsa_publickey = PEM_read_bio_RSA_PUBKEY(bio, NULL, 0, NULL);
BIO_free(bio);

For the private key, you can load it like this:

BIO *bio = BIO_new_mem_buf((void*)private_key, (int)strlen(private_key));
RSA *rsa_privatekey = PEM_read_bio_RSAPrivateKey(bio, NULL, 0, NULL);
BIO_free(bio);

That’s it, you have a valid RSA public key and private key object

Weird, alternative Version

I’ve seen (on many many occasions), this alternative version:

RSA *rsa_privatekey = RSA_new();
BIO *bio = BIO_new_mem_buf((void*)private_key, (int)strlen(private_key));
rsa_privatekey = PEM_read_bio_RSAPrivateKey(bio, &rsa_privatekey, 0, NULL);
BIO_free(bio);

I have no idea, why they do it like this – this seems rather leaky to me but maybe I’m missing something (again the documentation isn’t helpful).

Yes, PEM_read_bio_RSAPrivateKey does take a RSA **x value as its second argument but not sure what that does.

The point is, it works fine without doing this, so maybe best to avoid.. (please feel free to comment if you’re knowledgeable on this)

Note: See also Eddy Gammon’s comment below for some additional input.

Loading a private key with a password

If your private key is password protected, it’s fairly easy, the 4th parameter to PEM_read_bio_RSAPrivateKey is just for that.

BIO *bio = BIO_new_mem_buf((void*)private_key, (int)strlen(private_key));
char *password = "donkeysandunicorns";
RSA *rsa_privatekey = PEM_read_bio_RSAPrivateKey(bio, NULL, 0, password);
BIO_free(bio);

All done.

If you want to implement a system where the user is prompted for the password, I’m pretty sure this is what the 3rd parameter is for. Not interested in it for this example though.

Encrypting data with the public key

Say this is your data:

NSString *test = @"hello";
NSData *data = [test dataUsingEncoding:NSUTF8StringEncoding];

Here’s how you encrypt it with your private key

// Allocate a buffer   
int maxSize = RSA_size(rsa_publickey);
unsigned char *output = (unsigned char *) malloc(maxSize * sizeof(char));

// Fill buffer with encrypted data
int bytes = RSA_public_encrypt((int)[data length], [data bytes], output, rsa_publickey, RSA_PKCS1_PADDING);

// If you want a NSData object back
NSData *result = [NSData dataWithBytes:output length:bytes];

Decrypting the data back with the private key

Pretty easy too:

// Allocate a buffer
unsigned char *decrypted = (unsigned char *) malloc(1000);

// Fill buffer with decrypted data
RSA_private_decrypt(bytes, output, decrypted, rsa_privatekey, RSA_PKCS1_PADDING);

Note: here I’m just re-using bytes because I know it’s the right length from the encryption method but you should calculate it based on the input data.

That’s it, all done, decrypted should have the string "hello". Make sure you remember to de-alloc memory and what-not that you’ve used.

If it doesn’t work

The openssl function can cause errors. I particularly had problems working out the bio stuff.

What you should do for errors is this:

First make sure to load the string, the error message will make way more sense.

ERR_load_crypto_strings();

Then say PEM_read_bio_RSAPrivateKey returns NULL instead of a valid RSA object.

You can do this to get more info:

if (rsa_privatekey == NULL)
{
    char buffer[500];
    ERR_error_string(ERR_get_error(), buffer);
    NSLog(@"%@",[NSString stringWithUTF8String:buffer]);
}

You’ll then get an error like error:0906D064:PEM routines:PEM_read_bio:bad base64 decode

Unsupported encryption

If you get an error like this: error:0906B072:PEM routines:PEM_get_EVP_CIPHER_INFO:unsupported encryption you must call this first:

OpenSSL_add_all_algorithms();

Closing words

This isn’t so bad at the end really, but I really can’t thank the openssl documentation for this.

After finishing the test, I came across another library called libtomcrypt, which I haven’t tried but looks promising too.

One comment on “Basic openssl RSA encrypt/decrypt example in Cocoa

  1. Eddy Gammon

    In regards to using alternative way or reading-in the keys, the one tidbit I’ve managed to find about that is under here: https://wiki.openssl.org/index.php/Manual:Pem(3)#NOTES

    Essentially it says that using an uninitialised pointer to read the key into may fail. Hence the relatively common pattern of doing an RSA_new() beforehand and then passing across the pointer location.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *