Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
36 / 36
100.00% covered (success)
100.00%
7 / 7
CRAP
100.00% covered (success)
100.00%
1 / 1
GenericHash
100.00% covered (success)
100.00%
36 / 36
100.00% covered (success)
100.00%
7 / 7
12
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 validateKey
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 validateHashLength
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 signature
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 verify
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 makeHash
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 makeKey
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php declare(strict_types=1);
2/*
3 * This file is part of Aplus Framework Crypto Library.
4 *
5 * (c) Natan Felles <natanfelles@gmail.com>
6 *
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
9 */
10namespace Framework\Crypto;
11
12use LengthException;
13use RangeException;
14use SensitiveParameter;
15use SodiumException;
16
17/**
18 * Class GenericHash.
19 *
20 * @package crypto
21 */
22class GenericHash
23{
24    protected string $key;
25    protected int $hashLength = \SODIUM_CRYPTO_GENERICHASH_BYTES;
26
27    /**
28     * GenericHash constructor.
29     *
30     * @param string $key
31     * @param int $hashLength
32     *
33     * @see GenericHash::makeKey()
34     *
35     * @throws LengthException if key length is not between 16 and 64
36     * @throws RangeException if the hashLength value is not in the range 16 to 64
37     */
38    public function __construct(
39        #[SensitiveParameter] string $key,
40        int $hashLength = \SODIUM_CRYPTO_GENERICHASH_BYTES
41    ) {
42        $this->validateKey($key);
43        $this->validateHashLength($hashLength);
44        $this->key = $key;
45        $this->hashLength = $hashLength;
46    }
47
48    /**
49     * Validates a key.
50     *
51     * @param string $key
52     *
53     * @throws LengthException if key length is not between 16 and 64
54     */
55    protected function validateKey(#[SensitiveParameter] string $key) : void
56    {
57        $length = \mb_strlen($key, '8bit');
58        if ($length < \SODIUM_CRYPTO_GENERICHASH_KEYBYTES_MIN
59            || $length > \SODIUM_CRYPTO_GENERICHASH_KEYBYTES_MAX
60        ) {
61            throw new LengthException(
62                'GenericHash key must have a length between '
63                . \SODIUM_CRYPTO_GENERICHASH_KEYBYTES_MIN . ' and '
64                . \SODIUM_CRYPTO_GENERICHASH_KEYBYTES_MAX . ', '
65                . $length . ' given'
66            );
67        }
68    }
69
70    /**
71     * Validates a hash length.
72     *
73     * @param int $length
74     *
75     * @throws RangeException if the length value is not in the range 16 to 64
76     */
77    protected function validateHashLength(int $length) : void
78    {
79        if ($length < \SODIUM_CRYPTO_GENERICHASH_BYTES_MIN
80            || $length > \SODIUM_CRYPTO_GENERICHASH_BYTES_MAX
81        ) {
82            throw new RangeException(
83                'Hash length must be a value between ' . \SODIUM_CRYPTO_GENERICHASH_BYTES_MIN
84                . ' and ' . \SODIUM_CRYPTO_GENERICHASH_BYTES_MAX . ', '
85                . $length . ' given'
86            );
87        }
88    }
89
90    /**
91     * Gets a message signature.
92     *
93     * @param string $message
94     * @param int|null $hashLength A custom hash length or null to use the length set in
95     * the constructor
96     *
97     * @throws RangeException if the hashLength is set and is not in the range 16 to 64
98     * @throws SodiumException
99     *
100     * @return string
101     */
102    public function signature(
103        #[SensitiveParameter] string $message,
104        int $hashLength = null
105    ) : string {
106        return Utils::bin2base64(
107            $this->makeHash($message, $hashLength),
108            \SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING
109        );
110    }
111
112    /**
113     * Verifies if a message matches a signature.
114     *
115     * @param string $message
116     * @param string $signature
117     * @param int|null $hashLength A custom hash length or null to use the length set in
118     * the constructor
119     *
120     * @throws RangeException if the hashLength is set and is not in the range 16 to 64
121     * @throws SodiumException
122     *
123     * @return bool
124     */
125    public function verify(
126        #[SensitiveParameter] string $message,
127        #[SensitiveParameter] string $signature,
128        int $hashLength = null
129    ) : bool {
130        return \hash_equals(
131            $this->makeHash($message, $hashLength),
132            Utils::base642bin($signature, \SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING)
133        );
134    }
135
136    /**
137     * Makes a hash to a message.
138     *
139     * @param string $message
140     * @param int|null $length A custom length or null to use the length set in
141     * the constructor
142     *
143     * @throws RangeException if the length is set and is not in the range 16 to 64
144     * @throws SodiumException
145     *
146     * @return string
147     */
148    protected function makeHash(#[SensitiveParameter] string $message, int $length = null) : string
149    {
150        if ($length !== null) {
151            $this->validateHashLength($length);
152        }
153        return \sodium_crypto_generichash(
154            $message,
155            $this->key,
156            $length ?? $this->hashLength
157        );
158    }
159
160    /**
161     * Makes a key.
162     *
163     * @return string
164     */
165    public static function makeKey() : string
166    {
167        return \sodium_crypto_generichash_keygen();
168    }
169}