Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
90.74% covered (success)
90.74%
98 / 108
50.00% covered (danger)
50.00%
4 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
EncryptionHelper
90.74% covered (success)
90.74%
98 / 108
50.00% covered (danger)
50.00%
4 / 8
27.58
0.00% covered (danger)
0.00%
0 / 1
 createSharedKey
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 encryptShared
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 encryptFile
86.36% covered (warning)
86.36%
19 / 22
0.00% covered (danger)
0.00%
0 / 1
6.09
 decryptShared
90.91% covered (success)
90.91%
10 / 11
0.00% covered (danger)
0.00%
0 / 1
4.01
 decryptFile
80.77% covered (warning)
80.77%
21 / 26
0.00% covered (danger)
0.00%
0 / 1
8.46
 createPairedKey
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
1
 encryptSecret
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
1
 decryptSecret
93.33% covered (success)
93.33%
14 / 15
0.00% covered (danger)
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 */
13declare(strict_types=1);
14
15namespace 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 */
25final 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}