Search Forum
(53671 Postings)
Search Site/Articles

Archived Articles
712 Articles

C# Books
C# Consultants
What Is C#?
Download Compiler
Code Archive
Archived Articles
Advertise
Contribute
C# Jobs
Beginners Tutorial
C# Contractors
C# Consulting
Links
C# Manual
Contact Us
Legal

GoDiagram for .NET from Northwoods Software www.nwoods.com


 
Printable Version

BASE64 ENCODING AND DECODING
By Biswajit Sarkar

The B64Handler class provides methods to convert ascii strings into Base64 encoded strings and to decode valid Base64 encoded strings into ascii strings.

Before looking into the accompanying code, it would perhaps be useful to understand the relevance of this class in the context of handling email protocols. Books and articles on programming languages as well as those on networking systems usually contain examples of SMTP dialogues (and their coded versions in the language under discussion) between MTAs and UAs to illustrate the mechanism of email transmission. However implementation of these examples as shown may not always work properly. The reason is that many SMTP servers require authentication before accepting mail from a UA and textbook examples frequently omit the authentication procedure. If you do not authenticate the transaction, you are likely to get a highly confusing message such as "Relaying not permitted". Assuming you are sending mail through the server of the ISP that provides internet access to you and, therefore, you have a valid username and password, what you need to do when faced with such a response is to carry out login authentication. A typical SMTP session with login authentication may look like this:

Server: 220 xyz Server ESMTP (*)

Client: HELO abc

Server: 250 xyz OK, [nn.n.nn.nnn].

Client: AUTH LOGIN

Server: 334 VXNlcm5hbWU6

Client: (Base64 encoded username)

Server: 334 UGFzc3dvcmQ6

Client: (Base64 encoded password)

Server: 235 2.7.0 LOGIN authentication successful

.

.

.

rest of the session as per SMTP

Note that the prompt-response sequence for login (lines 5 to 8) involves Base64 encoding and decoding. The string following '334' on line 5 is the Base64 encoded form of "Username:" and the one follwing the '334' on line 7 is that of "Password:". The user response must also be in a Base64 encoded form.

B64Handler is a convenient utility to take care of the Base64 encoding (and decoding when needed). If you would like to know more about Base64, you will find the specification in RFC1521.

B64Handler provides methods for encoding an ascii string into Base64 format and for decoding Base64 encoded strings into ascii strings. It can be used in three ways. One way to use it is from the command line. The code listing gives the usage. The second way is to use the methods for encoding and decoding in your code. It is not necessary to instantiate the class as all the methods are static. Finally, if you type 'B64Handler' without any arguments, a GUI will pop up through which short strings can be encoded or decoded.

The method signatures are as follows:

public static StringBuilder EncodeString(StringBuilder targetstring)

Returns Base64 encoded StringBuilder if encoding is possible. Otherwise throws

Exception -- for instance if there is a non-ascii character in the string to be encoded.

Parameter :

targetstring - the ascii string (represented as a StringBuilder) to be encoded.

public static StringBuilder DecodeString(StringBuilder targetstring)

Returns ascii StringBuilder if decoding is possible. Otherwise throws Exception -- for instance if there is an illegal character in the string to be decoded.

Parameter :

targetstring - the Base64 encoded string (represented as a StringBuilder) to be decoded.

public static StringBuilder dToBs(int decnum, int charnum)

Converts an integer decimal value, decnum, into a binary bitstring of length

charnum and returns the binary bitstring. Throws exception if the decimal

number is negative or too large to fit into the specified size. The return string

is in the form of a StringBuilder.

Parameters :

decnum -- the decimal integer to be converted into bitstring.

charnum -- the length of the bitstring.

public static int bsToD(StringBuilder bitstring)

Converts a binary bitstring into a decimal integer and returns the integer.

Note that the bitstring is considered to represent a positive binary number.

In other words the most significant bit is not treated as sign bit.

Parameter :

bitstring -- the binary bitstring (represented as a StringBuilder)to be converted.

Finally the following points should be noted:

1. While RFC1521 recommends that all non-Base64 characters in a Base64

string should be ignored, it also accepts the alternate approach -

the one adapted here - that the presence of non-Base64 characters may

lead to the decoding of a Base64 string being aborted.

2. The maximum prescribed length for a Base64 encoded string is 76

characters. Accordingly a 'CRLF' is inserted after every 76 characters.

3. It is assumed that Base64 strings have had linebreak characters removed

before they are passed to the decodeString method. ASCII strings

containing linebreak characters, however, may be passed to encodeString

method. Such strings will be encoded prperly.

4. RFC1521 does not explicitly specify that only ascii strings may be

encoded using the Base64 format. However ascii is the most common form

of characters used in my environment. Accordingly I have restricted the

coding/decoding to the ascii character set. Note that it is a relatively

simple matter to remove this restriction.

Now the code :

START OF CODE

using System;

using System.Text;

using System.Windows.Forms;

using System.Drawing;

class B64Handler

{

static char [] b64table = new char [] {'A','B','C','D','E','F','G','H','I','J',

'K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y',

'Z','a','b','c','d','e','f','g','h','i','j','k','l','m','n',

'o','p','q','r','s','t','u','v','w','x','y','z','0','1','2',

'3','4','5','6','7','8','9','+','/'};

public static StringBuilder EncodeString(StringBuilder targetstring)

{

StringBuilder stringcode = new StringBuilder();

StringBuilder codedstring = new StringBuilder();

StringBuilder stringtocode = new StringBuilder();

stringtocode = targetstring;

int l = stringtocode.Length;

int padnum = 0;

//reject zero-length strings

if(l==0)

{

throw new Exception("Error : No characters in string");

}

// convert the string into concatenated binary byte string

for(int i=0;i<l;i++)

{

// convert a character into a binary byte string

int charval = stringtocode[i];

if(charval>127)

{

throw new Exception("Error : Non ASCII character");

}

stringcode = stringcode.Append(dToBs(charval, 8));

}

// figure out how much padding is required

int k = l;

while((k%3)!=0)

{

padnum++;

k++;

}

// insert zeroes at the end of stringcode if required

// to make the string length a multiple of 6 bits

if(padnum!=0)

{

if(padnum==1)

{

stringcode = stringcode.Append("00");

}

else if(padnum==2)

{

stringcode = stringcode.Append("0000");

}

}

int stringcodelength = stringcode.Length;// length of binary string

int cursor = 0;

int maxlength = 0;

StringBuilder bitstring = new StringBuilder();

while(cursor<stringcodelength)

{

// find decimal value of each 6bit group

int value = bsToD(bitstring.Append(stringcode.ToString(cursor,6)));

cursor = cursor+6;

// the calculated value can't be greater than 63

if(value>63)

{

throw new Exception("Coding error");

}

// update codedstring -- insert crlf after every 76 characters

if(maxlength==76)

{

codedstring = codedstring.Append("\r\n");

maxlength = 0;

}

codedstring = codedstring.Append(b64table[value]);

maxlength++;

value = 0;

bitstring.Remove(0,6);

}

// add required padding

if(padnum==1)

{

codedstring = codedstring.Append("=");

}

else if(padnum==2)

{

codedstring = codedstring.Append("==");

}

return codedstring;

}

public static StringBuilder DecodeString(StringBuilder targetstring)

{

//check if length multiple of 4. else print error message.

//if decodable determine extent of padding and get rid of pads

//find value corresponding to each character and convert to

//a binary string. if illegal character print error message,

//otherwise concatenate. delete trailing zeroes as

//per padding. take each byte and convert to decimal, convert

//to character and create string.

StringBuilder stringcode = new StringBuilder();

StringBuilder decodedstring = new StringBuilder();

StringBuilder stringtodecode = new StringBuilder();

stringtodecode = targetstring;

int l = stringtodecode.Length;

int padnum = 0;

//reject zero-length strings

if(l==0)

{

throw new Exception("Error : No characters in string");

}

//if string length not multiple of 4 then error

if(l%4!=0)

{

throw new Exception("Illegal string length. Base64 decoding not possible.");

}

//otherwise remove padding

while(stringtodecode[l-1].Equals('='))

{

padnum++;

l--;

}

//maximum 2 padding characters can be there in a valid Base64

//encoded string

if(padnum>2)

{

throw new Exception("Excessive padding. Base64 decoding not possible.");

}

//for all the characters in the string find value, convert

//into 6bit binary and form concatenated string

for(int i=0;i<l;i++)

{

bool done = false;

int charval = 0;

while(!done)

{

char check = stringtodecode[i];

//find the value corresponding to each character

if(b64table[charval].Equals(check))

{

done = true;

//convert the value into 6bit string

//and form concatenated string

stringcode = stringcode.Append(dToBs(charval, 6));

}

else

{

//try next entry in b64table

charval++;

}

//if character not in b64table then error

if(charval>63)

{

throw new Exception("Illegal character in string : " +check);

}

}

}

//proceed if no error

l = stringcode.Length;

//if padded then the last 2 or 4 bits must be zero.

//if not then error

//otherwise remove the zeroes

if(padnum==1)

{

if((stringcode.ToString(l-2, 2)).Equals("00"))

{

if((l-2)%8==0)

{

l = l-2;

}

else

{

throw new Exception("Improper coding. Decoding not possible.");

}

}

else

{

throw new Exception("Improper coding. Decoding not possible.");

}

}

else if(padnum==2)

{

if((stringcode.ToString(l-4, 4)).Equals("0000"))

{

if((l-4)%8==0)

{

l = l-4;

}

else

{

throw new Exception("Improper coding. Decoding not possible.");

}

}

else

{

throw new Exception("Improper coding. Decoding not possible.");

}

}

//proceed if no error

StringBuilder bitstring = new StringBuilder();

int cursor = 0;

while(cursor<l)

{

// find decimal value of each 8bit group

int value = bsToD(bitstring.Append(stringcode.ToString(cursor,8)));

//the decimal value can't be greater than 127 as the

//original string must have had only ascii characters

if(value>127)

{

throw new Exception("Improper coding. Decoding not possible.");

}

cursor = cursor+8;

// update decodedstring

decodedstring = decodedstring.Append((char)value);

bitstring.Remove(0,8);

value = 0;

}

return decodedstring;

}

//converts a decimal value, decnum, into a binary bitstring of length charnum.

public static StringBuilder dToBs(int decnum, int charnum)

{

//if the number is negative or too large to be converted into

//a bitstring of specified size then throw exception.

if(decnum >= (int)Math.Pow(2,charnum)||decnum<0)

{

throw new Exception("Error: decimal value too large for bitstring size.");

}

StringBuilder bitstring = new StringBuilder();

for(int i=charnum-1;i>-1;i--)

{

int powerof2 = (int)Math.Pow(2,i);

if(decnum >= powerof2)

{

bitstring = bitstring.Append('1');

decnum = decnum - powerof2;

}

else

{

bitstring = bitstring.Append('0');

}

}

return bitstring;

}

//converts binary bitstring to decimal value. leading zeroes not essential

public static int bsToD(StringBuilder bitstring)

{

int stringlength = bitstring.Length;

int cursor = 0;

int value = 0;

for(int i=stringlength-1;i>-1;i--)

{

if(bitstring[cursor]=='1')

{

value = value+(int)Math.Pow(2,i);

}

cursor++;

}

return value;

}

public static void Main(String[] args)

{

if(args.Length==0)

{

Application.Run(new Base64CoDec());

}

else if(args.Length==2)

{

if(args[1].Equals("encode"))

{

try

{

Console.WriteLine(B64Handler.EncodeString(new StringBuilder(args[0])));

}

catch (Exception e)

{

Console.WriteLine(e.Message);

}

}

else if(args[1].Equals("decode"))

{

try

{

Console.WriteLine(B64Handler.DecodeString(new StringBuilder(args[0])));

}

catch (Exception e)

{

Console.WriteLine(e.Message);

}

}

else

{

Console.WriteLine("Usage: B64handler String s, String ende");

Console.WriteLine(" - s is the string -- without spaces -- to be encoded or decoded");

Console.WriteLine(" - ende : \"encode\" > string is to be encoded");

Console.WriteLine(" - ende : \"decode\" > string is to be decoded");

Console.WriteLine(" - Type B64handler without arguments to launch GUI");

}

}

else

{

Console.WriteLine("Usage: B64handler String s, String ende");

Console.WriteLine(" - s is the string -- without spaces -- to be encoded or decoded");

Console.WriteLine(" - ende : \"encode\" > string is to be encoded");

Console.WriteLine(" - ende : \"decode\" > string is to be decoded");

Console.WriteLine(" - Type B64handler without arguments to launch GUI");

}

}

}

public class Base64CoDec : Form

{

Label lInput = new Label();

Label lOutput = new Label();

TextBox tInput = new TextBox();

TextBox tOutput = new TextBox();

Button bnEncode = new Button();

Button bnDecode = new Button();

public Base64CoDec()

{

Font lFont = new Font("Arial", 12);

float tFontSize = tInput.Font.Size;

Font tFont = new Font("Arial", tFontSize);

tInput.TabIndex = 0;

tInput.Size = new Size(250, 25);

tInput.Location = new Point(15,45);

tInput.Font = tFont;

lInput.ForeColor = Color.Blue;

lInput.Font = lFont;

lInput.TabStop = false;

lInput.Text = "Input:";

lInput.Size = new Size(lInput.PreferredWidth, 25);

lInput.Location = new Point(15,15);

tOutput.TabStop = false;

tOutput.Size = new Size(250, 25);

tOutput.Location = new Point(15,115);

tOutput.Font = tFont;

lOutput.ForeColor = Color.Blue;

lOutput.TabStop = false;

lOutput.Font = lFont;

lOutput.Text = "Output:";

lOutput.Size = new Size(lOutput.PreferredWidth, 25);

lOutput.Location = new Point(15,85);

Font bFont = new Font("Arial", 10);

bnEncode.TabIndex = 1;

bnEncode.Font = bFont;

bnEncode.Text = "Encode";

bnEncode.Size = new Size(70, 25);

bnEncode.Location = new Point(15,150);

bnEncode.BackColor = Color.LightPink;

bnEncode.Click += new EventHandler(this.bnEncode_Click);

bnDecode.TabIndex = 2;

bnDecode.Font = bFont;

bnDecode.Text = "Decode";

bnDecode.BackColor = Color.LightGreen;

bnDecode.Location = new Point(90,150);

bnDecode.Size = new Size(70, 25);

bnDecode.Click += new EventHandler(this.bnDecode_Click);

this.Size = new Size(330,215);

this.FormBorderStyle = FormBorderStyle.Fixed3D;

this.Text = "Base64CODEC";

this.StartPosition = FormStartPosition.CenterScreen;

this.HelpButton = false;

this.MaximizeBox = false;

this.Controls.Add(tInput);

this.Controls.Add(lInput);

this.Controls.Add(tOutput);

this.Controls.Add(lOutput);

this.Controls.Add(bnEncode);

this.Controls.Add(bnDecode);

}

private void bnEncode_Click(Object sender, EventArgs e)

{

try

{

tOutput.ForeColor = Color.Black;

tOutput.Text = B64Handler.EncodeString(new StringBuilder(tInput.Text)).ToString();

}

catch(Exception be)

{

tOutput.ForeColor = Color.Red;

tOutput.Text = be.Message;

}

}

private void bnDecode_Click(Object sender, EventArgs e)

{

try

{

tOutput.ForeColor = Color.Black;

tOutput.Text = B64Handler.DecodeString(new StringBuilder(tInput.Text)).ToString();

}

catch(Exception be)

{

tOutput.ForeColor = Color.Red;

tOutput.Text = be.Message;

}

}

}

END OF CODE

To compile and run this program save it as 'B64Handler.cs'. The command 'csc B64Handler.cs' will compile the code and save it as 'B64Handler.exe'. If you want to use the coding/decoding functions in your code you can call the static methods 'EncodeString' or 'DecodeString'. The technique for calling methods in external exe files will not be discussed here.

The command line usage is as follows:

Usage: B64handler String s, String ende

- s is the string -- without spaces -- to be encoded or decoded

- ende : encode means string s is to be encoded

- ende : decode means string s is to be decoded

To use the GUI type B64Handler without any arguments.

CODE DISCUSSION:

I strongly recommend a study of RFC1521 before trying to analyze the code. Once the algorithm for Base64 encoding and decoding is known the basic logic of the programme can be easily understood. >From the programming point of view there are, however, some issues of interest that should be commented on.

B64Handler is basically a utility programme and it does not need any internal data structure other than 'b64table'. It is therefore natural to think of making the methods available without the need to create an object of type B64Handler. In other words all the methods should be static and to make it so, 'b64table' too needs to be static.

The next point to be noted is the extensive use of StringBuilders. The functional requirements could also have been met by strings. Then why StringBuilders? The answer lies in the fact that strings are immutable. That is, once created, they cannot be changed. The methods that allow string modification actually create a new string incorporating the modifications and discard the old one. This makes string modification expensive in terms of memory usage and time. As I need to keep on modifying the strings I work with in B64Handler, I use StringBuilders and not strings.

B64handler also provides a good example of how to use Exceptions to handle errors and abnormal situations. Note that many error conditions have been checked for and each time an error is detected an Exception is thrown. Whenever an Exception is thrown the method that has encountered the error returns. The calling method has two options: it can either handle the error or throw another Exception. In this case I have centralised the final handling of all Exceptions at the level which calls the encoding/decoding methods. At this level action is taken to handle the error conditions and no further Exception is thrown. If this final handling of an Exception is not provided for in the programme, it will come to an ignominious end and an "Unhandled Exception" message will be printed on the console. One must, therefore, plan the Exception handling structure of a programme with due care. Remember that an extensive error checking scheme together with a well structured Exception handling capability enhances the robustness of a programme.

I shall not comment here on that part of the programme (class Base64CoDec) that deals with the GUI. This is because I am working on a multipart article on GUI and GDI+. Matters related to user interfaces will be examined in that forthcoming series. In the meanwhile do mail me your comments on this article and/or your questions if any.