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 Base32
  34.  * [A-Z][2-7]
  35.  *
  36.  * @package ParagonIE\ConstantTime
  37.  */
  38. abstract class Base32 implements EncoderInterface
  39. {
  40.     /**
  41.      * Decode a Base32-encoded string into raw binary
  42.      *
  43.      * @param string $encodedString
  44.      * @param bool $strictPadding
  45.      * @return string
  46.      */
  47.     public static function decode(string $encodedString, bool $strictPadding = false): string
  48.     {
  49.         return static::doDecode($encodedString, false, $strictPadding);
  50.     }
  51.  
  52.     /**
  53.      * Decode an uppercase Base32-encoded string into raw binary
  54.      *
  55.      * @param string $src
  56.      * @param bool $strictPadding
  57.      * @return string
  58.      */
  59.     public static function decodeUpper(string $src, bool $strictPadding = false): string
  60.     {
  61.         return static::doDecode($src, true, $strictPadding);
  62.     }
  63.  
  64.     /**
  65.      * Encode into Base32 (RFC 4648)
  66.      *
  67.      * @param string $binString
  68.      * @return string
  69.      * @throws TypeError
  70.      */
  71.     public static function encode(string $binString): string
  72.     {
  73.         return static::doEncode($binString, false, true);
  74.     }
  75.     /**
  76.      * Encode into Base32 (RFC 4648)
  77.      *
  78.      * @param string $src
  79.      * @return string
  80.      * @throws TypeError
  81.      */
  82.     public static function encodeUnpadded(string $src): string
  83.     {
  84.         return static::doEncode($src, false, false);
  85.     }
  86.  
  87.     /**
  88.      * Encode into uppercase Base32 (RFC 4648)
  89.      *
  90.      * @param string $src
  91.      * @return string
  92.      * @throws TypeError
  93.      */
  94.     public static function encodeUpper(string $src): string
  95.     {
  96.         return static::doEncode($src, true, true);
  97.     }
  98.  
  99.     /**
  100.      * Encode into uppercase Base32 (RFC 4648)
  101.      *
  102.      * @param string $src
  103.      * @return string
  104.      * @throws TypeError
  105.      */
  106.     public static function encodeUpperUnpadded(string $src): string
  107.     {
  108.         return static::doEncode($src, true, false);
  109.     }
  110.  
  111.     /**
  112.      * Uses bitwise operators instead of table-lookups to turn 5-bit integers
  113.      * into 8-bit integers.
  114.      *
  115.      * @param int $src
  116.      * @return int
  117.      */
  118.     protected static function decode5Bits(int $src): int
  119.     {
  120.         $ret = -1;
  121.  
  122.         // if ($src > 96 && $src < 123) $ret += $src - 97 + 1; // -64
  123.         $ret += (((0x60 - $src) & ($src - 0x7b)) >> 8) & ($src - 96);
  124.  
  125.         // if ($src > 0x31 && $src < 0x38) $ret += $src - 24 + 1; // -23
  126.         $ret += (((0x31 - $src) & ($src - 0x38)) >> 8) & ($src - 23);
  127.  
  128.         return $ret;
  129.     }
  130.  
  131.     /**
  132.      * Uses bitwise operators instead of table-lookups to turn 5-bit integers
  133.      * into 8-bit integers.
  134.      *
  135.      * Uppercase variant.
  136.      *
  137.      * @param int $src
  138.      * @return int
  139.      */
  140.     protected static function decode5BitsUpper(int $src): int
  141.     {
  142.         $ret = -1;
  143.  
  144.         // if ($src > 64 && $src < 91) $ret += $src - 65 + 1; // -64
  145.         $ret += (((0x40 - $src) & ($src - 0x5b)) >> 8) & ($src - 64);
  146.  
  147.         // if ($src > 0x31 && $src < 0x38) $ret += $src - 24 + 1; // -23
  148.         $ret += (((0x31 - $src) & ($src - 0x38)) >> 8) & ($src - 23);
  149.  
  150.         return $ret;
  151.     }
  152.  
  153.     /**
  154.      * Uses bitwise operators instead of table-lookups to turn 8-bit integers
  155.      * into 5-bit integers.
  156.      *
  157.      * @param int $src
  158.      * @return string
  159.      */
  160.     protected static function encode5Bits(int $src): string
  161.     {
  162.         $diff = 0x61;
  163.  
  164.         // if ($src > 25) $ret -= 72;
  165.         $diff -= ((25 - $src) >> 8) & 73;
  166.  
  167.         return \pack('C', $src + $diff);
  168.     }
  169.  
  170.     /**
  171.      * Uses bitwise operators instead of table-lookups to turn 8-bit integers
  172.      * into 5-bit integers.
  173.      *
  174.      * Uppercase variant.
  175.      *
  176.      * @param int $src
  177.      * @return string
  178.      */
  179.     protected static function encode5BitsUpper(int $src): string
  180.     {
  181.         $diff = 0x41;
  182.  
  183.         // if ($src > 25) $ret -= 40;
  184.         $diff -= ((25 - $src) >> 8) & 41;
  185.  
  186.         return \pack('C', $src + $diff);
  187.     }
  188.  
  189.     /**
  190.      * @param string $encodedString
  191.      * @param bool $upper
  192.      * @return string
  193.      */
  194.     public static function decodeNoPadding(string $encodedString, bool $upper = false): string
  195.     {
  196.         $srcLen = Binary::safeStrlen($encodedString);
  197.         if ($srcLen === 0) {
  198.             return '';
  199.         }
  200.         if (($srcLen & 7) === 0) {
  201.             for ($j = 0; $j < 7 && $j < $srcLen; ++$j) {
  202.                 if ($encodedString[$srcLen - $j - 1] === '=') {
  203.                     throw new InvalidArgumentException(
  204.                         "decodeNoPadding() doesn't tolerate padding"
  205.                     );
  206.                 }
  207.             }
  208.         }
  209.         return static::doDecode(
  210.             $encodedString,
  211.             $upper,
  212.             true
  213.         );
  214.     }
  215.  
  216.     /**
  217.      * Base32 decoding
  218.      *
  219.      * @param string $src
  220.      * @param bool $upper
  221.      * @param bool $strictPadding
  222.      * @return string
  223.      *
  224.      * @throws TypeError
  225.      * @psalm-suppress RedundantCondition
  226.      */
  227.     protected static function doDecode(
  228.         string $src,
  229.         bool $upper = false,
  230.         bool $strictPadding = false
  231.     ): string {
  232.         // We do this to reduce code duplication:
  233.         $method = $upper
  234.             ? 'decode5BitsUpper'
  235.             : 'decode5Bits';
  236.  
  237.         // Remove padding
  238.         $srcLen = Binary::safeStrlen($src);
  239.         if ($srcLen === 0) {
  240.             return '';
  241.         }
  242.         if ($strictPadding) {
  243.             if (($srcLen & 7) === 0) {
  244.                 for ($j = 0; $j < 7; ++$j) {
  245.                     if ($src[$srcLen - 1] === '=') {
  246.                         $srcLen--;
  247.                     } else {
  248.                         break;
  249.                     }
  250.                 }
  251.             }
  252.             if (($srcLen & 7) === 1) {
  253.                 throw new RangeException(
  254.                     'Incorrect padding'
  255.                 );
  256.             }
  257.         } else {
  258.             $src = \rtrim($src, '=');
  259.             $srcLen = Binary::safeStrlen($src);
  260.         }
  261.  
  262.         $err = 0;
  263.         $dest = '';
  264.         // Main loop (no padding):
  265.         for ($i = 0; $i + 8 <= $srcLen; $i += 8) {
  266.             /** @var array<int, int> $chunk */
  267.             $chunk = \unpack('C*', Binary::safeSubstr($src, $i, 8));
  268.             /** @var int $c0 */
  269.             $c0 = static::$method($chunk[1]);
  270.             /** @var int $c1 */
  271.             $c1 = static::$method($chunk[2]);
  272.             /** @var int $c2 */
  273.             $c2 = static::$method($chunk[3]);
  274.             /** @var int $c3 */
  275.             $c3 = static::$method($chunk[4]);
  276.             /** @var int $c4 */
  277.             $c4 = static::$method($chunk[5]);
  278.             /** @var int $c5 */
  279.             $c5 = static::$method($chunk[6]);
  280.             /** @var int $c6 */
  281.             $c6 = static::$method($chunk[7]);
  282.             /** @var int $c7 */
  283.             $c7 = static::$method($chunk[8]);
  284.  
  285.             $dest .= \pack(
  286.                 'CCCCC',
  287.                 (($c0 << 3) | ($c1 >> 2)             ) & 0xff,
  288.                 (($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff,
  289.                 (($c3 << 4) | ($c4 >> 1)             ) & 0xff,
  290.                 (($c4 << 7) | ($c5 << 2) | ($c6 >> 3)) & 0xff,
  291.                 (($c6 << 5) | ($c7     )             ) & 0xff
  292.             );
  293.             $err |= ($c0 | $c1 | $c2 | $c3 | $c4 | $c5 | $c6 | $c7) >> 8;
  294.         }
  295.         // The last chunk, which may have padding:
  296.         if ($i < $srcLen) {
  297.             /** @var array<int, int> $chunk */
  298.             $chunk = \unpack('C*', Binary::safeSubstr($src, $i, $srcLen - $i));
  299.             /** @var int $c0 */
  300.             $c0 = static::$method($chunk[1]);
  301.  
  302.             if ($i + 6 < $srcLen) {
  303.                 /** @var int $c1 */
  304.                 $c1 = static::$method($chunk[2]);
  305.                 /** @var int $c2 */
  306.                 $c2 = static::$method($chunk[3]);
  307.                 /** @var int $c3 */
  308.                 $c3 = static::$method($chunk[4]);
  309.                 /** @var int $c4 */
  310.                 $c4 = static::$method($chunk[5]);
  311.                 /** @var int $c5 */
  312.                 $c5 = static::$method($chunk[6]);
  313.                 /** @var int $c6 */
  314.                 $c6 = static::$method($chunk[7]);
  315.  
  316.                 $dest .= \pack(
  317.                     'CCCC',
  318.                     (($c0 << 3) | ($c1 >> 2)             ) & 0xff,
  319.                     (($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff,
  320.                     (($c3 << 4) | ($c4 >> 1)             ) & 0xff,
  321.                     (($c4 << 7) | ($c5 << 2) | ($c6 >> 3)) & 0xff
  322.                 );
  323.                 $err |= ($c0 | $c1 | $c2 | $c3 | $c4 | $c5 | $c6) >> 8;
  324.                 if ($strictPadding) {
  325.                     $err |= ($c6 << 5) & 0xff;
  326.                 }
  327.             } elseif ($i + 5 < $srcLen) {
  328.                 /** @var int $c1 */
  329.                 $c1 = static::$method($chunk[2]);
  330.                 /** @var int $c2 */
  331.                 $c2 = static::$method($chunk[3]);
  332.                 /** @var int $c3 */
  333.                 $c3 = static::$method($chunk[4]);
  334.                 /** @var int $c4 */
  335.                 $c4 = static::$method($chunk[5]);
  336.                 /** @var int $c5 */
  337.                 $c5 = static::$method($chunk[6]);
  338.  
  339.                 $dest .= \pack(
  340.                     'CCCC',
  341.                     (($c0 << 3) | ($c1 >> 2)             ) & 0xff,
  342.                     (($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff,
  343.                     (($c3 << 4) | ($c4 >> 1)             ) & 0xff,
  344.                     (($c4 << 7) | ($c5 << 2)             ) & 0xff
  345.                 );
  346.                 $err |= ($c0 | $c1 | $c2 | $c3 | $c4 | $c5) >> 8;
  347.             } elseif ($i + 4 < $srcLen) {
  348.                 /** @var int $c1 */
  349.                 $c1 = static::$method($chunk[2]);
  350.                 /** @var int $c2 */
  351.                 $c2 = static::$method($chunk[3]);
  352.                 /** @var int $c3 */
  353.                 $c3 = static::$method($chunk[4]);
  354.                 /** @var int $c4 */
  355.                 $c4 = static::$method($chunk[5]);
  356.  
  357.                 $dest .= \pack(
  358.                     'CCC',
  359.                     (($c0 << 3) | ($c1 >> 2)             ) & 0xff,
  360.                     (($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff,
  361.                     (($c3 << 4) | ($c4 >> 1)             ) & 0xff
  362.                 );
  363.                 $err |= ($c0 | $c1 | $c2 | $c3 | $c4) >> 8;
  364.                 if ($strictPadding) {
  365.                     $err |= ($c4 << 7) & 0xff;
  366.                 }
  367.             } elseif ($i + 3 < $srcLen) {
  368.                 /** @var int $c1 */
  369.                 $c1 = static::$method($chunk[2]);
  370.                 /** @var int $c2 */
  371.                 $c2 = static::$method($chunk[3]);
  372.                 /** @var int $c3 */
  373.                 $c3 = static::$method($chunk[4]);
  374.  
  375.                 $dest .= \pack(
  376.                     'CC',
  377.                     (($c0 << 3) | ($c1 >> 2)             ) & 0xff,
  378.                     (($c1 << 6) | ($c2 << 1) | ($c3 >> 4)) & 0xff
  379.                 );
  380.                 $err |= ($c0 | $c1 | $c2 | $c3) >> 8;
  381.                 if ($strictPadding) {
  382.                     $err |= ($c3 << 4) & 0xff;
  383.                 }
  384.             } elseif ($i + 2 < $srcLen) {
  385.                 /** @var int $c1 */
  386.                 $c1 = static::$method($chunk[2]);
  387.                 /** @var int $c2 */
  388.                 $c2 = static::$method($chunk[3]);
  389.  
  390.                 $dest .= \pack(
  391.                     'CC',
  392.                     (($c0 << 3) | ($c1 >> 2)             ) & 0xff,
  393.                     (($c1 << 6) | ($c2 << 1)             ) & 0xff
  394.                 );
  395.                 $err |= ($c0 | $c1 | $c2) >> 8;
  396.                 if ($strictPadding) {
  397.                     $err |= ($c2 << 6) & 0xff;
  398.                 }
  399.             } elseif ($i + 1 < $srcLen) {
  400.                 /** @var int $c1 */
  401.                 $c1 = static::$method($chunk[2]);
  402.  
  403.                 $dest .= \pack(
  404.                     'C',
  405.                     (($c0 << 3) | ($c1 >> 2)             ) & 0xff
  406.                 );
  407.                 $err |= ($c0 | $c1) >> 8;
  408.                 if ($strictPadding) {
  409.                     $err |= ($c1 << 6) & 0xff;
  410.                 }
  411.             } else {
  412.                 $dest .= \pack(
  413.                     'C',
  414.                     (($c0 << 3)                          ) & 0xff
  415.                 );
  416.                 $err |= ($c0) >> 8;
  417.             }
  418.         }
  419.         $check = ($err === 0);
  420.         if (!$check) {
  421.             throw new RangeException(
  422.                 'Base32::doDecode() only expects characters in the correct base32 alphabet'
  423.             );
  424.         }
  425.         return $dest;
  426.     }
  427.  
  428.     /**
  429.      * Base32 Encoding
  430.      *
  431.      * @param string $src
  432.      * @param bool $upper
  433.      * @param bool $pad
  434.      * @return string
  435.      * @throws TypeError
  436.      */
  437.     protected static function doEncode(string $src, bool $upper = false, $pad = true): string
  438.     {
  439.         // We do this to reduce code duplication:
  440.         $method = $upper
  441.             ? 'encode5BitsUpper'
  442.             : 'encode5Bits';
  443.        
  444.         $dest = '';
  445.         $srcLen = Binary::safeStrlen($src);
  446.  
  447.         // Main loop (no padding):
  448.         for ($i = 0; $i + 5 <= $srcLen; $i += 5) {
  449.             /** @var array<int, int> $chunk */
  450.             $chunk = \unpack('C*', Binary::safeSubstr($src, $i, 5));
  451.             $b0 = $chunk[1];
  452.             $b1 = $chunk[2];
  453.             $b2 = $chunk[3];
  454.             $b3 = $chunk[4];
  455.             $b4 = $chunk[5];
  456.             $dest .=
  457.                 static::$method(              ($b0 >> 3)  & 31) .
  458.                 static::$method((($b0 << 2) | ($b1 >> 6)) & 31) .
  459.                 static::$method((($b1 >> 1)             ) & 31) .
  460.                 static::$method((($b1 << 4) | ($b2 >> 4)) & 31) .
  461.                 static::$method((($b2 << 1) | ($b3 >> 7)) & 31) .
  462.                 static::$method((($b3 >> 2)             ) & 31) .
  463.                 static::$method((($b3 << 3) | ($b4 >> 5)) & 31) .
  464.                 static::$method(  $b4                     & 31);
  465.         }
  466.         // The last chunk, which may have padding:
  467.         if ($i < $srcLen) {
  468.             /** @var array<int, int> $chunk */
  469.             $chunk = \unpack('C*', Binary::safeSubstr($src, $i, $srcLen - $i));
  470.             $b0 = $chunk[1];
  471.             if ($i + 3 < $srcLen) {
  472.                 $b1 = $chunk[2];
  473.                 $b2 = $chunk[3];
  474.                 $b3 = $chunk[4];
  475.                 $dest .=
  476.                     static::$method(              ($b0 >> 3)  & 31) .
  477.                     static::$method((($b0 << 2) | ($b1 >> 6)) & 31) .
  478.                     static::$method((($b1 >> 1)             ) & 31) .
  479.                     static::$method((($b1 << 4) | ($b2 >> 4)) & 31) .
  480.                     static::$method((($b2 << 1) | ($b3 >> 7)) & 31) .
  481.                     static::$method((($b3 >> 2)             ) & 31) .
  482.                     static::$method((($b3 << 3)             ) & 31);
  483.                 if ($pad) {
  484.                     $dest .= '=';
  485.                 }
  486.             } elseif ($i + 2 < $srcLen) {
  487.                 $b1 = $chunk[2];
  488.                 $b2 = $chunk[3];
  489.                 $dest .=
  490.                     static::$method(              ($b0 >> 3)  & 31) .
  491.                     static::$method((($b0 << 2) | ($b1 >> 6)) & 31) .
  492.                     static::$method((($b1 >> 1)             ) & 31) .
  493.                     static::$method((($b1 << 4) | ($b2 >> 4)) & 31) .
  494.                     static::$method((($b2 << 1)             ) & 31);
  495.                 if ($pad) {
  496.                     $dest .= '===';
  497.                 }
  498.             } elseif ($i + 1 < $srcLen) {
  499.                 $b1 = $chunk[2];
  500.                 $dest .=
  501.                     static::$method(              ($b0 >> 3)  & 31) .
  502.                     static::$method((($b0 << 2) | ($b1 >> 6)) & 31) .
  503.                     static::$method((($b1 >> 1)             ) & 31) .
  504.                     static::$method((($b1 << 4)             ) & 31);
  505.                 if ($pad) {
  506.                     $dest .= '====';
  507.                 }
  508.             } else {
  509.                 $dest .=
  510.                     static::$method(              ($b0 >> 3)  & 31) .
  511.                     static::$method( ($b0 << 2)               & 31);
  512.                 if ($pad) {
  513.                     $dest .= '======';
  514.                 }
  515.             }
  516.         }
  517.         return $dest;
  518.     }
  519. }
  520.