Base32 Encoding and Decoding in VB.NET

7 August 2011 (9 months ago)

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()

Comments

Kevin North

3 November 2011
(6 months ago)
Avatar

Thanks for the code - exactly what I was looking for, and I also had the same idea (using the base 32 string for a URL). However, it turned out not to be the huge time saver I hoped for as I had to fix a couple of critical bugs. But I'll share the bug fixes here so everyone can benefit.

Fix #1 for ToBase32String():
Insert after the "Dim LastSegmentUsefulDataLength" line:
       If LastSegmentUsefulDataLength = 0 Then
           LastSegmentUsefulDataLength = 8
       End If

Fix #2 for FromBase32String():
Replace the "Dim BytesToRemove" line with these 2 lines:
       Dim BytesToKeep As Integer = CInt(Math.Floor(CDbl(Data.Length) * 5 / 8))
       Dim BytesToRemove As Integer = RetVal.Count - BytesToKeep

Handy verification routine:
   Public Sub TestBase32()
       Dim i As Integer
       Dim j As Integer
       Dim Bytes, Output As Byte()

       For i = 104 To 0 Step -1
           Bytes = Array.CreateInstance(GetType(Byte), i)
           For j = 0 To i - 1
               Bytes(j) = 255 - i + j
           Next j
           Output = FromBase32String(ToBase32String(Bytes, True))
           If Bytes.Length <> Output.Length Then
               Throw New Exception("Old Length = " & Bytes.Length & ", New Length = " & Output.Length)
           End If
           For j = 0 To i - 1
               If Bytes(j) <> Output(j) Then
                   Throw New Exception("Old Byte(" & j & ") = " & Bytes(j).ToString() & ", New Length = " & Output(j).ToString())
               End If
           Next j
       Next i
   End Sub

Leave a Comment