TokenGenerator   A
last analyzed

Complexity

Total Complexity 29

Size/Duplication

Total Lines 314
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 29
eloc 87
c 2
b 0
f 0
dl 0
loc 314
ccs 66
cts 66
cp 1
rs 10

12 Methods

Rating   Name   Duplication   Size   Complexity  
A getTokenString() 0 11 2
A getHashingSalt() 0 5 2
A getEncryptionInitializationVector() 0 5 2
A getAsymmetricKeyPair() 0 24 3
A generatePrivateKey() 0 16 2
A getPasswordString() 0 17 3
A generatePublicKey() 0 18 3
A getPasswordCharacter() 0 12 4
A getHashingKey() 0 5 2
A getEncryptionKey() 0 5 2
A validateAsymmetricAlgorithmType() 0 17 2
A applyLengthValidation() 0 16 2
1
<?php
2
3
/**
4
 * Utility class for cryptography key and token generation.
5
 */
6
7
namespace CryptoManana\Utilities;
8
9
use CryptoManana\Core\Abstractions\Containers\AbstractRandomnessInjectable as RandomnessContainer;
10
use CryptoManana\Core\Interfaces\Containers\AsymmetricKeyPairGenerationInterface as KeyPairGeneration;
11
use CryptoManana\Core\Interfaces\Containers\EncryptionKeyGenerationInterface as EncryptionKeyGeneration;
12
use CryptoManana\Core\Interfaces\Containers\HashingKeyGenerationInterface as HashingKeyGeneration;
13
use CryptoManana\Core\Interfaces\Containers\TokenGenerationInterface as TokenStringGeneration;
14
use CryptoManana\Core\Traits\CommonValidations\KeyPairSizeValidationTrait as KeyPairSizeValidations;
15
use CryptoManana\Core\StringBuilder as StringBuilder;
16
use CryptoManana\DataStructures\KeyPair as KeyPairStructure;
17
18
/**
19
 * Class TokenGenerator - Utility class for cryptography token generation.
20
 *
21
 * @package CryptoManana\Utilities
22
 *
23
 * @property \CryptoManana\Core\Abstractions\Randomness\AbstractGenerator $randomnessSource The randomness generator.
24
 *
25
 * @mixin KeyPairSizeValidations
26
 */
27
class TokenGenerator extends RandomnessContainer implements
28
    TokenStringGeneration,
29
    HashingKeyGeneration,
30
    EncryptionKeyGeneration,
31
    KeyPairGeneration
32
{
33
    /**
34
     * Asymmetric key pair size in bits validations.
35
     *
36
     * {@internal Reusable implementation of the common key pair size in bits validation. }}
37
     */
38
    use KeyPairSizeValidations;
39
40
    /**
41
     * Internal method for validation of positive output length.
42
     *
43
     * @param int $length The output length value for validation.
44
     *
45
     * @throws \Exception Validation errors.
46
     */
47 17
    protected function applyLengthValidation($length)
48
    {
49 17
        $length = filter_var(
50 17
            $length,
51 17
            FILTER_VALIDATE_INT,
52 17
            [
53 17
                "options" => [
54 17
                    "min_range" => 1,
55 17
                    "max_range" => PHP_INT_MAX,
56 17
                ],
57 17
            ]
58 17
        );
59
60 17
        if ($length === false) {
61 2
            throw new \LengthException(
62 2
                'The length of the desired output data must me at least 1 character long.'
63 2
            );
64
        }
65
    }
66
67
    /**
68
     * Internal method for asymmetric algorithm type validation.
69
     *
70
     * @param int $algorithmType The asymmetric algorithm type integer code.
71
     *
72
     * @throws \Exception Validation errors.
73
     */
74 21
    protected function validateAsymmetricAlgorithmType($algorithmType)
75
    {
76 21
        $algorithmType = filter_var(
77 21
            $algorithmType,
78 21
            FILTER_VALIDATE_INT,
79 21
            [
80 21
                "options" => [
81 21
                    "min_range" => self::RSA_KEY_PAIR_TYPE,
82 21
                    "max_range" => self::DSA_KEY_PAIR_TYPE,
83 21
                ],
84 21
            ]
85 21
        );
86
87 21
        if ($algorithmType === false) {
88 2
            throw new \InvalidArgumentException(
89 2
                'The asymmetric algorithm type must be a valid integer between ' .
90 2
                self::RSA_KEY_PAIR_TYPE . ' and ' . self::DSA_KEY_PAIR_TYPE . '.'
91 2
            );
92
        }
93
    }
94
95
    /**
96
     * Internal method for generating a fresh private key pair of the given size and type.
97
     *
98
     * @param int $keySize The private key size in bits.
99
     * @param string $algorithmType The asymmetric algorithm type.
100
     *
101
     * @return resource The private key resource.
102
     * @throws \Exception Validation or system errors.
103
     *
104
     * @codeCoverageIgnore
105
     */
106
    protected function generatePrivateKey($keySize, $algorithmType)
107
    {
108
        $privateKeyResource = openssl_pkey_new(
109
            [
110
                'private_key_bits' => $keySize, // Size of the key (>= 384)
111
                'private_key_type' => $algorithmType, // The algorithm type (RSA/DSA) type
112
            ]
113
        );
114
115
        if ($privateKeyResource === false) {
116
            throw new \RuntimeException(
117
                'Failed to generate a private key, probably because of a misconfigured OpenSSL library.'
118
            );
119
        }
120
121
        return $privateKeyResource;
122
    }
123
124
    /**
125
     * Internal method for generating a fresh public key pair of the given size by extracting it from the private key.
126
     *
127
     * @param int $keySize The private key size in bits.
128
     * @param resource $privateKeyResource The private key resource.
129
     *
130
     * @return string The extracted public key string.
131
     * @throws \Exception Validation or system errors.
132
     *
133
     * @note The private key resource is passed via reference from the main logical method for performance reasons.
134
     *
135
     * @codeCoverageIgnore
136
     */
137
    protected function generatePublicKey($keySize, &$privateKeyResource)
138
    {
139
        $privateKeyInformation = openssl_pkey_get_details($privateKeyResource);
140
141
        if ($privateKeyInformation === false) {
142
            throw new \RuntimeException(
143
                'Failed to generate/extract and export a public key, probably because of a misconfigured ' .
144
                'OpenSSL library or an invalid private key.'
145
            );
146
        } elseif ($privateKeyInformation['bits'] !== $keySize) {
147
            throw new \DomainException('The extracted public key is not of the correct size: `' . $keySize . '`.');
148
        }
149
150
        // Free the private key (resource cleanup)
151
        @openssl_free_key($privateKeyResource);
152
        $privateKeyResource = null;
153
154
        return (string)$privateKeyInformation['key']; // <- The public key
155
    }
156
157
    /**
158
     * Internal method for generation of characters used for secure password string building.
159
     *
160
     * @param int|mixed $case Generation case as integer.
161
     *
162
     * @return string Password character.
163
     * @throws \Exception Validation Errors.
164
     */
165 2
    protected function getPasswordCharacter($case)
166
    {
167
        switch ($case) {
168 2
            case 1:
169 2
                return $this->randomnessSource->getDigit(true);
170 2
            case 2:
171 2
                return $this->randomnessSource->getLetter(false);
172 2
            case 3:
173 2
                return StringBuilder::stringToUpper($this->randomnessSource->getLetter(false));
174
175
            default:
176 2
                return $this->randomnessSource->getString(1, ['!', '@', '#', '$', '%', '^']);
177
        }
178
    }
179
180
    /**
181
     * Generate a random token string in alphanumeric or hexadecimal format.
182
     *
183
     * Note: This method can generate HEX output if the `$useAlphaNumeric` parameter is set to `false`.
184
     *
185
     * @param int $length The desired output length (default => 32).
186
     * @param bool|int $useAlphaNumeric Flag for switching to alphanumerical (default => true).
187
     *
188
     * @return string Randomly generated alphanumeric/hexadecimal token string.
189
     * @throws \Exception Validation errors.
190
     */
191 7
    public function getTokenString($length = self::MODERATE_TOKEN_LENGTH, $useAlphaNumeric = true)
192
    {
193 7
        $this->applyLengthValidation($length);
194
195 5
        if ($useAlphaNumeric) {
196 5
            $token = $this->randomnessSource->getAlphaNumeric($length, true);
197
        } else {
198 2
            $token = $this->randomnessSource->getHex($length, true);
199
        }
200
201 5
        return StringBuilder::stringReverse($token);
202
    }
203
204
    /**
205
     * Generate a random password string.
206
     *
207
     * Note: This method can use more special symbols on generation if the `$stronger` parameter is set to `true`.
208
     *
209
     * @param int $length The desired output length (default => 12).
210
     * @param bool|int $stronger Flag for using all printable ASCII characters (default => true).
211
     *
212
     * @return string Randomly generated password string.
213
     * @throws \Exception Validation errors.
214
     */
215 2
    public function getPasswordString($length = self::MODERATE_PASSWORD_LENGTH, $stronger = true)
216
    {
217 2
        $this->applyLengthValidation($length);
218
219 2
        if ($stronger) {
220 2
            $password = $this->randomnessSource->getAscii($length);
221
        } else {
222 2
            $password = '';
223
224 2
            for ($i = 1; $i <= $length; $i++) {
225 2
                $case = $this->randomnessSource->getInt(1, 4);
226
227 2
                $password .= $this->getPasswordCharacter($case);
228
            }
229
        }
230
231 2
        return $password;
232
    }
233
234
    /**
235
     * Generate a random HMAC key for hashing purposes.
236
     *
237
     * Note: The output string can be in raw bytes of the `$printable` parameter is set to `true`.
238
     *
239
     * @param int $length The desired output length (default => 16).
240
     * @param bool|int $printable Flag for using only printable characters instead of bytes (default => true).
241
     *
242
     * @return string Randomly generated HMAC key.
243
     * @throws \Exception Validation errors.
244
     */
245 2
    public function getHashingKey($length = self::DIGESTION_KEY_128_BITS, $printable = true)
246
    {
247 2
        $this->applyLengthValidation($length);
248
249 2
        return ($printable) ? $this->randomnessSource->getAscii($length) : $this->randomnessSource->getBytes($length);
250
    }
251
252
    /**
253
     * Generate a random salt string for hashing purposes.
254
     *
255
     * Note: The output string can be in raw bytes of the `$printable` parameter is set to `true`.
256
     *
257
     * @param int $length The desired output length (default => 16).
258
     * @param bool|int $printable Flag for using only printable characters instead of bytes (default => true).
259
     *
260
     * @return string Randomly generated hashing salt.
261
     * @throws \Exception Validation errors.
262
     */
263 2
    public function getHashingSalt($length = self::DIGESTION_SALT_128_BITS, $printable = true)
264
    {
265 2
        $this->applyLengthValidation($length);
266
267 2
        return ($printable) ? $this->randomnessSource->getAscii($length) : $this->randomnessSource->getBytes($length);
268
    }
269
270
    /**
271
     * Generate a random encryption key for symmetrical cyphers.
272
     *
273
     * Note: The output string can be in raw bytes of the `$printable` parameter is set to `true`.
274
     *
275
     * @param int $length The desired output length (default => 16).
276
     * @param bool|int $printable Flag for using only printable characters instead of bytes (default => true).
277
     *
278
     * @return string Randomly generated encryption key.
279
     * @throws \Exception Validation errors.
280
     */
281 2
    public function getEncryptionKey($length = self::SECRET_KEY_128_BITS, $printable = true)
282
    {
283 2
        $this->applyLengthValidation($length);
284
285 2
        return ($printable) ? $this->randomnessSource->getAscii($length) : $this->randomnessSource->getBytes($length);
286
    }
287
288
    /**
289
     * Generate a random initialization vector (IV) for encryption purposes.
290
     *
291
     * Note: The output string can be in raw bytes of the `$printable` parameter is set to `true`.
292
     *
293
     * @param int $length The desired output length (default => 16).
294
     * @param bool|int $printable Flag for using only printable characters instead of bytes (default => true).
295
     *
296
     * @return string Randomly generated encryption initialization vector.
297
     * @throws \Exception Validation errors.
298
     */
299 2
    public function getEncryptionInitializationVector($length = self::IV_128_BITS, $printable = true)
300
    {
301 2
        $this->applyLengthValidation($length);
302
303 2
        return ($printable) ? $this->randomnessSource->getAscii($length) : $this->randomnessSource->getBytes($length);
304
    }
305
306
    /**
307
     * Generate a random key pair for asymmetrical cyphers.
308
     *
309
     * @param int $keySize The key size in bits.
310
     * @param int $algorithmType The asymmetric algorithm type integer code.
311
     *
312
     * @return KeyPairStructure Randomly generated asymmetric key pair (private and public keys) as an object.
313
     * @throws \Exception Validation errors.
314
     *
315
     * @codeCoverageIgnore
316
     */
317
    public function getAsymmetricKeyPair($keySize = self::KEY_PAIR_4096_BITS, $algorithmType = self::RSA_KEY_PAIR_TYPE)
318
    {
319
        $this->validateAsymmetricAlgorithmType($algorithmType);
320
        $this->validateKeyPairSize($keySize);
321
322
        $privateKeyResource = $this->generatePrivateKey((int)$keySize, (int)$algorithmType);
323
324
        $privateKeyString = '';
325
        $privateExport = openssl_pkey_export($privateKeyResource, $privateKeyString);
326
327
        if (empty($privateKeyString) || $privateExport === false) {
328
            throw new \RuntimeException(
329
                'Failed to export the private key to a string, probably because of a misconfigured OpenSSL library.'
330
            );
331
        }
332
333
        $publicKeyString = $this->generatePublicKey((int)$keySize, $privateKeyResource);
334
335
        $object = new KeyPairStructure();
336
337
        $object->private = base64_encode($privateKeyString);
338
        $object->public = base64_encode($publicKeyString);
339
340
        return $object;
341
    }
342
}
343