Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
92.73% |
51 / 55 |
|
78.57% |
11 / 14 |
CRAP | |
0.00% |
0 / 1 |
Cryptography | |
92.73% |
51 / 55 |
|
78.57% |
11 / 14 |
27.28 | |
0.00% |
0 / 1 |
generateRandomInteger | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
generateRandomBinary | |
83.33% |
5 / 6 |
|
0.00% |
0 / 1 |
3.04 | |||
generateRandomString | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
4 | |||
encrypt | |
81.82% |
9 / 11 |
|
0.00% |
0 / 1 |
4.10 | |||
decrypt | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
3 | |||
hashPassword | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
verifyPassword | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
needsRehashPassword | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getPasswordInformation | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getPasswordAlgorithms | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getHashAlgorithms | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
generateHashCore | |
83.33% |
5 / 6 |
|
0.00% |
0 / 1 |
3.04 | |||
generateHashString | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
generateHashBinary | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | |
3 | declare(strict_types=1); |
4 | |
5 | namespace PeServer\Core; |
6 | |
7 | use Exception; |
8 | use Throwable; |
9 | use PeServer\Core\Binary; |
10 | use PeServer\Core\Collection\Arr; |
11 | use PeServer\Core\Errors\ErrorHandler; |
12 | use PeServer\Core\Text; |
13 | use PeServer\Core\Throws\ArgumentException; |
14 | use PeServer\Core\Throws\CryptoException; |
15 | use PeServer\Core\Throws\Throws; |
16 | |
17 | /** |
18 | * 暗号化周り |
19 | */ |
20 | abstract 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 | } |