Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
90.74% |
98 / 108 |
|
50.00% |
4 / 8 |
CRAP | |
0.00% |
0 / 1 |
EncryptionHelper | |
90.74% |
98 / 108 |
|
50.00% |
4 / 8 |
27.58 | |
0.00% |
0 / 1 |
createSharedKey | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
encryptShared | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
1 | |||
encryptFile | |
86.36% |
19 / 22 |
|
0.00% |
0 / 1 |
6.09 | |||
decryptShared | |
90.91% |
10 / 11 |
|
0.00% |
0 / 1 |
4.01 | |||
decryptFile | |
80.77% |
21 / 26 |
|
0.00% |
0 / 1 |
8.46 | |||
createPairedKey | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
1 | |||
encryptSecret | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
1 | |||
decryptSecret | |
93.33% |
14 / 15 |
|
0.00% |
0 / 1 |
5.01 |
1 | <?php |
2 | /** |
3 | * Jingga |
4 | * |
5 | * PHP Version 8.1 |
6 | * |
7 | * @package phpOMS\Security |
8 | * @copyright Dennis Eichhorn |
9 | * @license OMS License 2.0 |
10 | * @version 1.0.0 |
11 | * @link https://jingga.app |
12 | */ |
13 | declare(strict_types=1); |
14 | |
15 | namespace phpOMS\Security; |
16 | |
17 | /** |
18 | * Php encryption wrapper class. |
19 | * |
20 | * @package phpOMS\Security |
21 | * @license OMS License 2.0 |
22 | * @link https://jingga.app |
23 | * @since 1.0.0 |
24 | */ |
25 | final class EncryptionHelper |
26 | { |
27 | /** |
28 | * Create a shared key used by multiple entities. |
29 | * |
30 | * @return string |
31 | * |
32 | * @since 1.0.0 |
33 | */ |
34 | public static function createSharedKey() : string |
35 | { |
36 | $secretKey = \sodium_crypto_secretbox_keygen(); |
37 | |
38 | return \sodium_bin2hex($secretKey); |
39 | } |
40 | |
41 | /** |
42 | * Encrypt a message with a shared key |
43 | * |
44 | * @param string $message Message to encrypt |
45 | * @param string $keyHex Shared key as hex string used for encryption |
46 | * |
47 | * @return string |
48 | * |
49 | * @since 1.0.0 |
50 | */ |
51 | public static function encryptShared(string $message, string $keyHex) : string |
52 | { |
53 | $secretKey = \sodium_hex2bin($keyHex); |
54 | $nonce = \random_bytes(\SODIUM_CRYPTO_SECRETBOX_NONCEBYTES); |
55 | $ciphertext = \sodium_crypto_secretbox($message, $nonce, $secretKey); |
56 | |
57 | $result = \sodium_bin2base64($nonce . $ciphertext, \SODIUM_BASE64_VARIANT_ORIGINAL); |
58 | |
59 | \sodium_memzero($nonce); |
60 | \sodium_memzero($secretKey); |
61 | \sodium_memzero($ciphertext); |
62 | |
63 | /* |
64 | \sodium_memzero($message); |
65 | \sodium_memzero($keyHex); |
66 | */ |
67 | |
68 | return $result; |
69 | } |
70 | |
71 | /** |
72 | * Encrypt a file with a shared key |
73 | * |
74 | * @param string $in File to encrypt |
75 | * @param string $out Encrypted file |
76 | * @param string $keyHex Shared key as hex string used for encryption |
77 | * |
78 | * @return bool |
79 | * |
80 | * @since 1.0.0 |
81 | */ |
82 | public static function encryptFile(string $in, string $out, string $keyHex) : bool |
83 | { |
84 | $fpSource = \fopen($in, 'rb'); |
85 | $fpEncoded = \fopen($out . '.tmp', 'wb'); |
86 | |
87 | if ($fpSource === false || $fpEncoded === false) { |
88 | return false; |
89 | } |
90 | |
91 | $secretKey = \sodium_hex2bin($keyHex); |
92 | $nonce = \random_bytes(\SODIUM_CRYPTO_SECRETBOX_NONCEBYTES); |
93 | |
94 | \fwrite($fpEncoded, $nonce); |
95 | |
96 | while (!\feof($fpSource)) { |
97 | $buffer = \fread($fpSource, 4096); |
98 | if ($buffer === false) { |
99 | continue; |
100 | } |
101 | |
102 | $ciphertext = \sodium_crypto_secretbox($buffer, $nonce, $secretKey); |
103 | |
104 | \fwrite($fpEncoded, $ciphertext); |
105 | } |
106 | |
107 | \fclose($fpSource); |
108 | \fclose($fpEncoded); |
109 | |
110 | if ($in === $out) { |
111 | \unlink($in); |
112 | } |
113 | |
114 | \rename($out . '.tmp', $out); |
115 | |
116 | \sodium_memzero($nonce); |
117 | \sodium_memzero($secretKey); |
118 | \sodium_memzero($ciphertext); |
119 | |
120 | /* |
121 | \sodium_memzero($message); |
122 | \sodium_memzero($keyHex); |
123 | */ |
124 | |
125 | return true; |
126 | } |
127 | |
128 | /** |
129 | * Decrypt an encrypted message |
130 | * |
131 | * @param string $encrypted Encrypted message |
132 | * @param string $keyHex Shared key in hex |
133 | * |
134 | * @return string |
135 | * |
136 | * @since 1.0.0 |
137 | */ |
138 | public static function decryptShared(string $encrypted, string $keyHex) : string |
139 | { |
140 | if ($encrypted === '' || $keyHex === '') { |
141 | return $encrypted; |
142 | } |
143 | |
144 | $secretKey = \sodium_hex2bin($keyHex); |
145 | |
146 | $ciphertext = \sodium_base642bin($encrypted, \SODIUM_BASE64_VARIANT_ORIGINAL); |
147 | $nonce = \mb_substr($ciphertext, 0, \SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit'); |
148 | $ciphertext = \mb_substr($ciphertext, \SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit'); |
149 | |
150 | $plaintext = \sodium_crypto_secretbox_open($ciphertext, $nonce, $secretKey); |
151 | |
152 | \sodium_memzero($nonce); |
153 | \sodium_memzero($secretKey); |
154 | \sodium_memzero($ciphertext); |
155 | |
156 | /* |
157 | \sodium_memzero($keyHex); |
158 | */ |
159 | |
160 | return $plaintext === false ? '' : $plaintext; |
161 | } |
162 | |
163 | /** |
164 | * Decrypt an encrypted file |
165 | * |
166 | * @param string $in Encrypted file |
167 | * @param string $out Decrypted file |
168 | * @param string $keyHex Shared key in hex |
169 | * |
170 | * @return bool |
171 | * |
172 | * @since 1.0.0 |
173 | */ |
174 | public static function decryptFile(string $in, string $out, string $keyHex) : bool |
175 | { |
176 | $fpSource = \fopen($in, 'rb'); |
177 | $fpDecoded = \fopen($out . '.tmp', 'wb'); |
178 | |
179 | if ($fpSource === false || $fpDecoded === false) { |
180 | return false; |
181 | } |
182 | |
183 | $secretKey = \sodium_hex2bin($keyHex); |
184 | $nonce = \fread($fpSource, \SODIUM_CRYPTO_SECRETBOX_NONCEBYTES); |
185 | |
186 | if ($nonce === false) { |
187 | $nonce = ''; |
188 | } |
189 | |
190 | while (!\feof($fpSource)) { |
191 | $buffer = \fread($fpSource, 4096); |
192 | if ($buffer === false) { |
193 | continue; |
194 | } |
195 | |
196 | // $ciphertext = \mb_substr($buffer, \SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit'); Not required due to previous nonce fread |
197 | |
198 | $ciphertext = $buffer; |
199 | $plaintext = \sodium_crypto_secretbox_open($ciphertext, $nonce, $secretKey); |
200 | |
201 | if ($plaintext === false) { |
202 | return false; |
203 | } |
204 | |
205 | \fwrite($fpDecoded, $plaintext); |
206 | } |
207 | |
208 | \fclose($fpSource); |
209 | \fclose($fpDecoded); |
210 | |
211 | if ($in === $out) { |
212 | \unlink($in); |
213 | } |
214 | |
215 | \rename($out . '.tmp', $out); |
216 | |
217 | \sodium_memzero($nonce); |
218 | \sodium_memzero($secretKey); |
219 | \sodium_memzero($ciphertext); |
220 | |
221 | /* |
222 | \sodium_memzero($keyHex); |
223 | */ |
224 | |
225 | return true; |
226 | } |
227 | |
228 | /** |
229 | * Create a paired keys. |
230 | * |
231 | * @return array{alicePublic:string, alicePrivate:string, bobPublic:string, bobPrivate:string} |
232 | * |
233 | * @since 1.0.0 |
234 | */ |
235 | public static function createPairedKey() : array |
236 | { |
237 | $bobKeypair = \sodium_crypto_box_keypair(); |
238 | $bobPublicKey = \sodium_crypto_box_publickey($bobKeypair); |
239 | $bobPrivateKey = \sodium_crypto_box_secretkey($bobKeypair); |
240 | |
241 | $aliceKeypair = \sodium_crypto_box_keypair(); |
242 | $alicePublicKey = \sodium_crypto_box_publickey($aliceKeypair); |
243 | $alicePrivateKey = \sodium_crypto_box_secretkey($aliceKeypair); |
244 | |
245 | return [ |
246 | 'alicePublic' => \sodium_bin2hex($alicePublicKey), |
247 | 'alicePrivate' => \sodium_bin2hex($alicePrivateKey), |
248 | 'bobPublic' => \sodium_bin2hex($bobPublicKey), |
249 | 'bobPrivate' => \sodium_bin2hex($bobPrivateKey), |
250 | ]; |
251 | } |
252 | |
253 | /** |
254 | * Encrypt a message with a key pair |
255 | * |
256 | * @param string $message Message to encrypt |
257 | * @param string $privateKeyHex Private key (alicePrivate) |
258 | * @param string $publicKeyHex Public key (bobPublic) |
259 | * |
260 | * @return string |
261 | * |
262 | * @since 1.0.0 |
263 | */ |
264 | public static function encryptSecret(string $message, string $privateKeyHex, string $publicKeyHex) : string |
265 | { |
266 | $privateKey = \sodium_hex2bin($privateKeyHex); |
267 | $publicKey = \sodium_hex2bin($publicKeyHex); |
268 | |
269 | $key = \sodium_crypto_box_keypair_from_secretkey_and_publickey($privateKey, $publicKey); |
270 | $nonce = \random_bytes(\SODIUM_CRYPTO_BOX_NONCEBYTES); |
271 | $ciphertext = \sodium_crypto_box($message, $nonce, $key); |
272 | |
273 | $result = \sodium_bin2base64($nonce . $ciphertext, \SODIUM_BASE64_VARIANT_ORIGINAL); |
274 | |
275 | \sodium_memzero($key); |
276 | \sodium_memzero($nonce); |
277 | \sodium_memzero($ciphertext); |
278 | \sodium_memzero($privateKey); |
279 | \sodium_memzero($publicKey); |
280 | |
281 | /* |
282 | \sodium_memzero($message); |
283 | \sodium_memzero($privateKeyHex); |
284 | \sodium_memzero($publicKeyHex); |
285 | */ |
286 | |
287 | return $result; |
288 | } |
289 | |
290 | /** |
291 | * Decrypt a message with a key pair |
292 | * |
293 | * @param string $encrypted Message to encrypt |
294 | * @param string $privateKeyHex Private key (bobPrivate) |
295 | * @param string $publicKeyHex Public key (alicePublic) |
296 | * |
297 | * @return string |
298 | * |
299 | * @since 1.0.0 |
300 | */ |
301 | public static function decryptSecret(string $encrypted, string $privateKeyHex, string $publicKeyHex) : string |
302 | { |
303 | if ($encrypted === '' || $privateKeyHex === '' || $publicKeyHex === '') { |
304 | return $encrypted; |
305 | } |
306 | |
307 | $privateKey = \sodium_hex2bin($privateKeyHex); |
308 | $publicKey = \sodium_hex2bin($publicKeyHex); |
309 | |
310 | $message = \sodium_base642bin($encrypted, \SODIUM_BASE64_VARIANT_ORIGINAL); |
311 | $nonce = \mb_substr($message, 0, \SODIUM_CRYPTO_BOX_NONCEBYTES, '8bit'); |
312 | $ciphertext = \mb_substr($message, \SODIUM_CRYPTO_BOX_NONCEBYTES, null, '8bit'); |
313 | |
314 | $key = \sodium_crypto_box_keypair_from_secretkey_and_publickey($privateKey, $publicKey); |
315 | |
316 | $plaintext = \sodium_crypto_box_open($ciphertext, $nonce, $key); |
317 | |
318 | \sodium_memzero($key); |
319 | \sodium_memzero($ciphertext); |
320 | \sodium_memzero($nonce); |
321 | \sodium_memzero($privateKey); |
322 | \sodium_memzero($publicKey); |
323 | |
324 | /* |
325 | \sodium_memzero($message); |
326 | \sodium_memzero($privateKeyHex); |
327 | \sodium_memzero($publicKeyHex); |
328 | */ |
329 | |
330 | return $plaintext === false ? '' : $plaintext; |
331 | } |
332 | } |