1927933f5SAndreas Gohr<?php 2927933f5SAndreas Gohrdeclare(strict_types=1); 3927933f5SAndreas Gohrnamespace ParagonIE\ConstantTime; 4927933f5SAndreas Gohr 5*8e88a29bSAndreas Gohruse Override; 6927933f5SAndreas Gohruse RangeException; 7*8e88a29bSAndreas Gohruse SensitiveParameter; 8*8e88a29bSAndreas Gohruse SodiumException; 9927933f5SAndreas Gohruse TypeError; 10*8e88a29bSAndreas Gohruse function extension_loaded; 11*8e88a29bSAndreas Gohruse function pack; 12*8e88a29bSAndreas Gohruse function sodium_bin2hex; 13*8e88a29bSAndreas Gohruse function sodium_hex2bin; 14*8e88a29bSAndreas Gohruse function strlen; 15*8e88a29bSAndreas Gohruse function unpack; 16927933f5SAndreas Gohr 17927933f5SAndreas Gohr/** 18*8e88a29bSAndreas Gohr * Copyright (c) 2016 - 2025 Paragon Initiative Enterprises. 19927933f5SAndreas Gohr * Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com) 20927933f5SAndreas Gohr * 21927933f5SAndreas Gohr * Permission is hereby granted, free of charge, to any person obtaining a copy 22927933f5SAndreas Gohr * of this software and associated documentation files (the "Software"), to deal 23927933f5SAndreas Gohr * in the Software without restriction, including without limitation the rights 24927933f5SAndreas Gohr * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 25927933f5SAndreas Gohr * copies of the Software, and to permit persons to whom the Software is 26927933f5SAndreas Gohr * furnished to do so, subject to the following conditions: 27927933f5SAndreas Gohr * 28927933f5SAndreas Gohr * The above copyright notice and this permission notice shall be included in all 29927933f5SAndreas Gohr * copies or substantial portions of the Software. 30927933f5SAndreas Gohr * 31927933f5SAndreas Gohr * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32927933f5SAndreas Gohr * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33927933f5SAndreas Gohr * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34927933f5SAndreas Gohr * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35927933f5SAndreas Gohr * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 36927933f5SAndreas Gohr * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 37927933f5SAndreas Gohr * SOFTWARE. 38927933f5SAndreas Gohr */ 39927933f5SAndreas Gohr 40927933f5SAndreas Gohr/** 41927933f5SAndreas Gohr * Class Hex 42927933f5SAndreas Gohr * @package ParagonIE\ConstantTime 43927933f5SAndreas Gohr */ 44927933f5SAndreas Gohrabstract class Hex implements EncoderInterface 45927933f5SAndreas Gohr{ 46927933f5SAndreas Gohr /** 47927933f5SAndreas Gohr * Convert a binary string into a hexadecimal string without cache-timing 48927933f5SAndreas Gohr * leaks 49927933f5SAndreas Gohr * 50927933f5SAndreas Gohr * @param string $binString (raw binary) 51927933f5SAndreas Gohr * @return string 52927933f5SAndreas Gohr * @throws TypeError 53927933f5SAndreas Gohr */ 54*8e88a29bSAndreas Gohr #[Override] 55850e6620SAndreas Gohr public static function encode( 56*8e88a29bSAndreas Gohr #[SensitiveParameter] 57850e6620SAndreas Gohr string $binString 58850e6620SAndreas Gohr ): string { 59*8e88a29bSAndreas Gohr if (extension_loaded('sodium')) { 60*8e88a29bSAndreas Gohr try { 61*8e88a29bSAndreas Gohr return sodium_bin2hex($binString); 62*8e88a29bSAndreas Gohr } catch (SodiumException $ex) { 63*8e88a29bSAndreas Gohr throw new RangeException($ex->getMessage(), $ex->getCode(), $ex); 64*8e88a29bSAndreas Gohr } 65*8e88a29bSAndreas Gohr } 66927933f5SAndreas Gohr $hex = ''; 67*8e88a29bSAndreas Gohr $len = strlen($binString); 68927933f5SAndreas Gohr for ($i = 0; $i < $len; ++$i) { 69927933f5SAndreas Gohr /** @var array<int, int> $chunk */ 70*8e88a29bSAndreas Gohr $chunk = unpack('C', $binString[$i]); 71927933f5SAndreas Gohr $c = $chunk[1] & 0xf; 72927933f5SAndreas Gohr $b = $chunk[1] >> 4; 73927933f5SAndreas Gohr 74*8e88a29bSAndreas Gohr $hex .= pack( 75927933f5SAndreas Gohr 'CC', 76927933f5SAndreas Gohr (87 + $b + ((($b - 10) >> 8) & ~38)), 77927933f5SAndreas Gohr (87 + $c + ((($c - 10) >> 8) & ~38)) 78927933f5SAndreas Gohr ); 79927933f5SAndreas Gohr } 80927933f5SAndreas Gohr return $hex; 81927933f5SAndreas Gohr } 82927933f5SAndreas Gohr 83927933f5SAndreas Gohr /** 84927933f5SAndreas Gohr * Convert a binary string into a hexadecimal string without cache-timing 85927933f5SAndreas Gohr * leaks, returning uppercase letters (as per RFC 4648) 86927933f5SAndreas Gohr * 87927933f5SAndreas Gohr * @param string $binString (raw binary) 88927933f5SAndreas Gohr * @return string 89927933f5SAndreas Gohr * @throws TypeError 90927933f5SAndreas Gohr */ 91850e6620SAndreas Gohr public static function encodeUpper( 92*8e88a29bSAndreas Gohr #[SensitiveParameter] 93850e6620SAndreas Gohr string $binString 94850e6620SAndreas Gohr ): string { 95927933f5SAndreas Gohr $hex = ''; 96*8e88a29bSAndreas Gohr $len = strlen($binString); 97927933f5SAndreas Gohr 98927933f5SAndreas Gohr for ($i = 0; $i < $len; ++$i) { 99927933f5SAndreas Gohr /** @var array<int, int> $chunk */ 100*8e88a29bSAndreas Gohr $chunk = unpack('C', $binString[$i]); 101927933f5SAndreas Gohr $c = $chunk[1] & 0xf; 102927933f5SAndreas Gohr $b = $chunk[1] >> 4; 103927933f5SAndreas Gohr 104*8e88a29bSAndreas Gohr $hex .= pack( 105927933f5SAndreas Gohr 'CC', 106927933f5SAndreas Gohr (55 + $b + ((($b - 10) >> 8) & ~6)), 107927933f5SAndreas Gohr (55 + $c + ((($c - 10) >> 8) & ~6)) 108927933f5SAndreas Gohr ); 109927933f5SAndreas Gohr } 110927933f5SAndreas Gohr return $hex; 111927933f5SAndreas Gohr } 112927933f5SAndreas Gohr 113927933f5SAndreas Gohr /** 114927933f5SAndreas Gohr * Convert a hexadecimal string into a binary string without cache-timing 115927933f5SAndreas Gohr * leaks 116927933f5SAndreas Gohr * 117927933f5SAndreas Gohr * @param string $encodedString 118927933f5SAndreas Gohr * @param bool $strictPadding 119927933f5SAndreas Gohr * @return string (raw binary) 120927933f5SAndreas Gohr * @throws RangeException 121927933f5SAndreas Gohr */ 122*8e88a29bSAndreas Gohr #[Override] 123927933f5SAndreas Gohr public static function decode( 124*8e88a29bSAndreas Gohr #[SensitiveParameter] 125927933f5SAndreas Gohr string $encodedString, 126927933f5SAndreas Gohr bool $strictPadding = false 127927933f5SAndreas Gohr ): string { 128*8e88a29bSAndreas Gohr if (extension_loaded('sodium') && $strictPadding) { 129*8e88a29bSAndreas Gohr try { 130*8e88a29bSAndreas Gohr return sodium_hex2bin($encodedString); 131*8e88a29bSAndreas Gohr } catch (SodiumException $ex) { 132*8e88a29bSAndreas Gohr throw new RangeException($ex->getMessage(), $ex->getCode(), $ex); 133*8e88a29bSAndreas Gohr } 134*8e88a29bSAndreas Gohr } 135927933f5SAndreas Gohr $hex_pos = 0; 136927933f5SAndreas Gohr $bin = ''; 137927933f5SAndreas Gohr $c_acc = 0; 138*8e88a29bSAndreas Gohr $hex_len = strlen($encodedString); 139927933f5SAndreas Gohr $state = 0; 140927933f5SAndreas Gohr if (($hex_len & 1) !== 0) { 141927933f5SAndreas Gohr if ($strictPadding) { 142927933f5SAndreas Gohr throw new RangeException( 143927933f5SAndreas Gohr 'Expected an even number of hexadecimal characters' 144927933f5SAndreas Gohr ); 145927933f5SAndreas Gohr } else { 146927933f5SAndreas Gohr $encodedString = '0' . $encodedString; 147927933f5SAndreas Gohr ++$hex_len; 148927933f5SAndreas Gohr } 149927933f5SAndreas Gohr } 150927933f5SAndreas Gohr 151927933f5SAndreas Gohr /** @var array<int, int> $chunk */ 152*8e88a29bSAndreas Gohr $chunk = unpack('C*', $encodedString); 153927933f5SAndreas Gohr while ($hex_pos < $hex_len) { 154927933f5SAndreas Gohr ++$hex_pos; 155927933f5SAndreas Gohr $c = $chunk[$hex_pos]; 156927933f5SAndreas Gohr $c_num = $c ^ 48; 157927933f5SAndreas Gohr $c_num0 = ($c_num - 10) >> 8; 158927933f5SAndreas Gohr $c_alpha = ($c & ~32) - 55; 159927933f5SAndreas Gohr $c_alpha0 = (($c_alpha - 10) ^ ($c_alpha - 16)) >> 8; 160927933f5SAndreas Gohr 161927933f5SAndreas Gohr if (($c_num0 | $c_alpha0) === 0) { 162927933f5SAndreas Gohr throw new RangeException( 163927933f5SAndreas Gohr 'Expected hexadecimal character' 164927933f5SAndreas Gohr ); 165927933f5SAndreas Gohr } 166927933f5SAndreas Gohr $c_val = ($c_num0 & $c_num) | ($c_alpha & $c_alpha0); 167927933f5SAndreas Gohr if ($state === 0) { 168927933f5SAndreas Gohr $c_acc = $c_val * 16; 169927933f5SAndreas Gohr } else { 170*8e88a29bSAndreas Gohr $bin .= pack('C', $c_acc | $c_val); 171927933f5SAndreas Gohr } 172927933f5SAndreas Gohr $state ^= 1; 173927933f5SAndreas Gohr } 174927933f5SAndreas Gohr return $bin; 175927933f5SAndreas Gohr } 176927933f5SAndreas Gohr} 177