]> git.mxchange.org Git - friendica.git/blob - src/Util/Crypto.php
55852d59ba58defc7fc644ae09db9ea2486eafc0
[friendica.git] / src / Util / Crypto.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2020, Friendica
4  *
5  * @license GNU AGPL version 3 or any later version
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU Affero General Public License as
9  * published by the Free Software Foundation, either version 3 of the
10  * License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU Affero General Public License for more details.
16  *
17  * You should have received a copy of the GNU Affero General Public License
18  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19  *
20  */
21
22 namespace Friendica\Util;
23
24 use Friendica\Core\Hook;
25 use Friendica\Core\Logger;
26 use Friendica\Core\System;
27 use Friendica\DI;
28 use phpseclib\Crypt\RSA;
29 use phpseclib\Math\BigInteger;
30
31 /**
32  * Crypto class
33  */
34 class Crypto
35 {
36         // supported algorithms are 'sha256', 'sha1'
37         /**
38          * @param string $data data
39          * @param string $key  key
40          * @param string $alg  algorithm
41          * @return string
42          */
43         public static function rsaSign($data, $key, $alg = 'sha256')
44         {
45                 if (empty($key)) {
46                         Logger::warning('Empty key parameter', ['callstack' => System::callstack()]);
47                 }
48                 openssl_sign($data, $sig, $key, (($alg == 'sha1') ? OPENSSL_ALGO_SHA1 : $alg));
49                 return $sig;
50         }
51
52         /**
53          * @param string $data data
54          * @param string $sig  signature
55          * @param string $key  key
56          * @param string $alg  algorithm
57          * @return boolean
58          */
59         public static function rsaVerify($data, $sig, $key, $alg = 'sha256')
60         {
61                 if (empty($key)) {
62                         Logger::warning('Empty key parameter', ['callstack' => System::callstack()]);
63                 }
64                 return openssl_verify($data, $sig, $key, (($alg == 'sha1') ? OPENSSL_ALGO_SHA1 : $alg));
65         }
66
67         /**
68         /**
69          * @param string $m modulo
70          * @param string $e exponent
71          * @return string
72          */
73         public static function meToPem($m, $e)
74         {
75                 $rsa = new RSA();
76                 $rsa->loadKey(
77                         [
78                                 'e' => new BigInteger($e, 256),
79                                 'n' => new BigInteger($m, 256)
80                         ]);
81                 return $rsa->getPublicKey();
82         }
83
84         /**
85          * Transform RSA public keys to standard PEM output
86          *
87          * @param string $key A RSA public key
88          *
89          * @return string The PEM output of this key
90          */
91         public static function rsaToPem(string $key)
92         {
93                 $rsa = new RSA();
94                 $rsa->setPublicKey($key);
95
96                 return $rsa->getPublicKey(RSA::PUBLIC_FORMAT_PKCS8);
97         }
98
99         /**
100          * Extracts the modulo and exponent reference from a public PEM key
101          *
102          * @param string $key      public PEM key
103          * @param string $modulus  (ref) modulo reference
104          * @param string $exponent (ref) exponent reference
105          *
106          * @return void
107          */
108         public static function pemToMe(string $key, &$modulus, &$exponent)
109         {
110                 $rsa = new RSA();
111                 $rsa->loadKey($key);
112                 $rsa->setPublicKey();
113
114                 $modulus  = $rsa->modulus->toBytes();
115                 $exponent = $rsa->exponent->toBytes();
116         }
117
118         /**
119          * @param integer $bits number of bits
120          * @return mixed
121          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
122          */
123         public static function newKeypair($bits)
124         {
125                 $openssl_options = [
126                         'digest_alg'       => 'sha1',
127                         'private_key_bits' => $bits,
128                         'encrypt_key'      => false
129                 ];
130
131                 $conf = DI::config()->get('system', 'openssl_conf_file');
132                 if ($conf) {
133                         $openssl_options['config'] = $conf;
134                 }
135                 $result = openssl_pkey_new($openssl_options);
136
137                 if (empty($result)) {
138                         Logger::log('new_keypair: failed');
139                         return false;
140                 }
141
142                 // Get private key
143                 $response = ['prvkey' => '', 'pubkey' => ''];
144
145                 openssl_pkey_export($result, $response['prvkey']);
146
147                 // Get public key
148                 $pkey = openssl_pkey_get_details($result);
149                 $response['pubkey'] = $pkey["key"];
150
151                 return $response;
152         }
153
154         /**
155          * Encrypt a string with 'aes-256-cbc' cipher method.
156          *
157          * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
158          *
159          * @param string $data
160          * @param string $key   The key used for encryption.
161          * @param string $iv    A non-NULL Initialization Vector.
162          *
163          * @return string|boolean Encrypted string or false on failure.
164          */
165         private static function encryptAES256CBC($data, $key, $iv)
166         {
167                 return openssl_encrypt($data, 'aes-256-cbc', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0"));
168         }
169
170         /**
171          * Decrypt a string with 'aes-256-cbc' cipher method.
172          *
173          * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
174          *
175          * @param string $data
176          * @param string $key   The key used for decryption.
177          * @param string $iv    A non-NULL Initialization Vector.
178          *
179          * @return string|boolean Decrypted string or false on failure.
180          */
181         private static function decryptAES256CBC($data, $key, $iv)
182         {
183                 return openssl_decrypt($data, 'aes-256-cbc', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0"));
184         }
185
186         /**
187          *
188          * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
189          *
190          * @param string $data
191          * @param string $pubkey The public key.
192          * @param string $alg    The algorithm used for encryption.
193          *
194          * @return array
195          * @throws \Exception
196          */
197         public static function encapsulate($data, $pubkey, $alg = 'aes256cbc')
198         {
199                 if ($alg === 'aes256cbc') {
200                         return self::encapsulateAes($data, $pubkey);
201                 }
202                 return self::encapsulateOther($data, $pubkey, $alg);
203         }
204
205         /**
206          *
207          * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
208          *
209          * @param string $data
210          * @param string $pubkey The public key.
211          * @param string $alg    The algorithm used for encryption.
212          *
213          * @return array
214          * @throws \Exception
215          */
216         private static function encapsulateOther($data, $pubkey, $alg)
217         {
218                 if (!$pubkey) {
219                         Logger::log('no key. data: '.$data);
220                 }
221                 $fn = 'encrypt' . strtoupper($alg);
222                 if (method_exists(__CLASS__, $fn)) {
223                         $result = ['encrypted' => true];
224                         $key = random_bytes(256);
225                         $iv  = random_bytes(256);
226                         $result['data'] = Strings::base64UrlEncode(self::$fn($data, $key, $iv), true);
227
228                         // log the offending call so we can track it down
229                         if (!openssl_public_encrypt($key, $k, $pubkey)) {
230                                 $x = debug_backtrace();
231                                 Logger::notice('RSA failed', ['trace' => $x[0]]);
232                         }
233
234                         $result['alg'] = $alg;
235                         $result['key'] = Strings::base64UrlEncode($k, true);
236                         openssl_public_encrypt($iv, $i, $pubkey);
237                         $result['iv'] = Strings::base64UrlEncode($i, true);
238
239                         return $result;
240                 } else {
241                         $x = ['data' => $data, 'pubkey' => $pubkey, 'alg' => $alg, 'result' => $data];
242                         Hook::callAll('other_encapsulate', $x);
243
244                         return $x['result'];
245                 }
246         }
247
248         /**
249          *
250          * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
251          *
252          * @param string $data
253          * @param string $pubkey
254          *
255          * @return array
256          * @throws \Exception
257          */
258         private static function encapsulateAes($data, $pubkey)
259         {
260                 if (!$pubkey) {
261                         Logger::log('aes_encapsulate: no key. data: ' . $data);
262                 }
263
264                 $key = random_bytes(32);
265                 $iv  = random_bytes(16);
266                 $result = ['encrypted' => true];
267                 $result['data'] = Strings::base64UrlEncode(self::encryptAES256CBC($data, $key, $iv), true);
268
269                 // log the offending call so we can track it down
270                 if (!openssl_public_encrypt($key, $k, $pubkey)) {
271                         $x = debug_backtrace();
272                         Logger::log('aes_encapsulate: RSA failed. ' . print_r($x[0], true));
273                 }
274
275                 $result['alg'] = 'aes256cbc';
276                 $result['key'] = Strings::base64UrlEncode($k, true);
277                 openssl_public_encrypt($iv, $i, $pubkey);
278                 $result['iv'] = Strings::base64UrlEncode($i, true);
279
280                 return $result;
281         }
282
283         /**
284          *
285          * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
286          *
287          * @param array $data ['iv' => $iv, 'key' => $key, 'alg' => $alg, 'data' => $data]
288          * @param string $prvkey The private key used for decryption.
289          *
290          * @return string|boolean The decrypted string or false on failure.
291          * @throws \Exception
292          */
293         public static function unencapsulate(array $data, $prvkey)
294         {
295                 if (!$data) {
296                         return;
297                 }
298
299                 $alg = $data['alg'] ?? 'aes256cbc';
300                 if ($alg === 'aes256cbc') {
301                         return self::unencapsulateAes($data['data'], $prvkey);
302                 }
303
304                 return self::unencapsulateOther($data, $prvkey, $alg);
305         }
306
307         /**
308          *
309          * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
310          *
311          * @param array $data
312          * @param string $prvkey The private key used for decryption.
313          * @param string $alg
314          *
315          * @return string|boolean The decrypted string or false on failure.
316          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
317          */
318         private static function unencapsulateOther(array $data, $prvkey, $alg)
319         {
320                 $fn = 'decrypt' . strtoupper($alg);
321
322                 if (method_exists(__CLASS__, $fn)) {
323                         openssl_private_decrypt(Strings::base64UrlDecode($data['key']), $k, $prvkey);
324                         openssl_private_decrypt(Strings::base64UrlDecode($data['iv']), $i, $prvkey);
325
326                         return self::$fn(Strings::base64UrlDecode($data['data']), $k, $i);
327                 } else {
328                         $x = ['data' => $data, 'prvkey' => $prvkey, 'alg' => $alg, 'result' => $data];
329                         Hook::callAll('other_unencapsulate', $x);
330
331                         return $x['result'];
332                 }
333         }
334
335         /**
336          *
337          * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
338          *
339          * @param array  $data
340          * @param string $prvkey The private key used for decryption.
341          *
342          * @return string|boolean The decrypted string or false on failure.
343          * @throws \Exception
344          */
345         private static function unencapsulateAes($data, $prvkey)
346         {
347                 openssl_private_decrypt(Strings::base64UrlDecode($data['key']), $k, $prvkey);
348                 openssl_private_decrypt(Strings::base64UrlDecode($data['iv']), $i, $prvkey);
349
350                 return self::decryptAES256CBC(Strings::base64UrlDecode($data['data']), $k, $i);
351         }
352
353
354         /**
355          * Creates cryptographic secure random digits
356          *
357          * @param string $digits The count of digits
358          * @return int The random Digits
359          *
360          * @throws \Exception In case 'random_int' isn't usable
361          */
362         public static function randomDigits($digits)
363         {
364                 $rn = '';
365
366                 // generating cryptographically secure pseudo-random integers
367                 for ($i = 0; $i < $digits; $i++) {
368                         $rn .= random_int(0, 9);
369                 }
370
371                 return $rn;
372         }
373 }