Chris Roberts

Base32 Encoding and Decoding in VB.NET

• Posted in Programming

Like the more common Base64 encoding, Base32 encoding is a method for turning binary data into a string composed of a small, defined set of ASCII characters. Base64 takes advantage of the entire alphabet in both upper and lower case, the digits 0 to 9 and the '+' and '/' symbols. This can be problematic where, for example, the encoded data needs to be used as part of a URL where the '+' and '/' symbols have special meaning and where case-sensitivity can cause problems.

Base32 addresses these issues by using a further reduced set of characters - the entire alphabet (but only one case) and the digits 2 to 7. The digits '0' and '1' are ommitted due to their similarity to the letters 'O' and 'I'. This makes Base32 even more useful in situations where human readability is a concern.

More information about Base32 can, of course, be found on the Base32 Wikipedia page.

During a recent project, I had a requirement to use Base32 encoding and was a little surprised to find that the .NET library doesn't include support for it alongside Base64 in the System.Convert namespace. So, I went about writing my own support as a pair of extension methods.

For the sake of tidyness, I originally wanted to add these extension methods to the System.Convert class, but it seems it's impossible to create Shared extension methods in VB.NET at the moment. So - I have written one extension method for the String class which converts a Base32 encoded string into a Byte array. The other extension method is for the Byte array type which converts the binary data into a Base 32 encoded string.

The Code

The code is included below - please feel free to use it. If you have any comments or suggestions for improvements, please let me know. Enjoy!

Imports System.Runtime.CompilerServices

Module Base32

    Const cBase32Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
    Const cBase32Pad = "="

    <Extension()> _
    Public Function ToBase32String(ByVal Data As Byte(), Optional IncludePadding As Boolean = True) As String
        Dim RetVal As String = ""
        Dim Segments As New List(Of Long)

        ' Divide the input data into 5 byte segments
        Dim Index As Integer = 0
        While Index < Data.Length
            Dim CurrentSegment As Long = 0
            Dim SegmentSize As Integer = 0

            While Index < Data.Length And SegmentSize < 5
                CurrentSegment <<= 8
                CurrentSegment += Data(Index)

                Index += 1
                SegmentSize += 1
            End While

            ' If the size of the last segment was less than 5 bytes, pad with zeros
            CurrentSegment <<= (8 * (5 - SegmentSize))
            Segments.Add(CurrentSegment)
        End While

        ' Convert each 5 byte segment into 8 character strings
        For Each CurrentSegment As Long In Segments
            For x As Integer = 0 To 7
                RetVal &= cBase32Alphabet.Chars((CurrentSegment >> (7 - x) * 5) And &H1F)
            Next
        Next

        ' Correct the end of the string (where the input wasn't a multiple of 5 bytes)
        Dim LastSegmentUsefulDataLength As Integer = Math.Ceiling((Data.Length Mod 5) * 8 / 5)
        RetVal = RetVal.Substring(0, RetVal.Length - (8 - LastSegmentUsefulDataLength))

        ' Add the padding characters 
        If IncludePadding Then
            RetVal &= New String(cBase32Pad, 8 - LastSegmentUsefulDataLength)
        End If

        Return RetVal
    End Function

    <Extension()> _
    Public Function FromBase32String(ByVal Data As String) As Byte()
        Dim RetVal As New List(Of Byte)
        Dim Segments As New List(Of Long)

        ' Remove any trailing padding
        Data = Data.TrimEnd(New Char() {cBase32Pad})

        ' Process the string 8 characters at a time
        Dim Index As Integer = 0
        While Index < Data.Length
            Dim CurrentSegment As Long = 0
            Dim SegmentSize As Integer = 0

            While Index < Data.Length And SegmentSize < 8
                CurrentSegment <<= 5
                CurrentSegment = CurrentSegment Or cBase32Alphabet.IndexOf(Data.Chars(Index))

                Index += 1
                SegmentSize += 1
            End While

            ' If the size of the last segment was less than 40 bits, pad it
            CurrentSegment <<= (5 * (8 - SegmentSize))
            Segments.Add(CurrentSegment)
        End While

        ' Break the 5 byte segments back down into individual bytes
        For Each CurrentSegment As Long In Segments
            For x = 0 To 4
                RetVal.Add((CurrentSegment >> (4 - x) * 8) And &HFF)
            Next
        Next

        ' Remove any bytes of padding from the output
        Dim BytesToRemove As Integer = 5 - (Math.Ceiling(Math.Ceiling(3 * 8 / 5) / 2))
        RetVal.RemoveRange(RetVal.Count - BytesToRemove, BytesToRemove)

        Return RetVal.ToArray()
    End Function

End Module

Making it Work

This code can then be used to encode / decode data as follows:

Dim DataToEncode As Byte() = {54, 124, 84, 25, 19, 156}

Dim Base32 As String
Base32 = DataToEncode.ToBase32String()

Dim DecodedData As Byte()
DecodedData = Base32.FromBase32String()