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.
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.
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.
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:
BYO openssl
Generate some certificates for testing.
Make sense of the openssl documentation
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.