Base32.php 16 KB

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