Skip to content

Commit

Permalink
* Add Base64UrlSafe
Browse files Browse the repository at this point in the history
* Create an RFC4648 class for strict RFC conformity
* Added more unit tests
  • Loading branch information
paragonie-security authored and paragonie-scott committed Mar 14, 2016
1 parent c88e997 commit 4a7477d
Show file tree
Hide file tree
Showing 9 changed files with 387 additions and 68 deletions.
38 changes: 13 additions & 25 deletions src/Base32.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*
* @package ParagonIE\ConstantTime
*/
abstract class Base32
abstract class Base32 implements EncoderInterface
{
/**
* Decode a Base32-encoded string into raw binary
Expand All @@ -18,41 +18,29 @@ abstract class Base32
public static function decode($src)
{
// Remove padding
$srcLen = Core::safeStrlen($src);
$srcLen = Binary::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--;
}
for ($j = 0; $j < 7; ++$j) {
if ($src[$srcLen - 1] === '=') {
$srcLen--;
} else {
break;
}
}
}
if (($srcLen & 7) === 1) {
return false;
throw new \RangeException(
'Incorrect padding'
);
}

$err = 0;
$dest = '';
for ($i = 0; $i + 8 <= $srcLen; $i += 8) {
$chunk = \unpack('C*', Core::safeSubstr($src, $i, 8));
$chunk = \unpack('C*', Binary::safeSubstr($src, $i, 8));
$c0 = static::decode5Bits($chunk[1]);
$c1 = static::decode5Bits($chunk[2]);
$c2 = static::decode5Bits($chunk[3]);
Expand All @@ -73,7 +61,7 @@ public static function decode($src)
$err |= ($c0 | $c1 | $c2 | $c3 | $c4 | $c5 | $c6 | $c7) >> 8;
}
if ($i < $srcLen) {
$chunk = \unpack('C*', Core::safeSubstr($src, $i, $srcLen - $i));
$chunk = \unpack('C*', Binary::safeSubstr($src, $i, $srcLen - $i));
$c0 = static::decode5Bits($chunk[1]);

if ($i + 6 < $srcLen) {
Expand Down Expand Up @@ -174,9 +162,9 @@ public static function decode($src)
public static function encode($src)
{
$dest = '';
$srcLen = Core::safeStrlen($src);
$srcLen = Binary::safeStrlen($src);
for ($i = 0; $i + 5 <= $srcLen; $i += 5) {
$chunk = \unpack('C*', Core::safeSubstr($src, $i, 5));
$chunk = \unpack('C*', Binary::safeSubstr($src, $i, 5));
$b0 = $chunk[1];
$b1 = $chunk[2];
$b2 = $chunk[3];
Expand All @@ -193,7 +181,7 @@ public static function encode($src)
static::encode5Bits( $b4 & 31);
}
if ($i < $srcLen) {
$chunk = \unpack('C*', Core::safeSubstr($src, $i, $srcLen - $i));
$chunk = \unpack('C*', Binary::safeSubstr($src, $i, $srcLen - $i));
$b0 = $chunk[1];
if ($i + 3 < $srcLen) {
$b1 = $chunk[2];
Expand Down
24 changes: 16 additions & 8 deletions src/Base64.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*
* @package ParagonIE\ConstantTime
*/
abstract class Base64
abstract class Base64 implements EncoderInterface
{
/**
* Encode into Base64
Expand All @@ -20,9 +20,9 @@ abstract class Base64
public static function encode($src)
{
$dest = '';
$srcLen = Core::safeStrlen($src);
$srcLen = Binary::safeStrlen($src);
for ($i = 0; $i + 3 <= $srcLen; $i += 3) {
$chunk = \unpack('C*', Core::safeSubstr($src, $i, 3));
$chunk = \unpack('C*', Binary::safeSubstr($src, $i, 3));
$b0 = $chunk[1];
$b1 = $chunk[2];
$b2 = $chunk[3];
Expand All @@ -34,7 +34,7 @@ public static function encode($src)
static::encode6Bits( $b2 & 63);
}
if ($i < $srcLen) {
$chunk = \unpack('C*', Core::safeSubstr($src, $i, $srcLen - $i));
$chunk = \unpack('C*', Binary::safeSubstr($src, $i, $srcLen - $i));
$b0 = $chunk[1];
if ($i + 1 < $srcLen) {
$b1 = $chunk[2];
Expand Down Expand Up @@ -63,7 +63,7 @@ public static function encode($src)
public static function decode($src)
{
// Remove padding
$srcLen = Core::safeStrlen($src);
$srcLen = Binary::safeStrlen($src);
if ($srcLen === 0) {
return '';
}
Expand All @@ -76,13 +76,15 @@ public static function decode($src)
}
}
if (($srcLen & 3) === 1) {
return false;
throw new \RangeException(
'Incorrect padding'
);
}

$err = 0;
$dest = '';
for ($i = 0; $i + 4 <= $srcLen; $i += 4) {
$chunk = \unpack('C*', Core::safeSubstr($src, $i, 4));
$chunk = \unpack('C*', Binary::safeSubstr($src, $i, 4));
$c0 = static::decode6Bits($chunk[1]);
$c1 = static::decode6Bits($chunk[2]);
$c2 = static::decode6Bits($chunk[3]);
Expand All @@ -95,9 +97,12 @@ public static function decode($src)
((($c2 << 6) | $c3 ) & 0xff)
);
$err |= ($c0 | $c1 | $c2 | $c3) >> 8;
if ($err !== 0) {
\var_dump(Binary::safeSubstr($src, $i, 4), [$c0, $c1, $c2, $c3]);
}
}
if ($i < $srcLen) {
$chunk = \unpack('C*', Core::safeSubstr($src, $i, $srcLen - $i));
$chunk = \unpack('C*', Binary::safeSubstr($src, $i, $srcLen - $i));
$c0 = static::decode6Bits($chunk[1]);
$c1 = static::decode6Bits($chunk[2]);

Expand All @@ -116,6 +121,9 @@ public static function decode($src)
);
$err |= ($c0 | $c1) >> 8;
}
if ($err !== 0) {
\var_dump(Binary::safeSubstr($src, $i, $srcLen - $i), [$c0, $c1, $c2, $c3]);
}
}
if ($err !== 0) {
throw new \RangeException(
Expand Down
66 changes: 66 additions & 0 deletions src/Base64UrlSafe.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php
namespace ParagonIE\ConstantTime;

/**
* Class Base64DotSlash
* ./[A-Z][a-z][0-9]
*
* @package ParagonIE\ConstantTime
*/
abstract class Base64UrlSafe extends Base64
{

/**
*
* Base64 character set:
* [A-Z] [a-z] [0-9] - _
* 0x41-0x5a, 0x61-0x7a, 0x30-0x39, 0x2d, 0x5f
*
* @param int $src
* @return int
*/
protected static function decode6Bits($src)
{
$ret = -1;

// if ($src > 0x40 && $src < 0x5b) $ret += $src - 0x41 + 1; // -64
$ret += (((0x40 - $src) & ($src - 0x5b)) >> 8) & ($src - 64);

// if ($src > 0x60 && $src < 0x7b) $ret += $src - 0x61 + 26 + 1; // -70
$ret += (((0x60 - $src) & ($src - 0x7b)) >> 8) & ($src - 70);

// if ($src > 0x2f && $src < 0x3a) $ret += $src - 0x30 + 52 + 1; // 5
$ret += (((0x2f - $src) & ($src - 0x3a)) >> 8) & ($src + 5);

// if ($src == 0x2c) $ret += 62 + 1;
$ret += (((0x2c - $src) & ($src - 0x2e)) >> 8) & 63;

// if ($src == 0x5f) ret += 63 + 1;
$ret += (((0x5e - $src) & ($src - 0x60)) >> 8) & 64;

return $ret;
}

/**
* @param int $src
* @return string
*/
protected static function encode6Bits($src)
{
$diff = 0x41;

// if ($src > 25) $diff += 0x61 - 0x41 - 26; // 6
$diff += ((25 - $src) >> 8) & 6;

// if ($src > 51) $diff += 0x30 - 0x61 - 26; // -75
$diff -= ((51 - $src) >> 8) & 75;

// if ($src > 61) $diff += 0x2d - 0x30 - 10; // -13
$diff -= ((61 - $src) >> 8) & 13;

// if ($src > 62) $diff += 0x5f - 0x2b - 1; // 3
$diff += ((62 - $src) >> 8) & 49;

return \pack('C', $src + $diff);
}
}
2 changes: 1 addition & 1 deletion src/Core.php → src/Binary.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php
namespace ParagonIE\ConstantTime;

abstract class Core
abstract class Binary
{
/**
* Safe string length
Expand Down
24 changes: 24 additions & 0 deletions src/EncoderInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php
namespace ParagonIE\ConstantTime;

interface EncoderInterface
{
/**
* Convert a binary string into a hexadecimal string without cache-timing
* leaks
*
* @param string $bin_string (raw binary)
* @return string
*/
public static function encode($bin_string);

/**
* Convert a binary string into a hexadecimal string without cache-timing
* leaks
*
* @param string $encoded_string
* @return string (raw binary)
*/
public static function decode($encoded_string);

}
41 changes: 34 additions & 7 deletions src/Hex.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php
namespace ParagonIE\ConstantTime;

abstract class Hex
abstract class Hex implements EncoderInterface
{
/**
* Convert a binary string into a hexadecimal string without cache-timing
Expand All @@ -13,9 +13,9 @@ abstract class Hex
public static function encode($bin_string)
{
$hex = '';
$len = Core::safeStrlen($bin_string);
$len = Binary::safeStrlen($bin_string);
for ($i = 0; $i < $len; ++$i) {
$chunk = \unpack('C', Core::safeSubstr($bin_string, $i, 2));
$chunk = \unpack('C', Binary::safeSubstr($bin_string, $i, 2));
$c = $chunk[1] & 0xf;
$b = $chunk[1] >> 4;
$hex .= pack(
Expand All @@ -27,6 +27,30 @@ public static function encode($bin_string)
return $hex;
}

/**
* Convert a binary string into a hexadecimal string without cache-timing
* leaks, returning uppercase letters (as per RFC 4648)
*
* @param string $bin_string (raw binary)
* @return string
*/
public static function encodeUpper($bin_string)
{
$hex = '';
$len = Binary::safeStrlen($bin_string);
for ($i = 0; $i < $len; ++$i) {
$chunk = \unpack('C', Binary::safeSubstr($bin_string, $i, 2));
$c = $chunk[1] & 0xf;
$b = $chunk[1] >> 4;
$hex .= pack(
'CC',
(55 + $b + ((($b - 10) >> 8) & ~6)),
(55 + $c + ((($c - 10) >> 8) & ~6))
);
}
return $hex;
}

/**
* Convert a hexadecimal string into a binary string without cache-timing
* leaks
Expand All @@ -40,8 +64,13 @@ public static function decode($hex_string)
$hex_pos = 0;
$bin = '';
$c_acc = 0;
$hex_len = Core::safeStrlen($hex_string);
$hex_len = Binary::safeStrlen($hex_string);
$state = 0;
if (($hex_len & 1) !== 0) {
throw new \RangeException(
'Expected an even number of hexadecimal characters'
);
}

$chunk = \unpack('C*', $hex_string);
while ($hex_pos < $hex_len) {
Expand All @@ -66,6 +95,4 @@ public static function decode($hex_string)
}
return $bin;
}


}
}
Loading

0 comments on commit 4a7477d

Please sign in to comment.