Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.
   1  <?php
   2  namespace ParagonIE\ConstantTime;
   3  
   4  /**
   5   *  Copyright (c) 2016 - 2017 Paragon Initiative Enterprises.
   6   *  Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com)
   7   *
   8   *  Permission is hereby granted, free of charge, to any person obtaining a copy
   9   *  of this software and associated documentation files (the "Software"), to deal
  10   *  in the Software without restriction, including without limitation the rights
  11   *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  12   *  copies of the Software, and to permit persons to whom the Software is
  13   *  furnished to do so, subject to the following conditions:
  14   *
  15   *  The above copyright notice and this permission notice shall be included in all
  16   *  copies or substantial portions of the Software.
  17   *
  18   *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  19   *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  20   *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  21   *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  22   *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  23   *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  24   *  SOFTWARE.
  25   */
  26  
  27  /**
  28   * Class Base32
  29   * [A-Z][2-7]
  30   *
  31   * @package ParagonIE\ConstantTime
  32   */
  33  abstract class Base32 implements EncoderInterface
  34  {
  35      /**
  36       * Decode a Base32-encoded string into raw binary
  37       *
  38       * @param string $src
  39       * @param bool $strictPadding
  40       * @return string
  41       */
  42      public static function decode($src, $strictPadding = \false)
  43      {
  44          return static::doDecode($src, \false, $strictPadding);
  45      }
  46  
  47      /**
  48       * Decode an uppercase Base32-encoded string into raw binary
  49       *
  50       * @param string $src
  51       * @param bool $strictPadding
  52       * @return string
  53       */
  54      public static function decodeUpper($src, $strictPadding = \false)
  55      {
  56          return static::doDecode($src, \true, $strictPadding);
  57      }
  58  
  59      /**
  60       * Encode into Base32 (RFC 4648)
  61       *
  62       * @param string $src
  63       * @return string
  64       */
  65      public static function encode($src)
  66      {
  67          return static::doEncode($src, \false);
  68      }
  69  
  70      /**
  71       * Encode into Base32 (RFC 4648)
  72       *
  73       * @param string $src
  74       * @return string
  75       * @throws \TypeError
  76       */
  77      public static function encodeUnpadded($src)
  78      {
  79          return static::doEncode($src, false, false);
  80      }
  81  
  82      /**
  83       * Encode into uppercase Base32 (RFC 4648)
  84       *
  85       * @param string $src
  86       * @return string
  87       */
  88      public static function encodeUpper($src)
  89      {
  90          return static::doEncode($src, \true);
  91      }
  92  
  93      /**
  94       * Encode into uppercase Base32 (RFC 4648)
  95       *
  96       * @param string $src
  97       * @return string
  98       * @throws \TypeError
  99       */
 100      public static function encodeUpperUnpadded($src)
 101      {
 102          return static::doEncode($src, true, false);
 103      }
 104  
 105      /**
 106       * Uses bitwise operators instead of table-lookups to turn 5-bit integers
 107       * into 8-bit integers.
 108       *
 109       * @param int $src
 110       * @return int
 111       */
 112      protected static function decode5Bits($src)
 113      {
 114          $ret = -1;
 115  
 116          // if ($src > 96 && $src < 123) $ret += $src - 97 + 1; // -64
 117          $ret += (((0x60 - $src) & ($src - 0x7b)) >> 8) & ($src - 96);
 118  
 119          // if ($src > 0x31 && $src < 0x38) $ret += $src - 24 + 1; // -23
 120          $ret += (((0x31 - $src) & ($src - 0x38)) >> 8) & ($src - 23);
 121  
 122          return $ret;
 123      }
 124  
 125      /**
 126       * Uses bitwise operators instead of table-lookups to turn 5-bit integers
 127       * into 8-bit integers.
 128       *
 129       * Uppercase variant.
 130       *
 131       * @param int $src
 132       * @return int
 133       */
 134      protected static function decode5BitsUpper($src)
 135      {
 136          $ret = -1;
 137  
 138          // if ($src > 64 && $src < 91) $ret += $src - 65 + 1; // -64
 139          $ret += (((0x40 - $src) & ($src - 0x5b)) >> 8) & ($src - 64);
 140  
 141          // if ($src > 0x31 && $src < 0x38) $ret += $src - 24 + 1; // -23
 142          $ret += (((0x31 - $src) & ($src - 0x38)) >> 8) & ($src - 23);
 143  
 144          return $ret;
 145      }
 146  
 147      /**
 148       * Uses bitwise operators instead of table-lookups to turn 8-bit integers
 149       * into 5-bit integers.
 150       *
 151       * @param int $src
 152       * @return string
 153       */
 154      protected static function encode5Bits($src)
 155      {
 156          $diff = 0x61;
 157  
 158          // if ($src > 25) $ret -= 72;
 159          $diff -= ((25 - $src) >> 8) & 73;
 160  
 161          return \pack('C', $src + $diff);
 162      }
 163  
 164      /**
 165       * Uses bitwise operators instead of table-lookups to turn 8-bit integers
 166       * into 5-bit integers.
 167       *
 168       * Uppercase variant.
 169       *
 170       * @param int $src
 171       * @return string
 172       */
 173      protected static function encode5BitsUpper($src)
 174      {
 175          $diff = 0x41;
 176  
 177          // if ($src > 25) $ret -= 40;
 178          $diff -= ((25 - $src) >> 8) & 41;
 179  
 180          return \pack('C', $src + $diff);
 181      }
 182  
 183  
 184      /**
 185       * Base32 decoding
 186       *
 187       * @param string $src
 188       * @param bool $upper
 189       * @param bool $strictPadding
 190       * @return string
 191       */
 192      protected static function doDecode($src, $upper = \false, $strictPadding = \true)
 193      {
 194          // We do this to reduce code duplication:
 195          $method = $upper
 196              ? 'decode5BitsUpper'
 197              : 'decode5Bits';
 198  
 199          // Remove padding
 200          $srcLen = Binary::safeStrlen($src);
 201          if ($srcLen === 0) {
 202              return '';
 203          }
 204          if ($strictPadding) {
 205              if (($srcLen & 7) === 0) {
 206                  for ($j = 0; $j < 7; ++$j) {
 207                      if ($src[$srcLen - 1] === '=') {
 208                          $srcLen--;
 209                      } else {
 210                          break;
 211                      }
 212                  }
 213              }
 214              if (($srcLen & 7) === 1) {
 215                  throw new \RangeException(
 216                      'Incorrect padding'
 217                  );
 218              }
 219          } else {
 220              $src = \rtrim($src, '=');
 221              $srcLen = Binary::safeStrlen($src);
 222          }
 223  
 224          $err = 0;
 225          $dest = '';
 226          // Main loop (no padding):
 227          for ($i = 0; $i + 8 <= $srcLen; $i += 8) {
 228              $chunk = \unpack('C*', Binary::safeSubstr($src, $i, 8));
 229              $c0 = static::$method($chunk[1]);
 230              $c1 = static::$method($chunk[2]);
 231              $c2 = static::$method($chunk[3]);
 232              $c3 = static::$method($chunk[4]);
 233              $c4 = static::$method($chunk[5]);
 234              $c5 = static::$method($chunk[6]);
 235              $c6 = static::$method($chunk[7]);
 236              $c7 = static::$method($chunk[8]);
 237  
 238              $dest .= \pack(
 239                  'CCCCC',
 240                  (($c0 << 3) | ($c1 >> 2)             ) & 0xff,
 241                  (($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff,
 242                  (($c3 << 4) | ($c4 >> 1)             ) & 0xff,
 243                  (($c4 << 7) | ($c5 << 2) | ($c6 >> 3)) & 0xff,
 244                  (($c6 << 5) | ($c7     )             ) & 0xff
 245              );
 246              $err |= ($c0 | $c1 | $c2 | $c3 | $c4 | $c5 | $c6 | $c7) >> 8;
 247          }
 248          // The last chunk, which may have padding:
 249          if ($i < $srcLen) {
 250              $chunk = \unpack('C*', Binary::safeSubstr($src, $i, $srcLen - $i));
 251              $c0 = static::$method($chunk[1]);
 252  
 253              if ($i + 6 < $srcLen) {
 254                  $c1 = static::$method($chunk[2]);
 255                  $c2 = static::$method($chunk[3]);
 256                  $c3 = static::$method($chunk[4]);
 257                  $c4 = static::$method($chunk[5]);
 258                  $c5 = static::$method($chunk[6]);
 259                  $c6 = static::$method($chunk[7]);
 260  
 261                  $dest .= \pack(
 262                      'CCCC',
 263                      (($c0 << 3) | ($c1 >> 2)             ) & 0xff,
 264                      (($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff,
 265                      (($c3 << 4) | ($c4 >> 1)             ) & 0xff,
 266                      (($c4 << 7) | ($c5 << 2) | ($c6 >> 3)) & 0xff
 267                  );
 268                  $err |= ($c0 | $c1 | $c2 | $c3 | $c4 | $c5 | $c6) >> 8;
 269              } elseif ($i + 5 < $srcLen) {
 270                  $c1 = static::$method($chunk[2]);
 271                  $c2 = static::$method($chunk[3]);
 272                  $c3 = static::$method($chunk[4]);
 273                  $c4 = static::$method($chunk[5]);
 274                  $c5 = static::$method($chunk[6]);
 275  
 276                  $dest .= \pack(
 277                      'CCCC',
 278                      (($c0 << 3) | ($c1 >> 2)             ) & 0xff,
 279                      (($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff,
 280                      (($c3 << 4) | ($c4 >> 1)             ) & 0xff,
 281                      (($c4 << 7) | ($c5 << 2)             ) & 0xff
 282                  );
 283                  $err |= ($c0 | $c1 | $c2 | $c3 | $c4 | $c5) >> 8;
 284              } elseif ($i + 4 < $srcLen) {
 285                  $c1 = static::$method($chunk[2]);
 286                  $c2 = static::$method($chunk[3]);
 287                  $c3 = static::$method($chunk[4]);
 288                  $c4 = static::$method($chunk[5]);
 289  
 290                  $dest .= \pack(
 291                      'CCC',
 292                      (($c0 << 3) | ($c1 >> 2)             ) & 0xff,
 293                      (($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff,
 294                      (($c3 << 4) | ($c4 >> 1)             ) & 0xff
 295                  );
 296                  $err |= ($c0 | $c1 | $c2 | $c3 | $c4) >> 8;
 297              } elseif ($i + 3 < $srcLen) {
 298                  $c1 = static::$method($chunk[2]);
 299                  $c2 = static::$method($chunk[3]);
 300                  $c3 = static::$method($chunk[4]);
 301  
 302                  $dest .= \pack(
 303                      'CC',
 304                      (($c0 << 3) | ($c1 >> 2)             ) & 0xff,
 305                      (($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff
 306                  );
 307                  $err |= ($c0 | $c1 | $c2 | $c3) >> 8;
 308              } elseif ($i + 2 < $srcLen) {
 309                  $c1 = static::$method($chunk[2]);
 310                  $c2 = static::$method($chunk[3]);
 311  
 312                  $dest .= \pack(
 313                      'CC',
 314                      (($c0 << 3) | ($c1 >> 2)             ) & 0xff,
 315                      (($c1 << 6) | ($c2 << 1)             ) & 0xff
 316                  );
 317                  $err |= ($c0 | $c1 | $c2) >> 8;
 318              } elseif ($i + 1 < $srcLen) {
 319                  $c1 = static::$method($chunk[2]);
 320  
 321                  $dest .= \pack(
 322                      'C',
 323                      (($c0 << 3) | ($c1 >> 2)             ) & 0xff
 324                  );
 325                  $err |= ($c0 | $c1) >> 8;
 326              } else {
 327                  $dest .= \pack(
 328                      'C',
 329                      (($c0 << 3)                          ) & 0xff
 330                  );
 331                  $err |= ($c0) >> 8;
 332              }
 333          }
 334          if ($err !== 0) {
 335              throw new \RangeException(
 336                  'Base32::doDecode() only expects characters in the correct base32 alphabet'
 337              );
 338          }
 339          return $dest;
 340      }
 341  
 342      /**
 343       * Base32 Decoding
 344       *
 345       * @param string $src
 346       * @param bool $upper
 347       * @param bool $pad
 348       * @return string
 349       */
 350      protected static function doEncode($src, $upper = \false, $pad = \true)
 351      {
 352          // We do this to reduce code duplication:
 353          $method = $upper
 354              ? 'encode5BitsUpper'
 355              : 'encode5Bits';
 356  
 357          $dest = '';
 358          $srcLen = Binary::safeStrlen($src);
 359  
 360          // Main loop (no padding):
 361          for ($i = 0; $i + 5 <= $srcLen; $i += 5) {
 362              $chunk = \unpack('C*', Binary::safeSubstr($src, $i, 5));
 363              $b0 = $chunk[1];
 364              $b1 = $chunk[2];
 365              $b2 = $chunk[3];
 366              $b3 = $chunk[4];
 367              $b4 = $chunk[5];
 368              $dest .=
 369                  static::$method(              ($b0 >> 3)  & 31) .
 370                  static::$method((($b0 << 2) | ($b1 >> 6)) & 31) .
 371                  static::$method((($b1 >> 1)             ) & 31) .
 372                  static::$method((($b1 << 4) | ($b2 >> 4)) & 31) .
 373                  static::$method((($b2 << 1) | ($b3 >> 7)) & 31) .
 374                  static::$method((($b3 >> 2)             ) & 31) .
 375                  static::$method((($b3 << 3) | ($b4 >> 5)) & 31) .
 376                  static::$method(  $b4                     & 31);
 377          }
 378          // The last chunk, which may have padding:
 379          if ($i < $srcLen) {
 380              $chunk = \unpack('C*', Binary::safeSubstr($src, $i, $srcLen - $i));
 381              $b0 = $chunk[1];
 382              if ($i + 3 < $srcLen) {
 383                  $b1 = $chunk[2];
 384                  $b2 = $chunk[3];
 385                  $b3 = $chunk[4];
 386                  $dest .=
 387                      static::$method(              ($b0 >> 3)  & 31) .
 388                      static::$method((($b0 << 2) | ($b1 >> 6)) & 31) .
 389                      static::$method((($b1 >> 1)             ) & 31) .
 390                      static::$method((($b1 << 4) | ($b2 >> 4)) & 31) .
 391                      static::$method((($b2 << 1) | ($b3 >> 7)) & 31) .
 392                      static::$method((($b3 >> 2)             ) & 31) .
 393                      static::$method((($b3 << 3)             ) & 31);
 394                  if ($pad) {
 395                      $dest .= '=';
 396                  }
 397              } elseif ($i + 2 < $srcLen) {
 398                  $b1 = $chunk[2];
 399                  $b2 = $chunk[3];
 400                  $dest .=
 401                      static::$method(              ($b0 >> 3)  & 31) .
 402                      static::$method((($b0 << 2) | ($b1 >> 6)) & 31) .
 403                      static::$method((($b1 >> 1)             ) & 31) .
 404                      static::$method((($b1 << 4) | ($b2 >> 4)) & 31) .
 405                      static::$method((($b2 << 1)             ) & 31);
 406                  if ($pad) {
 407                      $dest .= '===';
 408                  }
 409              } elseif ($i + 1 < $srcLen) {
 410                  $b1 = $chunk[2];
 411                  $dest .=
 412                      static::$method(              ($b0 >> 3)  & 31) .
 413                      static::$method((($b0 << 2) | ($b1 >> 6)) & 31) .
 414                      static::$method((($b1 >> 1)             ) & 31) .
 415                      static::$method((($b1 << 4)             ) & 31);
 416                  if ($pad) {
 417                      $dest .= '====';
 418                  }
 419              } else {
 420                  $dest .=
 421                      static::$method(              ($b0 >> 3)  & 31) .
 422                      static::$method( ($b0 << 2)               & 31);
 423                  if ($pad) {
 424                      $dest .= '======';
 425                  }
 426              }
 427          }
 428          return $dest;
 429      }
 430  }