diff options
author | Paragon Initiative Enterprises <security@paragonie.com> | 2016-03-11 19:00:34 -0500 |
---|---|---|
committer | Scott <scott@paragonie.com> | 2016-03-11 19:00:34 -0500 |
commit | d331798e6cabdce20dddaacd78c364aac4adf694 (patch) | |
tree | 6bb1be4e43b7e6035c80b02074f79fca620e0d23 | |
parent | 8eb4800a6e5744deb15d18170b105bbdb06a80da (diff) | |
download | constant_time_encoding-d331798e6cabdce20dddaacd78c364aac4adf694.zip constant_time_encoding-d331798e6cabdce20dddaacd78c364aac4adf694.tar.gz constant_time_encoding-d331798e6cabdce20dddaacd78c364aac4adf694.tar.bz2 |
RFC 4648 base64-encodingv0.2.0
-rw-r--r-- | src/Encoding.php | 228 | ||||
-rw-r--r-- | tests/EncodingTest.php | 155 |
2 files changed, 382 insertions, 1 deletions
diff --git a/src/Encoding.php b/src/Encoding.php index 81983c2..5a5f648 100644 --- a/src/Encoding.php +++ b/src/Encoding.php @@ -25,11 +25,204 @@ namespace ParagonIE\ConstantTime; class Encoding { /** + * Decode a Base32-encoded string into raw binary + * + * @param string $src + * @return string + */ + public static function base32Decode($src) + { + // Remove padding + $srcLen = self::safeStrlen($src); + if ($srcLen === 0) { + return ''; + } + if (($srcLen & 7) === 0) { + if ($src[$srcLen - 1] === '=') { + $srcLen--; + if ($src[$srcLen - 1] === '=') { + $srcLen--; + } + if ($src[$srcLen - 1] === '=') { + $srcLen--; + } + if ($src[$srcLen - 1] === '=') { + $srcLen--; + } + if ($src[$srcLen - 1] === '=') { + $srcLen--; + } + if ($src[$srcLen - 1] === '=') { + $srcLen--; + } + if ($src[$srcLen - 1] === '=') { + $srcLen--; + } + } + } + if (($srcLen & 7) === 1) { + return false; + } + + $err = 0; + $dest = ''; + for ($i = 0; $i + 8 <= $srcLen; $i += 8) { + $c0 = self::base32Decode5Bits(ord($src[$i])); + $c1 = self::base32Decode5Bits(ord($src[$i + 1])); + $c2 = self::base32Decode5Bits(ord($src[$i + 2])); + $c3 = self::base32Decode5Bits(ord($src[$i + 3])); + $c4 = self::base32Decode5Bits(ord($src[$i + 4])); + $c5 = self::base32Decode5Bits(ord($src[$i + 5])); + $c6 = self::base32Decode5Bits(ord($src[$i + 6])); + $c7 = self::base32Decode5Bits(ord($src[$i + 7])); + + $dest .= + chr((($c0 << 3) | ($c1 >> 2) ) & 0xff) . + chr((($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff) . + chr((($c3 << 4) | ($c4 >> 1) ) & 0xff) . + chr((($c4 << 7) | ($c5 << 2) | ($c6 >> 3)) & 0xff) . + chr((($c6 << 5) | ($c7 ) ) & 0xff); + } + if ($i < $srcLen) { + $c0 = self::base32Decode5Bits(ord($src[$i])); + if ($i + 6 < $srcLen) { + $c1 = self::base32Decode5Bits(ord($src[$i + 1])); + $c2 = self::base32Decode5Bits(ord($src[$i + 2])); + $c3 = self::base32Decode5Bits(ord($src[$i + 3])); + $c4 = self::base32Decode5Bits(ord($src[$i + 4])); + $c5 = self::base32Decode5Bits(ord($src[$i + 5])); + $c6 = self::base32Decode5Bits(ord($src[$i + 6])); + + $dest .= + chr((($c0 << 3) | ($c1 >> 2) ) & 0xff) . + chr((($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff) . + chr((($c3 << 4) | ($c4 >> 1) ) & 0xff) . + chr((($c4 << 7) | ($c5 << 2) | ($c6 >> 3)) & 0xff); + } elseif ($i + 5 < $srcLen) { + $c1 = self::base32Decode5Bits(ord($src[$i + 1])); + $c2 = self::base32Decode5Bits(ord($src[$i + 2])); + $c3 = self::base32Decode5Bits(ord($src[$i + 3])); + $c4 = self::base32Decode5Bits(ord($src[$i + 4])); + $c5 = self::base32Decode5Bits(ord($src[$i + 5])); + + $dest .= + chr((($c0 << 3) | ($c1 >> 2) ) & 0xff) . + chr((($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff) . + chr((($c3 << 4) | ($c4 >> 1) ) & 0xff) . + chr((($c4 << 7) | ($c5 << 2) ) & 0xff); + } elseif ($i + 4 < $srcLen) { + $c1 = self::base32Decode5Bits(ord($src[$i + 1])); + $c2 = self::base32Decode5Bits(ord($src[$i + 2])); + $c3 = self::base32Decode5Bits(ord($src[$i + 3])); + $c4 = self::base32Decode5Bits(ord($src[$i + 4])); + + $dest .= + chr((($c0 << 3) | ($c1 >> 2) ) & 0xff) . + chr((($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff) . + chr((($c3 << 4) | ($c4 >> 1) ) & 0xff); + } elseif ($i + 3 < $srcLen) { + $c1 = self::base32Decode5Bits(ord($src[$i + 1])); + $c2 = self::base32Decode5Bits(ord($src[$i + 2])); + $c3 = self::base32Decode5Bits(ord($src[$i + 3])); + + $dest .= + chr((($c0 << 3) | ($c1 >> 2) ) & 0xff) . + chr((($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff) . + ''; //chr((($c3 << 4) ) & 0xff); + } elseif ($i + 2 < $srcLen) { + $c1 = self::base32Decode5Bits(ord($src[$i + 1])); + $c2 = self::base32Decode5Bits(ord($src[$i + 2])); + + $dest .= + chr((($c0 << 3) | ($c1 >> 2) ) & 0xff) . + chr((($c1 << 6) | ($c2 << 1) ) & 0xff); + } elseif ($i + 1 < $srcLen) { + $c1 = self::base32Decode5Bits(ord($src[$i + 1])); + $dest .= + chr((($c0 << 3) | ($c1 >> 2) ) & 0xff); + } else { + $dest .= + chr((($c0 << 3) ) & 0xff); + } + } + return $dest; + } + + /** + * Encode into Base32 (RFC 4648) + * + * @param string $src + * @return string + */ + public static function base32Encode($src) + { + $dest = ''; + $srcLen = self::safeStrlen($src); + for ($i = 0; $i + 5 <= $srcLen; $i += 5) { + $b0 = ord($src[$i]); + $b1 = ord($src[$i + 1]); + $b2 = ord($src[$i + 2]); + $b3 = ord($src[$i + 3]); + $b4 = ord($src[$i + 4]); + $dest .= + self::base32Encode5Bits( ($b0 >> 3) & 31) . + self::base32Encode5Bits((($b0 << 2) | ($b1 >> 6)) & 31) . + self::base32Encode5Bits((($b1 >> 1) ) & 31) . + self::base32Encode5Bits((($b1 << 4) | ($b2 >> 4)) & 31) . + self::base32Encode5Bits((($b2 << 1) | ($b3 >> 7)) & 31) . + self::base32Encode5Bits((($b3 >> 2) ) & 31) . + self::base32Encode5Bits((($b3 << 3) | ($b4 >> 5)) & 31) . + self::base32Encode5Bits( $b4 & 31); + } + if ($i < $srcLen) { + $b0 = ord($src[$i]); + if ($i + 3 < $srcLen) { + $b1 = ord($src[$i + 1]); + $b2 = ord($src[$i + 2]); + $b3 = ord($src[$i + 3]); + $dest .= + self::base32Encode5Bits( ($b0 >> 3) & 31) . + self::base32Encode5Bits((($b0 << 2) | ($b1 >> 6)) & 31) . + self::base32Encode5Bits((($b1 >> 1) ) & 31) . + self::base32Encode5Bits((($b1 << 4) | ($b2 >> 4)) & 31) . + self::base32Encode5Bits((($b2 << 1) | ($b3 >> 7)) & 31) . + self::base32Encode5Bits((($b3 >> 2) ) & 31) . + self::base32Encode5Bits((($b3 << 3) ) & 31) . + '='; + } elseif ($i + 2 < $srcLen) { + $b1 = ord($src[$i + 1]); + $b2 = ord($src[$i + 2]); + $dest .= + self::base32Encode5Bits( ($b0 >> 3) & 31) . + self::base32Encode5Bits((($b0 << 2) | ($b1 >> 6)) & 31) . + self::base32Encode5Bits((($b1 >> 1) ) & 31) . + self::base32Encode5Bits((($b1 << 4) | ($b2 >> 4)) & 31) . + self::base32Encode5Bits((($b2 << 1) ) & 31) . + '==='; + } elseif ($i + 1 < $srcLen) { + $b1 = ord($src[$i + 1]); + $dest .= + self::base32Encode5Bits( ($b0 >> 3) & 31) . + self::base32Encode5Bits((($b0 << 2) | ($b1 >> 6)) & 31) . + self::base32Encode5Bits((($b1 >> 1) ) & 31) . + self::base32Encode5Bits((($b1 << 4) ) & 31) . + '===='; + } else { + $dest .= + self::base32Encode5Bits( ($b0 >> 3) & 31) . + self::base32Encode5Bits( ($b0 << 2) & 31) . + '======'; + } + } + return $dest; + } + + /** * Encode into Base64 * * Base64 character set "[A-Z][a-z][0-9]+/" * - * @param $src + * @param string $src * @return string */ public static function base64Encode($src) @@ -366,6 +559,7 @@ class Encoding { $hex_pos = 0; $bin = ''; + $c_acc = 0; $hex_len = self::safeStrlen($hex_string); $state = 0; @@ -394,6 +588,38 @@ class Encoding /** * + * @param $src + * @return int + */ + protected static function base32Decode5Bits($src) + { + $ret = -1; + + // if ($src > 64 && $src < 91) $ret += $src - 65 + 1; // -64 + $ret += (((0x40 - $src) & ($src - 0x5b)) >> 8) & ($src - 64); + + // if ($src > 0x31 && $src < 0x38) $ret += $src - 24 + 1; // -23 + $ret += (((0x31 - $src) & ($src - 0x38)) >> 8) & ($src - 23); + + return $ret; + } + + /** + * @param $src + * @return string + */ + protected static function base32Encode5Bits($src) + { + $diff = 0x41; + + // if ($src > 25) $ret -= 40; + $diff -= ((25 - $src) >> 8) & 41; + + return chr($src + $diff); + } + + /** + * * Base64 character set: * [A-Z] [a-z] [0-9] + / * 0x41-0x5a, 0x61-0x7a, 0x30-0x39, 0x2b, 0x2f diff --git a/tests/EncodingTest.php b/tests/EncodingTest.php index 556c5a5..17a7af2 100644 --- a/tests/EncodingTest.php +++ b/tests/EncodingTest.php @@ -3,6 +3,152 @@ use \ParagonIE\ConstantTime\Encoding; class EncodingTest extends PHPUnit_Framework_TestCase { + /** + * Based on test vectors from RFC 4648 + */ + public function testBase32Encode() + { + $this->assertEquals( + Encoding::base32Encode("\x00"), + 'AA======' + ); + $this->assertEquals( + Encoding::base32Encode("\x00\x00"), + 'AAAA====' + ); + $this->assertEquals( + Encoding::base32Encode("\x00\x00\x00"), + 'AAAAA===' + ); + $this->assertEquals( + Encoding::base32Encode("\x00\x00\x00\x00"), + 'AAAAAAA=' + ); + $this->assertEquals( + Encoding::base32Encode("\x00\x00\x00\x00\x00"), + 'AAAAAAAA' + ); + $this->assertEquals( + Encoding::base32Encode("f"), + 'MY======' + ); + $this->assertEquals( + Encoding::base32Encode("fo"), + 'MZXQ====' + ); + $this->assertEquals( + Encoding::base32Encode("foo"), + 'MZXW6===' + ); + $this->assertEquals( + Encoding::base32Encode("foob"), + 'MZXW6YQ=' + ); + $this->assertEquals( + Encoding::base32Encode("fooba"), + 'MZXW6YTB' + ); + $this->assertEquals( + Encoding::base32Encode("foobar"), + 'MZXW6YTBOI======' + ); + $this->assertEquals( + Encoding::base32Encode("\x00\x00\x0F\xFF\xFF"), + 'AAAA7777' + ); + $this->assertEquals( + Encoding::base32Encode("\xFF\xFF\xF0\x00\x00"), + '7777AAAA' + ); + + $this->assertEquals( + Encoding::base32Encode("\xce\x73\x9c\xe7\x39"), + 'ZZZZZZZZ' + ); + $this->assertEquals( + Encoding::base32Encode("\xd6\xb5\xad\x6b\x5a"), + '22222222' + ); + } + /** + * Based on test vectors from RFC 4648 + */ + public function testBase32Decode() + { + $this->assertEquals( + "\x00\x00\x00\x00\x00\x00", + Encoding::base32Decode('AAAAAAAAAA======') + ); + $this->assertEquals( + "\x00\x00\x00\x00\x00\x00\x00", + Encoding::base32Decode('AAAAAAAAAAAA====') + ); + $this->assertEquals( + "\x00\x00\x00\x00\x00\x00\x00\x00", + Encoding::base32Decode('AAAAAAAAAAAAA===') + ); + $this->assertEquals( + "\x00\x00\x00\x00\x00\x00\x00\x00\x00", + Encoding::base32Decode('AAAAAAAAAAAAAAA=') + ); + $this->assertEquals( + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + Encoding::base32Decode('AAAAAAAAAAAAAAAA') + ); + $this->assertEquals( + "\x00", + Encoding::base32Decode('AA======') + ); + $this->assertEquals( + "\x00\x00", + Encoding::base32Decode('AAAA====') + ); + $this->assertEquals( + "\x00\x00\x00", + Encoding::base32Decode('AAAAA===') + ); + $this->assertEquals( + "\x00\x00\x00\x00", + Encoding::base32Decode('AAAAAAA=') + ); + $this->assertEquals( + "\x00\x00\x00\x00\x00", + Encoding::base32Decode('AAAAAAAA') + ); + $this->assertEquals( + "\x00\x00\x0F\xFF\xFF", + Encoding::base32Decode('AAAA7777') + ); + $this->assertEquals( + "\xFF\xFF\xF0\x00\x00", + Encoding::base32Decode('7777AAAA') + ); + $this->assertEquals( + "\xce\x73\x9c\xe7\x39", + Encoding::base32Decode('ZZZZZZZZ') + ); + $this->assertEquals( + "\xd6\xb5\xad\x6b\x5a", + Encoding::base32Decode('22222222') + ); + $this->assertEquals( + 'foobar', + Encoding::base32Decode('MZXW6YTBOI======') + ); + + $rand = random_bytes(9); + $enc = Encoding::base32Encode($rand); + + $this->assertEquals( + Encoding::base32Encode($rand), + Encoding::base32Encode(Encoding::base32Decode($enc)) + ); + $this->assertEquals( + $rand, + Encoding::base32Decode($enc) + ); + } + public function testBasicEncoding() { $str = random_bytes(33); @@ -11,5 +157,14 @@ class EncodingTest extends PHPUnit_Framework_TestCase $str, Encoding::base64Decode($enc) ); + + for ($i = 1; $i < 34; ++$i) { + $rand = random_bytes($i); + $enc = Encoding::base32Encode($rand); + $this->assertEquals( + bin2hex($rand), + bin2hex(Encoding::base32Decode($enc)) + ); + } } }
\ No newline at end of file |