[ Index ] |
PHP Cross Reference of DokuWiki |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body