Home Back to C/C++ Index Tips & Tricks Index Links

A Base64 Encoder and Decoder

Here's another tutorial.  Base64 is a simple encryption and decryption technique popularly used over the Internet where strong security isn't necessary.  Have you ever tried to access a web page that requested you to provide a user id and password?  If you supply an incorrect user id or password, you receive a "401 Unauthorized" response.  That resource (web page) might be protected using "Basic" authentication.

Basic authentication employs a technique called Base64 for password and user id encryption and decryption.  Remember, this technique is relatively easy to crack and thus, not recommended to secure sensitive resources.  Another, slightly more robust technique is called MD5, but again, by modern standards, even MD5 is not considered strong encryption.

Regardless, Base64 is fun to code and is very useful in teaching some C/C++ techniques that involve bit manipulation and pointer arithmetic.  The source code and binary executables for both Unix and Windows follow.  The source code is identical for each platform.  I've also included a makefile so that you can compile the source yourself if you have an appropraite compiler and linkage editor.

The Windows download is a zip file containing the Base64 source, executable, readme text file and a makefile if you want to compile it yourself.  The makefile was generated with Microsoft Visual C++ 5.0.

To compile the Windows console version yourself, you need a compiler and linkage editor as well as some flavor of make.  The following works for me:

pkunzip   base64.zip
nmake  /f base64.mak

The UNIX/Linux download is a "tar ball" containing the same.
To extract the "tar ball" change to your directory of choice and use:

gunzip -c base64.tar.gz | tar -xvf -

(Note the trailing hyphen)

The UNIX/Linux "tar ball" contains a GLIBC 2.0 executable, so you might need to recompile base64 before you can use it.  To do this, switch to the directory where you extracted  the "tar ball" files and type:

make clean
make

The source code is identical for both the Windows and the UNIX/Linux versions.

Download base64
Windows Version
   
File date:   Monday, 01-Nov-2004 16:50:21 EST
File size:   21,838 bytes
UNIX/Linux Version
   
File date:   Monday, 01-Nov-2004 16:50:21 EST
File size:   13,587 bytes

Here is the "base64" source code.

/*---------------------------------------------------------------------------*/
/* base64                                                                    */
/* ======                                                                    */
/*                                                                           */
/* Base64 is a stand-alone C program to encode 7-Bit ASCII strings into      */
/* base64 encoded strings and decode base64 encoded strings back into 7 bit  */
/* ASCII strings.                                                            */
/*                                                                           */
/* Base64 encoding is sometimes used for simple HTTP authentication. That is */
/* when strong encryption isn't necessary, Base64 encryption is used to      */
/* authenticate User-ID's and Passwords.                                     */
/*                                                                           */
/* Base64 processes a string by octets (3 Byte blocks).  For every octet in  */
/* the decoded string, four byte blocks are generated in the encoded string. */
/* If the decoded string length is not a multiple of 3, the Base64 algorithm */
/* pads the end of the encoded string with equal signs '='.                  */
/*                                                                           */
/* An example taken from RFC 2617 (HTTP Authentication):                     */
/*                                                                           */
/* Resource (URL) requires basic authentication (Authorization: Basic) for   */
/* access, otherwise a HTTP 401 Unauthorized response is returned.           */
/*                                                                           */
/* User-ID:Password string  = "Aladdin:open sesame"                          */
/* Base64 encoded   string  = "QWxhZGRpbjpvcGVuIHNlc2FtZQ=="                 */
/*                                                                           */
/* Usage:   base64 OPTION [STRING]                                           */
/* ------                                                                    */
/* OPTION:  -h Displays a brief messages.                                    */
/*          -e Base64 encode the 7-Bit ASCII STRING.                         */
/*          -d Decode the Base64 STRING to 7-Bit ASCII.                      */
/*                                                                           */
/* STRING:  Either a 7-Bit ASCII text string for encoding or a Base64        */
/*          encoded string for decoding back to 7-Bit ASCII.                 */
/*                                                                           */
/* Note:    For EBCDIC and other collating sequences, the STRING must first  */
/*          be converted to 7-Bit ASCII before passing it to this module and */
/*          the return string must be converted back to the appropriate      */
/*          collating sequence.                                              */
/*                                                                           */
/* Student Exercises:                                                        */
/* ------------------                                                        */
/* 1. Modify base64 to accept an additional parameter "Quiet Mode" (-q) to   */
/*    optionally supress the ending statistics and only display the encoded  */
/*    or decoded string.                                                     */
/*                                                                           */
/* 2. Make base64 callable from another program as follows:                  */
/*    a. Add an externally callable function to determine and return the     */
/*       size of the buffer required for encoding or decoding.               */
/*    b. Make base64 accept three parameters; input and output buffer point- */
/*       ers and a flag indicating encoding or decoding.                     */
/*    c. Modify base64 so that a calling program can:                        */
/*       i.   Request the size of a buffer required either for encoding or   */
/*            decoding.                                                      */
/*       ii.  Allocate a buffer based on the result from the previous        */
/*            call.                                                          */
/*       iii. Call base64 with the appropriate pointers and flag to encode   */
/*            or decode a string into the callers buffer.                    */
/*                                                                           */
/* Copyright (c) 1994 - 2003                                                 */
/* Marc Niegowski                                                            */
/* Connectivity, Inc.                                                        */
/* All rights reserved.                                                      */
/*---------------------------------------------------------------------------*/
#include    <stdlib.h>                      // calloc and free prototypes.
#include    <stdio.h>                       // printf prototype.
#include    <string.h>                      // str* and memset prototypes.

typedef
unsigned
char    uchar;                              // Define unsigned char as uchar.

typedef
unsigned
int     uint;                               // Define unsigned int as uint.

bool    b64help(void);                      // Displays brief help messages.
bool    b64encode(char *);                  // Encodes a string to Base64.
bool    b64decode(char *);                  // Decodes a string to ASCII.
void    b64stats(char *, char *, bool);     // Display encoding/decoding stats.
bool    b64valid(uchar *);                  // Tests for a valid Base64 char.
bool    b64isnot(char *, char *);           // Displays an invalid message.
char   *b64buffer(char *, bool);            // Alloc. encoding/decoding buffer.

// Macro definitions:

#define b64is7bit(c)  ((c) > 0x7f ? 0 : 1)  // Valid 7-Bit ASCII character?
#define b64blocks(l) (((l) + 2) / 3 * 4 + 1)// Length rounded to 4 byte block.
#define b64octets(l)  ((l) / 4  * 3 + 1)    // Length rounded to 3 byte octet.

// Note:    Tables are in hex to support different collating sequences

static  
const                                       // Base64 Index into encoding
uchar  pIndex[]     =   {                   // and decoding table.
                        0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
                        0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50,
                        0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
                        0x59, 0x5a, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
                        0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e,
                        0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,
                        0x77, 0x78, 0x79, 0x7a, 0x30, 0x31, 0x32, 0x33,
                        0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2b, 0x2f
                        };

static
const                                       // Base64 encoding and decoding
uchar   pBase64[]   =   {                   // table.
                        0x3e, 0x7f, 0x7f, 0x7f, 0x3f, 0x34, 0x35, 0x36,
                        0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x7f,
                        0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x00, 0x01,
                        0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
                        0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
                        0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
                        0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x1a, 0x1b,
                        0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
                        0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
                        0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33
                        };

static
const                                       // Help message.
char   *pHelp   = "Usage:  base64 OPTION [STRING]\n\n"
                  "OPTION: -d   Decode base64 STRING to text string\n"
                  "        -e   Encode text STRING to base64 string\n"
                  "        -h   Display this help text\n\n"
                  "STRING: The normal decoded text for option -e\n"
                  "        The encoded string for option -d\n\n"
                  "Be sure to enclose the STRING in quotes if it\n"
                  "contains any white space.\n\n";

/*---------------------------------------------------------------------------*/
/* main - Module entry point.                                                */
/* ==========================                                                */
/*                                                                           */
/* Call with:   int     - Number of arguments on the stack.                  */
/*              char ** - Pointer to an array of argument arrays.            */
/*              char ** - Pointer to an array of environment variable arrays.*/
/*                                                                           */
/* Returns:     bool    - True (!0) if the operation was successful.         */
/*                      - False (0) if the operation was unsuccessful.       */
/*---------------------------------------------------------------------------*/
bool main(int argc, char **argv, char **envp)
{
    printf("Base64 Encoder/Decoder\n\n");

    if  ((argc > 1) && (!strcmp(argv[1], "-h")))
        return b64help();                   // Operator asked for help.

    if (argc != 3)
    {
        printf("Invalid number of parameters.\n\n");
        return b64help();                   // Operator doesn't know the parms.
    }

    if  ((strlen(argv[1]) != 2) || (argv[1][0] != '-'))
    {
        printf("Invalid option: %s\n\n", argv[1]);
        return b64help();                   // Operator doesn't know the parms.
    }

    switch  (argv[1][1])
    {
    case    'd':                            // Decoding?
        return b64decode(argv[2]);          // Yes, call decoder and...
        break;                              // ...return with result code.
    case    'e':                            // Encoding?
        return b64encode(argv[2]);          // Yes, call encoder and...
        break;                              // ...return with result code.
    default:
        printf("Invalid option: %s\n\n", argv[1]);
        return b64help();                   // Operator doesn't know the parms.
    }
    return true;                            // Return to caller with true.
}

/*---------------------------------------------------------------------------*/
/* b64help - Display a brief help message.                                   */
/* =======================================                                   */
/*                                                                           */
/* Call with:   void    - Nothing.                                           */
/*                                                                           */
/* Returns:     bool    - False (0) for main if invalid parms were provided. */
/*---------------------------------------------------------------------------*/
bool b64help(void)
{
    printf(pHelp);                          // Display the help message.
    return false;                           // Return to caller with False.
}

/*---------------------------------------------------------------------------*/
/* b64encode - Encode a 7-Bit ASCII string to a Base64 string.               */
/* ===========================================================               */
/*                                                                           */
/* Call with:   char *  - The 7-Bit ASCII string to encode.                  */
/*                                                                           */
/* Returns:     bool    - True (!0) if the operation was successful.         */
/*                        False (0) if the operation was unsuccessful.       */
/*---------------------------------------------------------------------------*/
bool b64encode(char *s)
{
    int     l   = strlen(s);                // Get the length of the string.
    int     x   = 0;                        // General purpose integers.
    char   *b, *p;                          // Encoded buffer pointers.

    while   (x < l)                         // Validate each byte of the string
    {                                       // ...to ensure that it's 7-Bit...
        if (!b64is7bit((uchar) *(s + x)))   // ...ASCII.
        {
            printf("\"%s\" is not a 7-Bit ASCII string.\n", s);
            return false;                   // Return false if it's not.
        }
        x++;                                // Next byte.
    }

    if (!(b = b64buffer(s, true)))          // Allocate an encoding buffer.
        return false;                       // Can't allocate encoding buffer.

    memset(b, 0x3d, b64blocks(l) - 1);      // Initialize it to "=". 

    p = b;                                  // Save the buffer pointer.
    x = 0;                                  // Initialize string index.

    while   (x < (l - (l % 3)))             // encode each 3 byte octet.
    {
        *b++   = pIndex[  s[x]             >> 2];
        *b++   = pIndex[((s[x]     & 0x03) << 4) + (s[x + 1] >> 4)];
        *b++   = pIndex[((s[x + 1] & 0x0f) << 2) + (s[x + 2] >> 6)];
        *b++   = pIndex[  s[x + 2] & 0x3f];
         x    += 3;                         // Next octet.
    }

    if (l - x)                              // Partial octet remaining?
    {
        *b++        = pIndex[s[x] >> 2];    // Yes, encode it.

        if  (l - x == 1)                    // End of octet?
            *b      = pIndex[ (s[x] & 0x03) << 4];
        else                            
        {                                   // No, one more part.
            *b++    = pIndex[((s[x]     & 0x03) << 4) + (s[x + 1] >> 4)];
            *b      = pIndex[ (s[x + 1] & 0x0f) << 2];
        }
    }

    b64stats(s, p, true);                   // Display some encoding stats.
    free(p);                                // De-allocate the encoding buffer.
    printf("Base64 encoding complete.\n");
    return true;                            // Return to caller with success.
}

/*---------------------------------------------------------------------------*/
/* b64decode - Decode a Base64 string to a 7-Bit ASCII string.               */
/* ===========================================================               */
/*                                                                           */
/* Call with:   char *  - The Base64 string to decode.                       */
/*                                                                           */
/* Returns:     bool    - True (!0) if the operation was successful.         */
/*                        False (0) if the operation was unsuccessful.       */
/*---------------------------------------------------------------------------*/
bool b64decode(char *s)
{
    int     l = strlen(s);                  // Get length of Base64 string.
    char   *b, *p;                          // Decoding buffer pointers.
    uchar   c = 0;                          // Character to decode.
    int     x = 0;                          // General purpose integers.
    int     y = 0;

    static                                  // Collating sequence...
    const                                   // ...independant "===".
    char    pPad[]  =   {0x3d, 0x3d, 0x3d, 0x00};

    if  (l % 4)                             // If it's not modulo 4, then it...
        return b64isnot(s, NULL);           // ...can't be a Base64 string.

    if  (b = strchr(s, pPad[0]))            // Only one, two or three equal...
    {                                       // ...'=' signs are allowed at...
        if  ((b - s) < (l - 3))             // ...the end of the Base64 string.
            return b64isnot(s, NULL);       // Any other equal '=' signs are...
        else                                // ...invalid.
            if  (strncmp(b, (char *) pPad + 3 - (s + l - b), s + l - b))
                return b64isnot(s, NULL);
    }

    if  (!(b = b64buffer(s, false)))        // Allocate a decoding buffer.
        return false;                       // Can't allocate decoding buffer.

    p = s;                                  // Save the encoded string pointer.
    x = 0;                                  // Initialize index.

    while ((c = *s++))                      // Decode every byte of the...
    {                                       // Base64 string.
        if  (c == pPad[0])                  // Ignore "=".
            break;

        if (!b64valid(&c))                  // Valid Base64 Index?
            return b64isnot(s, b);          // No, return false.
        
        switch(x % 4)                       // Decode 4 byte words into...
        {                                   // ...3 byte octets.
        case    0:                          // Byte 0 of word.
            b[y]    =  c << 2;
            break;                          
        case    1:                          // Byte 1 of word.
            b[y]   |=  c >> 4;

            if (!b64is7bit((uchar) b[y++])) // Is 1st byte of octet valid?
                return b64isnot(s, b);      // No, return false.

            b[y]    = (c & 0x0f) << 4;
            break;
        case    2:                          // Byte 2 of word.
            b[y]   |=  c >> 2;

            if (!b64is7bit((uchar) b[y++])) // Is 2nd byte of octet valid?
                return b64isnot(s, b);      // No, return false.

            b[y]    = (c & 0x03) << 6;
            break;
        case    3:                          // Byte 3 of word.
            b[y]   |=  c;

            if (!b64is7bit((uchar) b[y++])) // Is 3rd byte of octet valid?
                return b64isnot(s, b);      // No, return false.
        }
        x++;                                // Increment word byte.
    }

    b64stats(p, b, false);                  // Display some decoding stats.
    free(b);                                // De-allocate decoding buffer.
    printf("Base64 decoding complete.\n");
    return true;                            // Return to caller with success.
}

/*---------------------------------------------------------------------------*/
/* b64valid - validate the character to decode.                              */
/* ============================================                              */
/*                                                                           */
/* Checks whether the character to decode falls within the boundaries of the */
/* Base64 decoding table.                                                    */
/*                                                                           */
/* Call with:   char    - The Base64 character to decode.                    */
/*                                                                           */
/* Returns:     bool    - True (!0) if the character is valid.               */
/*                        False (0) if the character is not valid.           */
/*---------------------------------------------------------------------------*/
bool b64valid(uchar *c)
{
    if ((*c < 0x2b) || (*c > 0x7a))         // If not within the range of...
        return false;                       // ...the table, return false.
    
    if ((*c = pBase64[*c - 0x2b]) == 0x7f)  // If it falls within one of...
        return false;                       // ...the gaps, return false.

    return true;                            // Otherwise, return true.
}

/*---------------------------------------------------------------------------*/
/* b64isnot - Display an error message and clean up.                         */
/* =================================================                         */
/*                                                                           */
/* Call this routine to display a message indicating that the string being   */
/* decoded is an invalid Base64 string and de-allocate the decoding buffer.  */
/*                                                                           */
/* Call with:   char *  - Pointer to the Base64 string being decoded.        */
/*              char *  - Pointer to the decoding buffer or NULL if it isn't */
/*                        allocated and doesn't need to be de-allocated.     */
/*                                                                           */
/* Returns:     bool    - True (!0) if the character is valid.               */
/*                        False (0) if the character is not valid.           */
/*---------------------------------------------------------------------------*/
bool b64isnot(char *p, char *b)
{
    printf("\"%s\" is not a Base64 encoded string.\n", p);

    if  (b)                                 // If the buffer pointer is not...
        free(b);                            // ...NULL, de-allocate it.

    return  false;                          // Return false for main.
}

/*---------------------------------------------------------------------------*/
/* b64buffer - Allocate the decoding or encoding buffer.                     */
/* =====================================================                     */
/*                                                                           */
/* Call this routine to allocate an encoding buffer in 4 byte blocks or a    */
/* decoding buffer in 3 byte octets.  We use "calloc" to initialize the      */
/* buffer to 0x00's for strings.                                             */
/*                                                                           */
/* Call with:   char *  - Pointer to the string to be encoded or decoded.    */
/*              bool    - True (!0) to allocate an encoding buffer.          */
/*                        False (0) to allocate a decoding buffer.           */
/*                                                                           */
/* Returns:     char *  - Pointer to the buffer or NULL if the buffer        */
/*                        could not be allocated.                            */
/*---------------------------------------------------------------------------*/
char *b64buffer(char *s, bool f)
{
    int     l = strlen(s);                  // String size to encode or decode.
    char   *b;                              // String pointers.

    if  (!l)                                // If the string size is 0...
        return  NULL;                       // ...return null.

   if (!(b = (char *) calloc((f ? b64blocks(l) : b64octets(l)),
               sizeof(char))))
        printf("Insufficient real memory to %s \"%s\".\n",
              (f ? "encode" : "decode"), s);
    return  b;                              // Return the pointer or null.
}

/*---------------------------------------------------------------------------*/
/* b64stats - Display encoding or decoding statistics.                       */
/* ===================================================                       */
/*                                                                           */
/* Call this routine to display some encoding or decoding statistics.        */
/*                                                                           */
/* Call with:   char *  - Pointer to original input string.                  */
/*              char *  - Pointer to the resultant encoded or decoded string.*/
/*              bool    - True (!0) to display encoding statistics.          */
/*                        False (0) to display decoding statistics.          */
/*                                                                           */
/* Returns:     void    - Nothing.                                           */
/*---------------------------------------------------------------------------*/
void b64stats(char *s, char *b, bool f)
{
    char   *e, *d, *p;                      // General purpose string pointers.

    e   = "Encoded";                        // Point to the "Encoded" string.
    d   = "Decoded";                        // Point to the "Decoded" string.

    if  (!f)                                // If we are decoding...
    {                                       // ...switch the pointers.
        p   = e;                            // Save, the "Encoded" pointer.
        e   = d;                            // Point to the "Decoded" string.
        d   = p;                            // Point to the "Encoded" string.
    }
                                            // Display the statistics.
    printf("%s string length = %d\n", d, strlen(s));
    printf("%s string        = \"%s\"\n", d, s);
    printf("%s buffer size   = %d\n", e,
          (f ? b64blocks(strlen(s)) : b64octets(strlen(s))));
    printf("%s string length = %d\n", e, strlen(b));
    printf("%s string        = \"%s\"\n", e, b);
}

I will continue to add tips and techniques as time permits.

Please direct any inquiries or problems regarding this web to webmaster@marcsweb.com


Page design, composition and HTML by Marc Niegowski
Copyright © 1998-2012, Marc Niegowski - Connectivity, Inc., All Rights Reserved
23 W. Fourth Street • Media • Pennsylvania • 19063-2805 • USA
Phone: 610-566-0227 • Fax: 610-566-0641 • Email: Marc@Tech-Center.com

Revision Date: Wednesday, November 15, 2006 10:02:19 AM