What is Base64?
Base64 refers to the radix, or the number of unique characters in an encoding system. You can think of this in terms of other radices that you might be more familiar with in the table below
Radix | Name | Character set | number of bits one character can exactly represent (power of 2) |
2 | binary | 0-1 | 1 |
8 | octal | 0-7 | 3 |
10 | decimal | 0-9 | n/a |
16 | hex | 0-9,a-f | 4 |
64 | base64 | 0-9,A-Z,a-z,+,/ | 6 |
256 | byte | binary 0-255 | 8 |
The purpose of base64 is to represent binary data in some printable form, so it can be sent losslessly over media transports that are only designed to deal with text data. Many Http services, APIs (and email) encode their data to base64 during transmission. There are other encoding systems such as uuencode and Base85, but the most common is Base64.
As per the table above, base64 is only capable of representing 6 bits per character, but binary data could need all 8 bits in a byte. This means that a Base64 string of characters will be longer than the number of bytes in the original. In fact each group of 3 bytes needs 4 base64 characters to represent it.
Encoding base64.
It’s pretty straightforward to convert a byte array (or another string) to Base64. The only tricky bit is where the input number of characters is not exactly divisible by 3, in which case you have to use some padding.
Here’s a simple encoder. It’s written to be able to take either a string or a Byte[] as input.
/* * convert to base64 * @param {string|Byte[]) input the data to be converted * @return {string} the encoded string */ function sampleB64Encode (input) { var b64Digits = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; // if input is a string, convert to a byte array var barry = typeof input === "string" ? [].map.call (input,function(d) { return d.charCodeAt(0);}) : input.slice(); // the number of groups of 3 characters will be var numberOfGroups = Math.floor((barry.length -1) / 3) + 1; // this is the padding character if not input length not divisible by 3 var pad = "="; // pad out with 0 to make it divisible by 3 while (barry.length < numberOfGroups *3 ) { barry.push(0); } // we'll do it a group of 3 input characters at a time for ( var index = 0, group = 0 , output = [] ; group < numberOfGroups ; group++ ) { // top 6 bits of first char output.push ( b64Digits.charAt ( (barry [index] >> 2) & 0x3f) ); // bottom 2 bits of first char, top 4 of second output.push ( b64Digits.charAt ( ((barry[index] & 0x03) << 4) | ( (barry[index +1] >> 4) & 0x0f) ) ); // bottom 4 bits of 2nd char, top 2 of third output.push ( b64Digits.charAt ( ((barry [index +1] & 0x0f) << 2) | ((barry[index +2] >> 6) & 0x03) ) ) ; // bottom 6 of third output.push ( b64Digits.charAt (barry[index+2] & 0x3f)); index += 3; } // deal with nondivisible by 3 if ( input.length % 3 !== 0 ) { output [output.length-1] = pad; } if ( input.length % 3 === 1) { output [output.length-2] = pad ; } // return as a string return output.join(''); }
Decoding Base64
It should be fairly straightforward to reverse engineer this. Your homework is to submit a decoder to me on our forum, and I’ll publish the best one.
Testing
Here’s a test comparing the results of this function to the Utilities.base64Encode(). This generates 1000 tests with random Byte Arrays and succeeds.
function test() { // do some tests for (var j=0 ; j < 1000 ; j++) { var size = Math.ceil(Math.random() * 200); var seed = []; for (var i=0;i<size;i++) { seed [i] = Math.ceil(Math.random() * 127) * (Math.round(Math.random()) ? -1 : 1); } var conv = sampleB64Encode (seed); var convu = Utilities.base64Encode(seed); if ( conv !== convu) throw conv; } }
Performance
It’s always going to be better to use the inbuilt Utilities function, but here’s a rare chance to see the difference in performance between something coded in apps script and its native counterpart. – You can see that
Utilities.base64Encode()
is over 7 times as fast as my attempt at an equivalent in apps script.
[15-04-01 14:56:02:979 BST] Time for Utilities.base64Encode(data):963
[15-04-01 14:56:02:980 BST] Time for sampleB64Encode):7494
Although, when you run this as a client app on chrome – the same code runs superfast and the difference is minimal
Time for btoa:30 main.js:34 Time for sampleB64Encode):49
Here’s the test.
function speedtest() { // do some tests function makeSeeds() { var seeds = []; for (var j=0 ; j < 1000 ; j++) { var size = Math.ceil(Math.random() * 200); var seed = []; for (var i=0;i<size;i++) { seed [i] = Math.ceil(Math.random() * 127) * (Math.round(Math.random()) ? -1 : 1); } seeds.push(seed); } return seeds; } var seeds = makeSeeds(); var start = new Date().getTime(); seeds.forEach(function (d) { return Utilities.base64Encode(d); }); var u = new Date().getTime() - start; var start = new Date().getTime(); seeds.forEach(function (d) { return sampleB64Encode(d); }); var s = new Date().getTime() - start; Logger.log ('Time for Utilities.base64Encode(data):' + u); Logger.log ('Time for sampleB64Encode):' + s); }Many of the snippets in this section of the site are part of the cUseful library. You can find the details here.