You want to represent binary data in as compact a textual representation as is reasonable, but the data must be easy to encode and decode, and it must use printable text characters.
Base64 encoding encodes six bits of data at a time, meaning that every six bits of input map to one character of output. The characters in the output will be a numeric digit, a letter (uppercase or lowercase), a forward slash, a plus, or the equal sign (which is a special padding character).
Note that four output characters map exactly to three input characters. As a result, if the input string isn’t a multiple of three characters, you’ll need to do some padding (explained in Section 4.5.3).
The base64 alphabet takes 6-bit binary values representing numbers from 0 to 63 and maps them to a set of printable ASCII characters. The values 0 through 25 map to the uppercase letters in order. The values 26 through 51 map to the lowercase letters. Then come the decimal digits from 0 to 9, and finally + and /.
If the length of the input string isn’t a multiple of three bytes, the leftover bits are padded to a multiple of six with zeros; then the last character is encoded. If only one byte would have been needed in the input to make it a multiple of three, the pad character ( =) is added to the end of the string. Otherwise, two pad characters are added.
#include <stdlib.h> static char b64table[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; /* Accepts a binary buffer with an associated size. * Returns a base64 encoded, NULL-terminated string. */ unsigned char *spc_base64_encode(unsigned char *input, size_t len, int wrap) { unsigned char *output, *p; size_t i = 0, mod = len % 3, toalloc; toalloc = (len / 3) * 4 + (3 - mod) % 3 + 1; if (wrap) { toalloc += len / 57; if (len % 57) toalloc++; } p = output = (unsigned char *)malloc(((len / 3) + (mod ? 1 : 0)) * 4 + 1); if (!p) return 0; while (i < len - mod) { *p++ = b64table[input[i++] >> 2]; *p++ = b64table[((input[i - 1] << 4) | (input[i] >> 4)) & 0x3f]; *p++ = b64table[((input[i] << 2) | (input[i + 1] >> 6)) & 0x3f]; *p++ = b64table[input[i + 1] & 0x3f]; i += 2; if (wrap && !(i % 57)) *p++ = '\n'; } if (!mod) { if (wrap && i % 57) *p++ = '\n'; *p = 0; return output; } else { *p++ = b64table[input[i++] >> 2]; *p++ = b64table[((input[i - 1] << 4) | (input[i] >> 4)) & 0x3f]; if (mod = = 1) { *p++ = '='; *p++ = '='; if (wrap) *p++ = '\n'; *p = 0; return output; } else { *p++ = b64table[(input[i] << 2) & 0x3f]; *p++ = '='; if (wrap) *p++ = '\n'; *p = 0; return output; } } }
The public interface to the above code is the following:
unsigned char *spc base64_encode(unsigned char *input, size_t len, int wrap);
The result is a NULL
-terminated string allocated
internally via malloc( )
. Some protocols may
expect you to “wrap” base64-encoded
data so that, when printed, it takes up less than 80 columns. If such
behavior is necessary, you can pass in a non-zero value for the final
parameter, which will cause this code to insert newlines once every
76 characters. In that case, the string will always end with a
newline (followed by the expected
NULL
-terminator).
If the call to malloc( )
fails because there is no
memory, this function returns 0.
Get Secure Programming Cookbook for C and C++ now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.