Base64 Encoding in Cocoa
I needed to Base64 encode binary data in some Cocoa Objective-C code I was writing, but unfortunately this is not part of the standard library. I did find some code to do it. However, the license for the code was unclear. And did I really need an AliteVec implementation? CocoaDev has a wiki page about encoding and decoding to Base64. Most of that code didn’t look too robust, though. Someone mentioned using the openssl
command line tool, and even wrote up some code that spawns an NSTask
to invoke openssl
. That got me thinking… OS X ships with OpenSSL, so why not use the library directly?
A quick keyword search on the man
pages lead me to BIO_f_base64(3). It even had some code examples! Now I just needed to figure out how to use the Cocoa classes instead of C file streams, and I’d be all set. Since this is my first attempt at using OpenSSL, I’m going to walk through the Base64 encoding code. While not terribly complex, it may be of use to someone who’s new to Objective-C and/or new to interfacing Objective-C to Unix/C libraries. For the impatient who just want the code, I posted encoding and decoding implementations on the CocoaDev Base64 wiki page. Or download an Xcode 2.2 project, complete with unit tests. It’s released under an MIT license.
Since NSData
is the Cocoa class for holding binary data, I figured adding a category seemed like the perfect way to proceed. This effectively adds Base64 encoding methods to the NSData
class:
@interface NSData (Base64)
- (NSString *) encodeBase64;
- (NSString *) encodeBase64WithNewlines: (BOOL) encodeWithNewlines;
@end
For the implementation of encodeBase64WithNewlines:
I needed to figure out how to bridge NSData
and OpenSSL. OpenSSL has its own I/O abstraction layer called BIO, which is essentially a stream API written in C. The memory BIOs, which back to a memory buffer, are just what I need. So the first step is to create a memory BIO that will contain the encoded data:
- (NSString *) encodeBase64WithNewlines: (BOOL) encodeWithNewlines;
{
BIO * mem = BIO_new(BIO_s_mem());
Left like this, BIO_read()
and BIO_write()
will just copy bytes to the buffer, unmodified, which is not very useful. OpenSSL allows you to add and remove filters that modify data as it is read or written. Let’s create a Base64 filter and add it to the chain:
BIO * b64 = BIO_new(BIO_f_base64());
if (!encodeWithNewlines)
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
mem = BIO_push(b64, mem);
Now as data is written to my memory BIO, it gets Base64 encoded on the way. The BIO_FLAGS_BASE64_NO_NL
flag disables adding newlines to the encoded data, if the caller requested it. With the BIO chain setup, encoding the bytes contained by the NSData
object is simple:
BIO_write(mem, [self bytes], [self length]);
BIO_flush(mem);
The final task is to get the encoded data out of the BIO and into an NSString
. The memory BIOs have an API to get a pointer and length to its backing buffer:
char * base64Pointer;
long base64Length = BIO_get_mem_data(mem, &base64Pointer);
With a pointer to the raw bytes, it’s just a matter of creating a new string from this data:
NSString * base64String = [NSString stringWithCString: base64Pointer
length: base64Length];
Since I know this is ASCII data, I don’t have to worry about Unicode encoding. Using this constructor, rather than one of the newer ones that takes an encoding parameter also means backward compatibility to older versions of OS X. Now that I’ve got a new NSString
with the Base64 encoded data, I just need to clean up all the OpenSSL resources and return:
BIO_free_all(mem);
return base64String;
}
I’ve tested this on OS X 10.4.5, but it should work on OS X 10.2.x and 10.3.x as OpenSSL is included in the 10.2.8 and 10.3.9 SDKs of Xcode 2.2. Decoding Base64 is mostly the same except data is read from a memory BIO with a Base64 filter attached, rather than writing to one. See CocoaDev or the Xcode project for the decoding code.