Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
92.73% covered (success)
92.73%
51 / 55
78.57% covered (warning)
78.57%
11 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
Cryptography
92.73% covered (success)
92.73%
51 / 55
78.57% covered (warning)
78.57%
11 / 14
27.28
0.00% covered (danger)
0.00%
0 / 1
 generateRandomInteger
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 generateRandomBinary
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
3.04
 generateRandomString
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
4
 encrypt
81.82% covered (warning)
81.82%
9 / 11
0.00% covered (danger)
0.00%
0 / 1
4.10
 decrypt
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 hashPassword
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 verifyPassword
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 needsRehashPassword
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPasswordInformation
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPasswordAlgorithms
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getHashAlgorithms
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 generateHashCore
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
3.04
 generateHashString
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 generateHashBinary
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace PeServer\Core;
6
7use Exception;
8use Throwable;
9use PeServer\Core\Binary;
10use PeServer\Core\Collection\Arr;
11use PeServer\Core\Errors\ErrorHandler;
12use PeServer\Core\Text;
13use PeServer\Core\Throws\ArgumentException;
14use PeServer\Core\Throws\CryptoException;
15use PeServer\Core\Throws\Throws;
16
17/**
18 * 暗号化周り
19 */
20abstract class Cryptography
21{
22    #region define
23
24    private const OPTION = 0;
25    /** 文字列への暗号化時のセパレータ */
26    public const SEPARATOR = '@';
27    /** 文字列乱数 標準文字列 */
28    public const DEFAULT_RANDOM_STRING = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
29    /** 文字列乱数 ファイルとして扱える文字列 */
30    public const FILE_RANDOM_STRING = '0123456789abcdefghijklmnopqrstuvwxyz';
31
32    #endregion
33
34    #region function
35
36    /**
37     * 乱数取得。
38     *
39     * `random_int` ラッパー。
40     *
41     * @param int $min 最小値。
42     * @param int $max 最大値。
43     * @return int 乱数。
44     * @throws CryptoException 失敗
45     * @see https://www.php.net/manual/function.random-int.php
46     */
47    public static function generateRandomInteger(int $min, int $max): int
48    {
49        try {
50            /** @disregard P1010 */
51            return random_int($min, $max);
52        } catch (Throwable $ex) {
53            Throws::reThrow(CryptoException::class, $ex);
54        }
55    }
56
57    /**
58     * ランダムバイナリデータを生成。
59     *
60     * `openssl_random_pseudo_bytes` ラッパー。
61     *
62     * @param int $length バイト数。
63     * @phpstan-param positive-int $length
64     * @return Binary バイナリデータ。
65     * @throws ArgumentException 失敗
66     * @throws CryptoException 失敗
67     * @see https://www.php.net/manual/function.openssl-random-pseudo-bytes.php
68     */
69    public static function generateRandomBinary(int $length): Binary
70    {
71        if ($length < 1) { //@phpstan-ignore-line [DOCTYPE]
72            throw new ArgumentException('$length: ' . $length);
73        }
74
75        $result = openssl_random_pseudo_bytes($length);
76        if ($result === false) { //@phpstan-ignore-line [PHPVERSION]
77            throw new CryptoException();
78        }
79
80        return new Binary($result);
81    }
82
83    /**
84     * ランダム文字列を生成。
85     *
86     * @param int $length 文字列長。
87     * @phpstan-param positive-int $length
88     * @param non-empty-string $characters ランダム文字の元になる文字列。
89     * @return string 文字列。
90     * @throws CryptoException 失敗
91     */
92    public static function generateRandomString(int $length, string $characters = self::DEFAULT_RANDOM_STRING): string
93    {
94        if ($length < 1) { //@phpstan-ignore-line [DOCTYPE]
95            throw new ArgumentException('$length: ' . $length);
96        }
97        if (Text::isNullOrEmpty($characters)) { //@phpstan-ignore-line [DOCTYPE]
98            throw new ArgumentException('$characters: empty');
99        }
100
101        $charactersArray = Text::toCharacters($characters);
102
103        $min = 0;
104        $max = Arr::getCount($charactersArray) - 1;
105
106        $result = '';
107
108        for ($i = 0; $i < $length; $i++) {
109            $index = self::generateRandomInteger($min, $max);
110            $result .= $charactersArray[$index];
111        }
112
113        return $result;
114    }
115
116    /**
117     * 文字列を暗号化。
118     *
119     * @param non-empty-string $algorithm 暗号化方法。
120     * @param string $rawValue 生文字列。
121     * @param string $password パスワード。
122     * @return string 暗号化された文字列。 アルゴリズム@IV@暗号化データ となる。
123     * @throws CryptoException 失敗
124     */
125    public static function encrypt(string $algorithm, string $rawValue, string $password): string
126    {
127        $result = ErrorHandler::trap(fn() => openssl_cipher_iv_length($algorithm));
128        if ($result->isFailureOrFalse()) {
129            throw new CryptoException($algorithm);
130        }
131
132        $ivLength = $result->value;
133        if ($ivLength < 1) {
134            throw new CryptoException('$ivLength: ' . $ivLength);
135        }
136
137        $iv = self::generateRandomBinary($ivLength);
138
139        $result = ErrorHandler::trap(fn() => openssl_encrypt($rawValue, $algorithm, $password, self::OPTION, $iv->raw));
140        if ($result->isFailureOrFalse()) {
141            throw new CryptoException($algorithm);
142        }
143
144        return $algorithm . self::SEPARATOR . $iv->toBase64() . self::SEPARATOR . $result->value;
145    }
146
147    /**
148     * Cryptography::encrypt で暗号化されたデータの復元。
149     *
150     * @param string $encValue 暗号化データ。
151     * @param string $password パスワード。
152     * @return string 生文字列。
153     * @throws CryptoException 失敗
154     */
155    public static function decrypt(string $encValue, string $password): string
156    {
157        $values = Text::split($encValue, self::SEPARATOR);
158        if (Arr::getCount($values) !== 3) {
159            throw new ArgumentException();
160        }
161        list($algorithm, $ivBase64, $encData) = $values;
162
163        $iv = Binary::fromBase64($ivBase64);
164
165        $result = ErrorHandler::trap(fn() => openssl_decrypt($encData, $algorithm, $password, self::OPTION, $iv->raw));
166        if ($result->isFailureOrFalse()) {
167            throw new CryptoException($algorithm);
168        }
169
170        return $result->value;
171    }
172
173    /**
174     * `password_hash($password, PASSWORD_DEFAULT)`ラッパー。
175     *
176     * @param string $password 生パスワード。
177     * @return string ハッシュ化パスワード。
178     * @see https://www.php.net/manual/function.password-hash.php
179     */
180    public static function hashPassword(string $password): string
181    {
182        return password_hash($password, PASSWORD_DEFAULT);
183    }
184
185    /**
186     * `password_verify(string $password, $hashPassword)` ラッパー。
187     *
188     * @param string $password 生パスワード。
189     * @param string $hashPassword ハッシュ化パスワード。
190     * @return boolean 一致。
191     * @see https://www.php.net/manual/function.password-verify.php
192     */
193    public static function verifyPassword(string $password, string $hashPassword): bool
194    {
195        return password_verify($password, $hashPassword);
196    }
197
198    /**
199     * `password_needs_rehash` ラッパー。
200     *
201     * @param string $hashPassword
202     * @return bool
203     * @see https://www.php.net/manual/function.password-needs-rehash.php
204     */
205    public static function needsRehashPassword(string $hashPassword): bool
206    {
207        return password_needs_rehash($hashPassword, null, []);
208    }
209
210    /**
211     * `password_get_info` ラッパー。
212     *
213     * @param string $hashPassword
214     * @return array{algo:string,algoName:string,options:array<mixed>}
215     * @see https://www.php.net/manual/function.password-get-info.php
216     */
217    public static function getPasswordInformation(string $hashPassword): array
218    {
219        //@phpstan-ignore-next-line [DOCTYPE]
220        return password_get_info($hashPassword);
221    }
222
223    /**
224     * `password_algos` ラッパー。
225     *
226     * @return string[]
227     * @see https://www.php.net/manual/function.password-algos.php
228     */
229    public static function getPasswordAlgorithms(): array
230    {
231        return password_algos();
232    }
233
234    /**
235     * ハッシュアルゴリズム一覧。
236     *
237     * `hash_algos` ラッパー。
238     *
239     * @return string[]
240     * @see https://www.php.net/manual/function.hash-algos.php
241     */
242    public static function getHashAlgorithms(): array
243    {
244        return hash_algos();
245    }
246
247    /**
248     * ハッシュ値生成。
249     *
250     * @param bool $isBinary
251     * @param string $algorithm
252     * @param Binary $binary
253     * @param array{seed?:?int} $options
254     * @return string
255     */
256    private static function generateHashCore(bool $isBinary, string $algorithm, Binary $binary, array $options = []): string
257    {
258        try {
259            $hash = hash($algorithm, $binary->raw, $isBinary, $options);
260        } catch (Throwable $ex) {
261            Throws::reThrow(CryptoException::class, $ex);
262        }
263        //$hash = hash($algorithm, $binary->raw, $isBinary);
264        if ($hash === false) { //@phpstan-ignore-line [PHP_VERSION]
265            throw new CryptoException();
266        }
267
268        return $hash;
269    }
270
271    /**
272     * ハッシュ化処理(文字列)。
273     *
274     * `hash` ラッパー。
275     *
276     * @param non-empty-string $algorithm
277     * @param Binary $binary 入力バイナリデータ。
278     * @param array{seed?:?int} $options
279     * @return string 文字列表現。
280     * @throws CryptoException
281     * @see https://www.php.net/manual/function.hash.php
282     */
283    public static function generateHashString(string $algorithm, Binary $binary, array $options = []): string
284    {
285        return self::generateHashCore(false, $algorithm, $binary, $options);
286    }
287
288    /**
289     * ハッシュ化処理(バイナリ)。
290     *
291     * `hash` ラッパー。
292     *
293     * @param non-empty-string $algorithm アルゴリズム。
294     * @param Binary $binary 入力バイナリデータ。
295     * @param array{seed?:?int} $options
296     * @return Binary ハッシュバイナリ。
297     * @throws CryptoException
298     * @see https://www.php.net/manual/function.hash.php
299     */
300    public static function generateHashBinary(string $algorithm, Binary $binary, array $options = []): Binary
301    {
302        return new Binary(self::generateHashCore(true, $algorithm, $binary, $options));
303    }
304
305    #endregion
306}