Subversion Repositories oidplus

Rev

Rev 827 | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. <?php
  2. declare(strict_types=1);
  3. namespace ParagonIE\ConstantTime;
  4.  
  5. use InvalidArgumentException;
  6. use RangeException;
  7. use TypeError;
  8.  
  9. /**
  10.  *  Copyright (c) 2016 - 2022 Paragon Initiative Enterprises.
  11.  *  Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com)
  12.  *
  13.  *  Permission is hereby granted, free of charge, to any person obtaining a copy
  14.  *  of this software and associated documentation files (the "Software"), to deal
  15.  *  in the Software without restriction, including without limitation the rights
  16.  *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  17.  *  copies of the Software, and to permit persons to whom the Software is
  18.  *  furnished to do so, subject to the following conditions:
  19.  *
  20.  *  The above copyright notice and this permission notice shall be included in all
  21.  *  copies or substantial portions of the Software.
  22.  *
  23.  *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  24.  *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  25.  *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  26.  *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  27.  *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  28.  *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  29.  *  SOFTWARE.
  30.  */
  31.  
  32. /**
  33.  * Class Base64
  34.  * [A-Z][a-z][0-9]+/
  35.  *
  36.  * @package ParagonIE\ConstantTime
  37.  */
  38. abstract class Base64 implements EncoderInterface
  39. {
  40.     /**
  41.      * Encode into Base64
  42.      *
  43.      * Base64 character set "[A-Z][a-z][0-9]+/"
  44.      *
  45.      * @param string $binString
  46.      * @return string
  47.      *
  48.      * @throws TypeError
  49.      */
  50.     public static function encode(string $binString): string
  51.     {
  52.         return static::doEncode($binString, true);
  53.     }
  54.  
  55.     /**
  56.      * Encode into Base64, no = padding
  57.      *
  58.      * Base64 character set "[A-Z][a-z][0-9]+/"
  59.      *
  60.      * @param string $src
  61.      * @return string
  62.      *
  63.      * @throws TypeError
  64.      */
  65.     public static function encodeUnpadded(string $src): string
  66.     {
  67.         return static::doEncode($src, false);
  68.     }
  69.  
  70.     /**
  71.      * @param string $src
  72.      * @param bool $pad   Include = padding?
  73.      * @return string
  74.      *
  75.      * @throws TypeError
  76.      */
  77.     protected static function doEncode(string $src, bool $pad = true): string
  78.     {
  79.         $dest = '';
  80.         $srcLen = Binary::safeStrlen($src);
  81.         // Main loop (no padding):
  82.         for ($i = 0; $i + 3 <= $srcLen; $i += 3) {
  83.             /** @var array<int, int> $chunk */
  84.             $chunk = \unpack('C*', Binary::safeSubstr($src, $i, 3));
  85.             $b0 = $chunk[1];
  86.             $b1 = $chunk[2];
  87.             $b2 = $chunk[3];
  88.  
  89.             $dest .=
  90.                 static::encode6Bits(               $b0 >> 2       ) .
  91.                 static::encode6Bits((($b0 << 4) | ($b1 >> 4)) & 63) .
  92.                 static::encode6Bits((($b1 << 2) | ($b2 >> 6)) & 63) .
  93.                 static::encode6Bits(  $b2                     & 63);
  94.         }
  95.         // The last chunk, which may have padding:
  96.         if ($i < $srcLen) {
  97.             /** @var array<int, int> $chunk */
  98.             $chunk = \unpack('C*', Binary::safeSubstr($src, $i, $srcLen - $i));
  99.             $b0 = $chunk[1];
  100.             if ($i + 1 < $srcLen) {
  101.                 $b1 = $chunk[2];
  102.                 $dest .=
  103.                     static::encode6Bits($b0 >> 2) .
  104.                     static::encode6Bits((($b0 << 4) | ($b1 >> 4)) & 63) .
  105.                     static::encode6Bits(($b1 << 2) & 63);
  106.                 if ($pad) {
  107.                     $dest .= '=';
  108.                 }
  109.             } else {
  110.                 $dest .=
  111.                     static::encode6Bits( $b0 >> 2) .
  112.                     static::encode6Bits(($b0 << 4) & 63);
  113.                 if ($pad) {
  114.                     $dest .= '==';
  115.                 }
  116.             }
  117.         }
  118.         return $dest;
  119.     }
  120.  
  121.     /**
  122.      * decode from base64 into binary
  123.      *
  124.      * Base64 character set "./[A-Z][a-z][0-9]"
  125.      *
  126.      * @param string $encodedString
  127.      * @param bool $strictPadding
  128.      * @return string
  129.      *
  130.      * @throws RangeException
  131.      * @throws TypeError
  132.      * @psalm-suppress RedundantCondition
  133.      */
  134.     public static function decode(string $encodedString, bool $strictPadding = false): string
  135.     {
  136.         // Remove padding
  137.         $srcLen = Binary::safeStrlen($encodedString);
  138.         if ($srcLen === 0) {
  139.             return '';
  140.         }
  141.  
  142.         if ($strictPadding) {
  143.             if (($srcLen & 3) === 0) {
  144.                 if ($encodedString[$srcLen - 1] === '=') {
  145.                     $srcLen--;
  146.                     if ($encodedString[$srcLen - 1] === '=') {
  147.                         $srcLen--;
  148.                     }
  149.                 }
  150.             }
  151.             if (($srcLen & 3) === 1) {
  152.                 throw new RangeException(
  153.                     'Incorrect padding'
  154.                 );
  155.             }
  156.             if ($encodedString[$srcLen - 1] === '=') {
  157.                 throw new RangeException(
  158.                     'Incorrect padding'
  159.                 );
  160.             }
  161.         } else {
  162.             $encodedString = \rtrim($encodedString, '=');
  163.             $srcLen = Binary::safeStrlen($encodedString);
  164.         }
  165.  
  166.         $err = 0;
  167.         $dest = '';
  168.         // Main loop (no padding):
  169.         for ($i = 0; $i + 4 <= $srcLen; $i += 4) {
  170.             /** @var array<int, int> $chunk */
  171.             $chunk = \unpack('C*', Binary::safeSubstr($encodedString, $i, 4));
  172.             $c0 = static::decode6Bits($chunk[1]);
  173.             $c1 = static::decode6Bits($chunk[2]);
  174.             $c2 = static::decode6Bits($chunk[3]);
  175.             $c3 = static::decode6Bits($chunk[4]);
  176.  
  177.             $dest .= \pack(
  178.                 'CCC',
  179.                 ((($c0 << 2) | ($c1 >> 4)) & 0xff),
  180.                 ((($c1 << 4) | ($c2 >> 2)) & 0xff),
  181.                 ((($c2 << 6) |  $c3      ) & 0xff)
  182.             );
  183.             $err |= ($c0 | $c1 | $c2 | $c3) >> 8;
  184.         }
  185.         // The last chunk, which may have padding:
  186.         if ($i < $srcLen) {
  187.             /** @var array<int, int> $chunk */
  188.             $chunk = \unpack('C*', Binary::safeSubstr($encodedString, $i, $srcLen - $i));
  189.             $c0 = static::decode6Bits($chunk[1]);
  190.  
  191.             if ($i + 2 < $srcLen) {
  192.                 $c1 = static::decode6Bits($chunk[2]);
  193.                 $c2 = static::decode6Bits($chunk[3]);
  194.                 $dest .= \pack(
  195.                     'CC',
  196.                     ((($c0 << 2) | ($c1 >> 4)) & 0xff),
  197.                     ((($c1 << 4) | ($c2 >> 2)) & 0xff)
  198.                 );
  199.                 $err |= ($c0 | $c1 | $c2) >> 8;
  200.                 if ($strictPadding) {
  201.                     $err |= ($c2 << 6) & 0xff;
  202.                 }
  203.             } elseif ($i + 1 < $srcLen) {
  204.                 $c1 = static::decode6Bits($chunk[2]);
  205.                 $dest .= \pack(
  206.                     'C',
  207.                     ((($c0 << 2) | ($c1 >> 4)) & 0xff)
  208.                 );
  209.                 $err |= ($c0 | $c1) >> 8;
  210.                 if ($strictPadding) {
  211.                     $err |= ($c1 << 4) & 0xff;
  212.                 }
  213.             } elseif ($strictPadding) {
  214.                 $err |= 1;
  215.             }
  216.         }
  217.         $check = ($err === 0);
  218.         if (!$check) {
  219.             throw new RangeException(
  220.                 'Base64::decode() only expects characters in the correct base64 alphabet'
  221.             );
  222.         }
  223.         return $dest;
  224.     }
  225.  
  226.     /**
  227.      * @param string $encodedString
  228.      * @return string
  229.      */
  230.     public static function decodeNoPadding(string $encodedString): string
  231.     {
  232.         $srcLen = Binary::safeStrlen($encodedString);
  233.         if ($srcLen === 0) {
  234.             return '';
  235.         }
  236.         if (($srcLen & 3) === 0) {
  237.             if ($encodedString[$srcLen - 1] === '=') {
  238.                 throw new InvalidArgumentException(
  239.                     "decodeNoPadding() doesn't tolerate padding"
  240.                 );
  241.             }
  242.             if (($srcLen & 3) > 1) {
  243.                 if ($encodedString[$srcLen - 2] === '=') {
  244.                     throw new InvalidArgumentException(
  245.                         "decodeNoPadding() doesn't tolerate padding"
  246.                     );
  247.                 }
  248.             }
  249.         }
  250.         return static::decode(
  251.             $encodedString,
  252.             true
  253.         );
  254.     }
  255.  
  256.     /**
  257.      * Uses bitwise operators instead of table-lookups to turn 6-bit integers
  258.      * into 8-bit integers.
  259.      *
  260.      * Base64 character set:
  261.      * [A-Z]      [a-z]      [0-9]      +     /
  262.      * 0x41-0x5a, 0x61-0x7a, 0x30-0x39, 0x2b, 0x2f
  263.      *
  264.      * @param int $src
  265.      * @return int
  266.      */
  267.     protected static function decode6Bits(int $src): int
  268.     {
  269.         $ret = -1;
  270.  
  271.         // if ($src > 0x40 && $src < 0x5b) $ret += $src - 0x41 + 1; // -64
  272.         $ret += (((0x40 - $src) & ($src - 0x5b)) >> 8) & ($src - 64);
  273.  
  274.         // if ($src > 0x60 && $src < 0x7b) $ret += $src - 0x61 + 26 + 1; // -70
  275.         $ret += (((0x60 - $src) & ($src - 0x7b)) >> 8) & ($src - 70);
  276.  
  277.         // if ($src > 0x2f && $src < 0x3a) $ret += $src - 0x30 + 52 + 1; // 5
  278.         $ret += (((0x2f - $src) & ($src - 0x3a)) >> 8) & ($src + 5);
  279.  
  280.         // if ($src == 0x2b) $ret += 62 + 1;
  281.         $ret += (((0x2a - $src) & ($src - 0x2c)) >> 8) & 63;
  282.  
  283.         // if ($src == 0x2f) ret += 63 + 1;
  284.         $ret += (((0x2e - $src) & ($src - 0x30)) >> 8) & 64;
  285.  
  286.         return $ret;
  287.     }
  288.  
  289.     /**
  290.      * Uses bitwise operators instead of table-lookups to turn 8-bit integers
  291.      * into 6-bit integers.
  292.      *
  293.      * @param int $src
  294.      * @return string
  295.      */
  296.     protected static function encode6Bits(int $src): string
  297.     {
  298.         $diff = 0x41;
  299.  
  300.         // if ($src > 25) $diff += 0x61 - 0x41 - 26; // 6
  301.         $diff += ((25 - $src) >> 8) & 6;
  302.  
  303.         // if ($src > 51) $diff += 0x30 - 0x61 - 26; // -75
  304.         $diff -= ((51 - $src) >> 8) & 75;
  305.  
  306.         // if ($src > 61) $diff += 0x2b - 0x30 - 10; // -15
  307.         $diff -= ((61 - $src) >> 8) & 15;
  308.  
  309.         // if ($src > 62) $diff += 0x2f - 0x2b - 1; // 3
  310.         $diff += ((62 - $src) >> 8) & 3;
  311.  
  312.         return \pack('C', $src + $diff);
  313.     }
  314. }
  315.