1927933f5SAndreas Gohr<?php 2927933f5SAndreas Gohrdeclare(strict_types=1); 3927933f5SAndreas Gohrnamespace ParagonIE\ConstantTime; 4927933f5SAndreas Gohr 5927933f5SAndreas Gohruse InvalidArgumentException; 6927933f5SAndreas Gohruse RangeException; 7927933f5SAndreas Gohruse TypeError; 8927933f5SAndreas Gohr 9927933f5SAndreas Gohr/** 10927933f5SAndreas Gohr * Copyright (c) 2016 - 2022 Paragon Initiative Enterprises. 11927933f5SAndreas Gohr * Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com) 12927933f5SAndreas Gohr * 13927933f5SAndreas Gohr * Permission is hereby granted, free of charge, to any person obtaining a copy 14927933f5SAndreas Gohr * of this software and associated documentation files (the "Software"), to deal 15927933f5SAndreas Gohr * in the Software without restriction, including without limitation the rights 16927933f5SAndreas Gohr * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17927933f5SAndreas Gohr * copies of the Software, and to permit persons to whom the Software is 18927933f5SAndreas Gohr * furnished to do so, subject to the following conditions: 19927933f5SAndreas Gohr * 20927933f5SAndreas Gohr * The above copyright notice and this permission notice shall be included in all 21927933f5SAndreas Gohr * copies or substantial portions of the Software. 22927933f5SAndreas Gohr * 23927933f5SAndreas Gohr * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24927933f5SAndreas Gohr * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25927933f5SAndreas Gohr * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26927933f5SAndreas Gohr * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27927933f5SAndreas Gohr * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28927933f5SAndreas Gohr * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29927933f5SAndreas Gohr * SOFTWARE. 30927933f5SAndreas Gohr */ 31927933f5SAndreas Gohr 32927933f5SAndreas Gohr/** 33927933f5SAndreas Gohr * Class Base64 34927933f5SAndreas Gohr * [A-Z][a-z][0-9]+/ 35927933f5SAndreas Gohr * 36927933f5SAndreas Gohr * @package ParagonIE\ConstantTime 37927933f5SAndreas Gohr */ 38927933f5SAndreas Gohrabstract class Base64 implements EncoderInterface 39927933f5SAndreas Gohr{ 40927933f5SAndreas Gohr /** 41927933f5SAndreas Gohr * Encode into Base64 42927933f5SAndreas Gohr * 43927933f5SAndreas Gohr * Base64 character set "[A-Z][a-z][0-9]+/" 44927933f5SAndreas Gohr * 45927933f5SAndreas Gohr * @param string $binString 46927933f5SAndreas Gohr * @return string 47927933f5SAndreas Gohr * 48927933f5SAndreas Gohr * @throws TypeError 49927933f5SAndreas Gohr */ 50*850e6620SAndreas Gohr public static function encode( 51*850e6620SAndreas Gohr #[\SensitiveParameter] 52*850e6620SAndreas Gohr string $binString 53*850e6620SAndreas Gohr ): string { 54927933f5SAndreas Gohr return static::doEncode($binString, true); 55927933f5SAndreas Gohr } 56927933f5SAndreas Gohr 57927933f5SAndreas Gohr /** 58927933f5SAndreas Gohr * Encode into Base64, no = padding 59927933f5SAndreas Gohr * 60927933f5SAndreas Gohr * Base64 character set "[A-Z][a-z][0-9]+/" 61927933f5SAndreas Gohr * 62927933f5SAndreas Gohr * @param string $src 63927933f5SAndreas Gohr * @return string 64927933f5SAndreas Gohr * 65927933f5SAndreas Gohr * @throws TypeError 66927933f5SAndreas Gohr */ 67*850e6620SAndreas Gohr public static function encodeUnpadded( 68*850e6620SAndreas Gohr #[\SensitiveParameter] 69*850e6620SAndreas Gohr string $src 70*850e6620SAndreas Gohr ): string { 71927933f5SAndreas Gohr return static::doEncode($src, false); 72927933f5SAndreas Gohr } 73927933f5SAndreas Gohr 74927933f5SAndreas Gohr /** 75927933f5SAndreas Gohr * @param string $src 76927933f5SAndreas Gohr * @param bool $pad Include = padding? 77927933f5SAndreas Gohr * @return string 78927933f5SAndreas Gohr * 79927933f5SAndreas Gohr * @throws TypeError 80927933f5SAndreas Gohr */ 81*850e6620SAndreas Gohr protected static function doEncode( 82*850e6620SAndreas Gohr #[\SensitiveParameter] 83*850e6620SAndreas Gohr string $src, 84*850e6620SAndreas Gohr bool $pad = true 85*850e6620SAndreas Gohr ): string { 86927933f5SAndreas Gohr $dest = ''; 87927933f5SAndreas Gohr $srcLen = Binary::safeStrlen($src); 88927933f5SAndreas Gohr // Main loop (no padding): 89927933f5SAndreas Gohr for ($i = 0; $i + 3 <= $srcLen; $i += 3) { 90927933f5SAndreas Gohr /** @var array<int, int> $chunk */ 91927933f5SAndreas Gohr $chunk = \unpack('C*', Binary::safeSubstr($src, $i, 3)); 92927933f5SAndreas Gohr $b0 = $chunk[1]; 93927933f5SAndreas Gohr $b1 = $chunk[2]; 94927933f5SAndreas Gohr $b2 = $chunk[3]; 95927933f5SAndreas Gohr 96927933f5SAndreas Gohr $dest .= 97927933f5SAndreas Gohr static::encode6Bits( $b0 >> 2 ) . 98927933f5SAndreas Gohr static::encode6Bits((($b0 << 4) | ($b1 >> 4)) & 63) . 99927933f5SAndreas Gohr static::encode6Bits((($b1 << 2) | ($b2 >> 6)) & 63) . 100927933f5SAndreas Gohr static::encode6Bits( $b2 & 63); 101927933f5SAndreas Gohr } 102927933f5SAndreas Gohr // The last chunk, which may have padding: 103927933f5SAndreas Gohr if ($i < $srcLen) { 104927933f5SAndreas Gohr /** @var array<int, int> $chunk */ 105927933f5SAndreas Gohr $chunk = \unpack('C*', Binary::safeSubstr($src, $i, $srcLen - $i)); 106927933f5SAndreas Gohr $b0 = $chunk[1]; 107927933f5SAndreas Gohr if ($i + 1 < $srcLen) { 108927933f5SAndreas Gohr $b1 = $chunk[2]; 109927933f5SAndreas Gohr $dest .= 110927933f5SAndreas Gohr static::encode6Bits($b0 >> 2) . 111927933f5SAndreas Gohr static::encode6Bits((($b0 << 4) | ($b1 >> 4)) & 63) . 112927933f5SAndreas Gohr static::encode6Bits(($b1 << 2) & 63); 113927933f5SAndreas Gohr if ($pad) { 114927933f5SAndreas Gohr $dest .= '='; 115927933f5SAndreas Gohr } 116927933f5SAndreas Gohr } else { 117927933f5SAndreas Gohr $dest .= 118927933f5SAndreas Gohr static::encode6Bits( $b0 >> 2) . 119927933f5SAndreas Gohr static::encode6Bits(($b0 << 4) & 63); 120927933f5SAndreas Gohr if ($pad) { 121927933f5SAndreas Gohr $dest .= '=='; 122927933f5SAndreas Gohr } 123927933f5SAndreas Gohr } 124927933f5SAndreas Gohr } 125927933f5SAndreas Gohr return $dest; 126927933f5SAndreas Gohr } 127927933f5SAndreas Gohr 128927933f5SAndreas Gohr /** 129927933f5SAndreas Gohr * decode from base64 into binary 130927933f5SAndreas Gohr * 131927933f5SAndreas Gohr * Base64 character set "./[A-Z][a-z][0-9]" 132927933f5SAndreas Gohr * 133927933f5SAndreas Gohr * @param string $encodedString 134927933f5SAndreas Gohr * @param bool $strictPadding 135927933f5SAndreas Gohr * @return string 136927933f5SAndreas Gohr * 137927933f5SAndreas Gohr * @throws RangeException 138927933f5SAndreas Gohr * @throws TypeError 139927933f5SAndreas Gohr */ 140*850e6620SAndreas Gohr public static function decode( 141*850e6620SAndreas Gohr #[\SensitiveParameter] 142*850e6620SAndreas Gohr string $encodedString, 143*850e6620SAndreas Gohr bool $strictPadding = false 144*850e6620SAndreas Gohr ): string { 145927933f5SAndreas Gohr // Remove padding 146927933f5SAndreas Gohr $srcLen = Binary::safeStrlen($encodedString); 147927933f5SAndreas Gohr if ($srcLen === 0) { 148927933f5SAndreas Gohr return ''; 149927933f5SAndreas Gohr } 150927933f5SAndreas Gohr 151927933f5SAndreas Gohr if ($strictPadding) { 152927933f5SAndreas Gohr if (($srcLen & 3) === 0) { 153927933f5SAndreas Gohr if ($encodedString[$srcLen - 1] === '=') { 154927933f5SAndreas Gohr $srcLen--; 155927933f5SAndreas Gohr if ($encodedString[$srcLen - 1] === '=') { 156927933f5SAndreas Gohr $srcLen--; 157927933f5SAndreas Gohr } 158927933f5SAndreas Gohr } 159927933f5SAndreas Gohr } 160927933f5SAndreas Gohr if (($srcLen & 3) === 1) { 161927933f5SAndreas Gohr throw new RangeException( 162927933f5SAndreas Gohr 'Incorrect padding' 163927933f5SAndreas Gohr ); 164927933f5SAndreas Gohr } 165927933f5SAndreas Gohr if ($encodedString[$srcLen - 1] === '=') { 166927933f5SAndreas Gohr throw new RangeException( 167927933f5SAndreas Gohr 'Incorrect padding' 168927933f5SAndreas Gohr ); 169927933f5SAndreas Gohr } 170927933f5SAndreas Gohr } else { 171927933f5SAndreas Gohr $encodedString = \rtrim($encodedString, '='); 172927933f5SAndreas Gohr $srcLen = Binary::safeStrlen($encodedString); 173927933f5SAndreas Gohr } 174927933f5SAndreas Gohr 175927933f5SAndreas Gohr $err = 0; 176927933f5SAndreas Gohr $dest = ''; 177927933f5SAndreas Gohr // Main loop (no padding): 178927933f5SAndreas Gohr for ($i = 0; $i + 4 <= $srcLen; $i += 4) { 179927933f5SAndreas Gohr /** @var array<int, int> $chunk */ 180927933f5SAndreas Gohr $chunk = \unpack('C*', Binary::safeSubstr($encodedString, $i, 4)); 181927933f5SAndreas Gohr $c0 = static::decode6Bits($chunk[1]); 182927933f5SAndreas Gohr $c1 = static::decode6Bits($chunk[2]); 183927933f5SAndreas Gohr $c2 = static::decode6Bits($chunk[3]); 184927933f5SAndreas Gohr $c3 = static::decode6Bits($chunk[4]); 185927933f5SAndreas Gohr 186927933f5SAndreas Gohr $dest .= \pack( 187927933f5SAndreas Gohr 'CCC', 188927933f5SAndreas Gohr ((($c0 << 2) | ($c1 >> 4)) & 0xff), 189927933f5SAndreas Gohr ((($c1 << 4) | ($c2 >> 2)) & 0xff), 190927933f5SAndreas Gohr ((($c2 << 6) | $c3 ) & 0xff) 191927933f5SAndreas Gohr ); 192927933f5SAndreas Gohr $err |= ($c0 | $c1 | $c2 | $c3) >> 8; 193927933f5SAndreas Gohr } 194927933f5SAndreas Gohr // The last chunk, which may have padding: 195927933f5SAndreas Gohr if ($i < $srcLen) { 196927933f5SAndreas Gohr /** @var array<int, int> $chunk */ 197927933f5SAndreas Gohr $chunk = \unpack('C*', Binary::safeSubstr($encodedString, $i, $srcLen - $i)); 198927933f5SAndreas Gohr $c0 = static::decode6Bits($chunk[1]); 199927933f5SAndreas Gohr 200927933f5SAndreas Gohr if ($i + 2 < $srcLen) { 201927933f5SAndreas Gohr $c1 = static::decode6Bits($chunk[2]); 202927933f5SAndreas Gohr $c2 = static::decode6Bits($chunk[3]); 203927933f5SAndreas Gohr $dest .= \pack( 204927933f5SAndreas Gohr 'CC', 205927933f5SAndreas Gohr ((($c0 << 2) | ($c1 >> 4)) & 0xff), 206927933f5SAndreas Gohr ((($c1 << 4) | ($c2 >> 2)) & 0xff) 207927933f5SAndreas Gohr ); 208927933f5SAndreas Gohr $err |= ($c0 | $c1 | $c2) >> 8; 209927933f5SAndreas Gohr if ($strictPadding) { 210927933f5SAndreas Gohr $err |= ($c2 << 6) & 0xff; 211927933f5SAndreas Gohr } 212927933f5SAndreas Gohr } elseif ($i + 1 < $srcLen) { 213927933f5SAndreas Gohr $c1 = static::decode6Bits($chunk[2]); 214927933f5SAndreas Gohr $dest .= \pack( 215927933f5SAndreas Gohr 'C', 216927933f5SAndreas Gohr ((($c0 << 2) | ($c1 >> 4)) & 0xff) 217927933f5SAndreas Gohr ); 218927933f5SAndreas Gohr $err |= ($c0 | $c1) >> 8; 219927933f5SAndreas Gohr if ($strictPadding) { 220927933f5SAndreas Gohr $err |= ($c1 << 4) & 0xff; 221927933f5SAndreas Gohr } 222927933f5SAndreas Gohr } elseif ($strictPadding) { 223927933f5SAndreas Gohr $err |= 1; 224927933f5SAndreas Gohr } 225927933f5SAndreas Gohr } 226927933f5SAndreas Gohr $check = ($err === 0); 227927933f5SAndreas Gohr if (!$check) { 228927933f5SAndreas Gohr throw new RangeException( 229927933f5SAndreas Gohr 'Base64::decode() only expects characters in the correct base64 alphabet' 230927933f5SAndreas Gohr ); 231927933f5SAndreas Gohr } 232927933f5SAndreas Gohr return $dest; 233927933f5SAndreas Gohr } 234927933f5SAndreas Gohr 235927933f5SAndreas Gohr /** 236927933f5SAndreas Gohr * @param string $encodedString 237927933f5SAndreas Gohr * @return string 238927933f5SAndreas Gohr */ 239*850e6620SAndreas Gohr public static function decodeNoPadding( 240*850e6620SAndreas Gohr #[\SensitiveParameter] 241*850e6620SAndreas Gohr string $encodedString 242*850e6620SAndreas Gohr ): string { 243927933f5SAndreas Gohr $srcLen = Binary::safeStrlen($encodedString); 244927933f5SAndreas Gohr if ($srcLen === 0) { 245927933f5SAndreas Gohr return ''; 246927933f5SAndreas Gohr } 247927933f5SAndreas Gohr if (($srcLen & 3) === 0) { 248*850e6620SAndreas Gohr // If $strLen is not zero, and it is divisible by 4, then it's at least 4. 249*850e6620SAndreas Gohr if ($encodedString[$srcLen - 1] === '=' || $encodedString[$srcLen - 2] === '=') { 250927933f5SAndreas Gohr throw new InvalidArgumentException( 251927933f5SAndreas Gohr "decodeNoPadding() doesn't tolerate padding" 252927933f5SAndreas Gohr ); 253927933f5SAndreas Gohr } 254927933f5SAndreas Gohr } 255927933f5SAndreas Gohr return static::decode( 256927933f5SAndreas Gohr $encodedString, 257927933f5SAndreas Gohr true 258927933f5SAndreas Gohr ); 259927933f5SAndreas Gohr } 260927933f5SAndreas Gohr 261927933f5SAndreas Gohr /** 262927933f5SAndreas Gohr * Uses bitwise operators instead of table-lookups to turn 6-bit integers 263927933f5SAndreas Gohr * into 8-bit integers. 264927933f5SAndreas Gohr * 265927933f5SAndreas Gohr * Base64 character set: 266927933f5SAndreas Gohr * [A-Z] [a-z] [0-9] + / 267927933f5SAndreas Gohr * 0x41-0x5a, 0x61-0x7a, 0x30-0x39, 0x2b, 0x2f 268927933f5SAndreas Gohr * 269927933f5SAndreas Gohr * @param int $src 270927933f5SAndreas Gohr * @return int 271927933f5SAndreas Gohr */ 272927933f5SAndreas Gohr protected static function decode6Bits(int $src): int 273927933f5SAndreas Gohr { 274927933f5SAndreas Gohr $ret = -1; 275927933f5SAndreas Gohr 276927933f5SAndreas Gohr // if ($src > 0x40 && $src < 0x5b) $ret += $src - 0x41 + 1; // -64 277927933f5SAndreas Gohr $ret += (((0x40 - $src) & ($src - 0x5b)) >> 8) & ($src - 64); 278927933f5SAndreas Gohr 279927933f5SAndreas Gohr // if ($src > 0x60 && $src < 0x7b) $ret += $src - 0x61 + 26 + 1; // -70 280927933f5SAndreas Gohr $ret += (((0x60 - $src) & ($src - 0x7b)) >> 8) & ($src - 70); 281927933f5SAndreas Gohr 282927933f5SAndreas Gohr // if ($src > 0x2f && $src < 0x3a) $ret += $src - 0x30 + 52 + 1; // 5 283927933f5SAndreas Gohr $ret += (((0x2f - $src) & ($src - 0x3a)) >> 8) & ($src + 5); 284927933f5SAndreas Gohr 285927933f5SAndreas Gohr // if ($src == 0x2b) $ret += 62 + 1; 286927933f5SAndreas Gohr $ret += (((0x2a - $src) & ($src - 0x2c)) >> 8) & 63; 287927933f5SAndreas Gohr 288927933f5SAndreas Gohr // if ($src == 0x2f) ret += 63 + 1; 289927933f5SAndreas Gohr $ret += (((0x2e - $src) & ($src - 0x30)) >> 8) & 64; 290927933f5SAndreas Gohr 291927933f5SAndreas Gohr return $ret; 292927933f5SAndreas Gohr } 293927933f5SAndreas Gohr 294927933f5SAndreas Gohr /** 295927933f5SAndreas Gohr * Uses bitwise operators instead of table-lookups to turn 8-bit integers 296927933f5SAndreas Gohr * into 6-bit integers. 297927933f5SAndreas Gohr * 298927933f5SAndreas Gohr * @param int $src 299927933f5SAndreas Gohr * @return string 300927933f5SAndreas Gohr */ 301927933f5SAndreas Gohr protected static function encode6Bits(int $src): string 302927933f5SAndreas Gohr { 303927933f5SAndreas Gohr $diff = 0x41; 304927933f5SAndreas Gohr 305927933f5SAndreas Gohr // if ($src > 25) $diff += 0x61 - 0x41 - 26; // 6 306927933f5SAndreas Gohr $diff += ((25 - $src) >> 8) & 6; 307927933f5SAndreas Gohr 308927933f5SAndreas Gohr // if ($src > 51) $diff += 0x30 - 0x61 - 26; // -75 309927933f5SAndreas Gohr $diff -= ((51 - $src) >> 8) & 75; 310927933f5SAndreas Gohr 311927933f5SAndreas Gohr // if ($src > 61) $diff += 0x2b - 0x30 - 10; // -15 312927933f5SAndreas Gohr $diff -= ((61 - $src) >> 8) & 15; 313927933f5SAndreas Gohr 314927933f5SAndreas Gohr // if ($src > 62) $diff += 0x2f - 0x2b - 1; // 3 315927933f5SAndreas Gohr $diff += ((62 - $src) >> 8) & 3; 316927933f5SAndreas Gohr 317927933f5SAndreas Gohr return \pack('C', $src + $diff); 318927933f5SAndreas Gohr } 319927933f5SAndreas Gohr} 320