Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

I don't actually know much about how base32 (or base64) works, but I noticed that there was no official base32 implementation in PHP, so I figured I'd make one.

I Googled around a bit to figure out how it works, and found this page. Using the examples at the bottom, I hacked up this base32 class. GitHub project: https://github.com/NTICompass/PHP-Base32

<?php
/**
 * NTICompass' crappy base32 library for PHP
 * 
 * http://labs.nticompassinc.com
 */
class Base32{
    var $encode, $decode, $type;

    // Supports RFC 4648 (default) or Crockford (http://www.crockford.com/wrmg/base32.html)
    function __construct($alphabet='rfc4648'){
        $alphabet = strtolower($alphabet);
        $this->type = $alphabet;
        // Crockford's alphabet removes I,L,O, and U
        $crockfordABC = range('A', 'Z');
        unset($crockfordABC[8], $crockfordABC[11], $crockfordABC[14], $crockfordABC[20]);
        $crockfordABC = array_values($crockfordABC);

        $alphabets = array(
            'rfc4648' => array_merge(range('A','Z'), range(2,7), array('=')),
            'crockford' => array_merge(range(0,9), $crockfordABC, array('='))
        );
        $this->encode = $alphabets[$alphabet];
        $this->decode = array_flip($this->encode);
        // Add extra letters for Crockford's alphabet
        if($alphabet === 'crockford'){
            $this->decode['O'] = 0;
            $this->decode['I'] = 1;
            $this->decode['L'] = 1;
        }
    }

    private function bin_chunk($binaryString, $bits){
        $binaryString = chunk_split($binaryString, $bits, ' ');
        if($this->endsWith($binaryString, ' ')){
            $binaryString = substr($binaryString, 0, strlen($binaryString)-1);
        }
        return explode(' ', $binaryString);
    }

    // String <-> Binary conversion
    // Based off: http://psoug.org/snippet/PHP-Binary-to-Text-Text-to-Binary_380.htm

    private function bin2str($binaryString){
        // Make sure binary string is in 8-bit chunks
        $binaryArray = $this->bin_chunk($binaryString, 8);
        $string = '';
        foreach($binaryArray as $bin){
            // Pad each value to 8 bits
            $bin = str_pad($bin, 8, 0, STR_PAD_RIGHT);
            // Convert binary strings to ascii
            $string .= chr(bindec($bin));
        }
        return $string;
    }

    private function str2bin($input){
        $bin = '';
        foreach(str_split($input) as $s){
            // Return each character as an 8-bit binary string
            $s = decbin(ord($s));
            $bin .= str_pad($s, 8, 0, STR_PAD_LEFT);
        }
        return $bin;
    }

    // starts/endsWith from:
    // http://stackoverflow.com/questions/834303/php-startswith-and-endswith-functions/834355#834355

    private function startsWith($haystack, $needle){
        $length = strlen($needle);
        return substr($haystack, 0, $length) === $needle;
    }

    private function endsWith($haystack, $needle){
        $length = strlen($needle);
        return substr($haystack, -$length) === $needle;
    }

    // base32 info from: http://www.garykessler.net/library/base64.html

    // base32_encode
    public function base32_encode($string){
        // Convert string to binary
        $binaryString = $this->str2bin($string);

        // Break into 5-bit chunks, then break that into an array
        $binaryArray = $this->bin_chunk($binaryString, 5);

        // Pad array to be divisible by 8
        while(count($binaryArray) % 8 !== 0){
            $binaryArray[] = null;
        }

        $base32String = '';

        // Encode in base32
        foreach($binaryArray as $bin){
            $char = 32;
            if(!is_null($bin)){
                // Pad the binary strings
                $bin = str_pad($bin, 5, 0, STR_PAD_RIGHT);
                $char = bindec($bin);
            }
            // Base32 character
            $base32String .= $this->encode[$char];
        }

        return $base32String;
    }

    // base32_decode
    public function base32_decode($base32String){
        $base32Array = str_split(str_replace('-', '', strtoupper($base32String)));
        $binaryArray = array();
        $string = '';
        foreach($base32Array as $str){
            $char = $this->decode[$str];
            if($char !== 32){
                $char = decbin($char);
                $string .= str_pad($char, 5, 0, STR_PAD_LEFT);
            }
        }
        while(strlen($string) %8 !== 0){
            $string = substr($string, 0, strlen($string)-1);
        }
        return $this->bin2str($string);
    }
}
?>

This code works, I've tested it using this page, and it gives the same result, but I don't think this is the best way of doing base32.

Is there a better way to do base32 that's maybe more efficient than what I have?

share|improve this question
add comment

3 Answers 3

up vote 2 down vote accepted

Your constructor going through all the work to build both alphabets and then throwing one away seems odd. I'd probably have a Base32 base class, and have the two alphabets be subclasses.

Using the binary conversion does seem problematic. This is especially true since the numbers are already in binary inside the computer.

I can see a few different approaches:

Make an array of 5 bit numbers:

value = 0
bits_remaining = 0
while more data or bits_remaining:
     while bits_remaining  > 5:
          remove first five bits of value and place into array
     value = value << 8 + ord(next letter in data)

Hand code for each 8 byte case:

 codes = array(
     (value[0] & 0xfd)) >> 3,
     (value[0] & 0x3) << 3 | value[1] & (0x7) >> 3,
     ...

Since (8 * 5) % 8 = 0 you can chunk your data into eight bit pieces and just hand code the neccesary bitflags to figure out which index should be fetched.

Use GMP

 value = gmp_init(0)
 for( letter in data)
 {
      value = gmp_or( gmp_mult( value, gmp_pow(2, 8)), ord(letter))
 }
 gmp_strval(value, 32)

(Actually algorithm have not been thought through, but perhaps this might give you an idea of things to try)

share|improve this answer
    
I wasn't really sure how to use bit operators, which is why I did it the way I did. I'll see what I can hack up from this. Thanks :-) –  Rocket Hazmat Oct 17 '11 at 0:43
add comment

For base32_decode You can use this

static function base32_decode($s){
        static $alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';

        $tmp = '';

        foreach (str_split($s) as $c) {
            if (false === ($v = strpos($alphabet, $c))) {
                $v = 0;
            }
            $tmp .= sprintf('%05b', $v);
        }
        $args = array_map('bindec', str_split($tmp, 8));
        array_unshift($args, 'C*');

        return rtrim(call_user_func_array('pack', $args), "\0");
    }
share|improve this answer
    
Hmm, this seems like a much quicker way than mine. I don't know much about how pack works, but this looks like a neat method. –  Rocket Hazmat Jan 9 '13 at 15:22
add comment

Nice, but just a little bit flawed: if the original file ends in a \0 it is stripped from the decoded file. Removing rtrim isn't an option either, as that adds unwanted \0

Not sure it it's a flaw of the code or a flaw in rfc4648

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.