xref: /dokuwiki/vendor/paragonie/constant_time_encoding/src/Hex.php (revision 8e88a29b81301f78509349ab1152bb09c229123e)
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