]> git.mxchange.org Git - friendica.git/blob - src/Util/Crypto.php
Changes:
[friendica.git] / src / Util / Crypto.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2010-2024, the Friendica project
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\DI;
27 use phpseclib3\Crypt\PublicKeyLoader;
28
29 /**
30  * Crypto class
31  */
32 class Crypto
33 {
34         // supported algorithms are 'sha256', 'sha1'
35         /**
36          * @param string $data data
37          * @param string $key  key
38          * @param string $alg  algorithm
39          * @return string
40          */
41         public static function rsaSign($data, $key, $alg = 'sha256')
42         {
43                 if (empty($key)) {
44                         Logger::warning('Empty key parameter');
45                 }
46                 openssl_sign($data, $sig, $key, (($alg == 'sha1') ? OPENSSL_ALGO_SHA1 : $alg));
47                 return $sig;
48         }
49
50         /**
51          * @param string $data data
52          * @param string $sig  signature
53          * @param string $key  key
54          * @param string $alg  algorithm
55          * @return boolean
56          */
57         public static function rsaVerify($data, $sig, $key, $alg = 'sha256')
58         {
59                 if (empty($key)) {
60                         Logger::warning('Empty key parameter');
61                 }
62                 return openssl_verify($data, $sig, $key, (($alg == 'sha1') ? OPENSSL_ALGO_SHA1 : $alg));
63         }
64
65         /**
66          * Transform RSA public keys to standard PEM output
67          *
68          * @param string $key A RSA public key
69          *
70          * @return string The PEM output of this key
71          */
72         public static function rsaToPem(string $key)
73         {
74                 return (string)PublicKeyLoader::load($key);
75         }
76
77         /**
78          * @param integer $bits number of bits
79          * @return mixed
80          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
81          */
82         public static function newKeypair($bits)
83         {
84                 $openssl_options = [
85                         'digest_alg'       => 'sha1',
86                         'private_key_bits' => $bits,
87                         'encrypt_key'      => false
88                 ];
89
90                 $conf = DI::config()->get('system', 'openssl_conf_file');
91                 if ($conf) {
92                         $openssl_options['config'] = $conf;
93                 }
94                 $result = openssl_pkey_new($openssl_options);
95
96                 if (empty($result)) {
97                         Logger::notice('new_keypair: failed');
98                         return false;
99                 }
100
101                 // Get private key
102                 $response = ['prvkey' => '', 'pubkey' => ''];
103
104                 openssl_pkey_export($result, $response['prvkey']);
105
106                 // Get public key
107                 $pkey = openssl_pkey_get_details($result);
108                 $response['pubkey'] = $pkey["key"];
109
110                 return $response;
111         }
112
113         /**
114          * Encrypt a string with 'aes-256-cbc' cipher method.
115          *
116          * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
117          *
118          * @param string $data
119          * @param string $key   The key used for encryption.
120          * @param string $iv    A non-NULL Initialization Vector.
121          *
122          * @return string|boolean Encrypted string or false on failure.
123          */
124         private static function encryptAES256CBC($data, $key, $iv)
125         {
126                 return openssl_encrypt($data, 'aes-256-cbc', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0"));
127         }
128
129         /**
130          * Decrypt a string with 'aes-256-cbc' cipher method.
131          *
132          * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
133          *
134          * @param string $data
135          * @param string $key   The key used for decryption.
136          * @param string $iv    A non-NULL Initialization Vector.
137          *
138          * @return string|boolean Decrypted string or false on failure.
139          */
140         private static function decryptAES256CBC($data, $key, $iv)
141         {
142                 return openssl_decrypt($data, 'aes-256-cbc', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0"));
143         }
144
145         /**
146          *
147          * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
148          *
149          * @param string $data
150          * @param string $pubkey The public key.
151          * @param string $alg    The algorithm used for encryption.
152          *
153          * @return array
154          * @throws \Exception
155          */
156         public static function encapsulate($data, $pubkey, $alg = 'aes256cbc')
157         {
158                 if ($alg === 'aes256cbc') {
159                         return self::encapsulateAes($data, $pubkey);
160                 }
161                 return self::encapsulateOther($data, $pubkey, $alg);
162         }
163
164         /**
165          *
166          * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
167          *
168          * @param string $data
169          * @param string $pubkey The public key.
170          * @param string $alg    The algorithm used for encryption.
171          *
172          * @return array
173          * @throws \Exception
174          */
175         private static function encapsulateOther($data, $pubkey, $alg)
176         {
177                 if (!$pubkey) {
178                         Logger::notice('no key. data: '.$data);
179                 }
180                 $fn = 'encrypt' . strtoupper($alg);
181                 if (method_exists(__CLASS__, $fn)) {
182                         $result = ['encrypted' => true];
183                         $key = random_bytes(256);
184                         $iv  = random_bytes(256);
185                         $result['data'] = Strings::base64UrlEncode(self::$fn($data, $key, $iv), true);
186
187                         // log the offending call so we can track it down
188                         if (!openssl_public_encrypt($key, $k, $pubkey)) {
189                                 $x = debug_backtrace();
190                                 Logger::notice('RSA failed', ['trace' => $x[0]]);
191                         }
192
193                         $result['alg'] = $alg;
194                         $result['key'] = Strings::base64UrlEncode($k, true);
195                         openssl_public_encrypt($iv, $i, $pubkey);
196                         $result['iv'] = Strings::base64UrlEncode($i, true);
197
198                         return $result;
199                 } else {
200                         $x = ['data' => $data, 'pubkey' => $pubkey, 'alg' => $alg, 'result' => $data];
201                         Hook::callAll('other_encapsulate', $x);
202
203                         return $x['result'];
204                 }
205         }
206
207         /**
208          *
209          * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
210          *
211          * @param string $data
212          * @param string $pubkey
213          *
214          * @return array
215          * @throws \Exception
216          */
217         private static function encapsulateAes($data, $pubkey)
218         {
219                 if (!$pubkey) {
220                         Logger::notice('aes_encapsulate: no key. data: ' . $data);
221                 }
222
223                 $key = random_bytes(32);
224                 $iv  = random_bytes(16);
225                 $result = ['encrypted' => true];
226                 $result['data'] = Strings::base64UrlEncode(self::encryptAES256CBC($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('aes_encapsulate: RSA failed.', ['data' => $x[0]]);
232                 }
233
234                 $result['alg'] = 'aes256cbc';
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         }
241
242         /**
243          *
244          * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
245          *
246          * @param array $data ['iv' => $iv, 'key' => $key, 'alg' => $alg, 'data' => $data]
247          * @param string $prvkey The private key used for decryption.
248          *
249          * @return string|boolean The decrypted string or false on failure.
250          * @throws \Exception
251          */
252         public static function unencapsulate(array $data, $prvkey)
253         {
254                 if (!$data) {
255                         return;
256                 }
257
258                 $alg = $data['alg'] ?? 'aes256cbc';
259                 if ($alg === 'aes256cbc') {
260                         return self::unencapsulateAes($data['data'], $prvkey);
261                 }
262
263                 return self::unencapsulateOther($data, $prvkey, $alg);
264         }
265
266         /**
267          *
268          * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
269          *
270          * @param array $data
271          * @param string $prvkey The private key used for decryption.
272          * @param string $alg
273          *
274          * @return string|boolean The decrypted string or false on failure.
275          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
276          */
277         private static function unencapsulateOther(array $data, $prvkey, $alg)
278         {
279                 $fn = 'decrypt' . strtoupper($alg);
280
281                 if (method_exists(__CLASS__, $fn)) {
282                         openssl_private_decrypt(Strings::base64UrlDecode($data['key']), $k, $prvkey);
283                         openssl_private_decrypt(Strings::base64UrlDecode($data['iv']), $i, $prvkey);
284
285                         return self::$fn(Strings::base64UrlDecode($data['data']), $k, $i);
286                 } else {
287                         $x = ['data' => $data, 'prvkey' => $prvkey, 'alg' => $alg, 'result' => $data];
288                         Hook::callAll('other_unencapsulate', $x);
289
290                         return $x['result'];
291                 }
292         }
293
294         /**
295          *
296          * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
297          *
298          * @param array  $data
299          * @param string $prvkey The private key used for decryption.
300          *
301          * @return string|boolean The decrypted string or false on failure.
302          * @throws \Exception
303          */
304         private static function unencapsulateAes($data, $prvkey)
305         {
306                 openssl_private_decrypt(Strings::base64UrlDecode($data['key']), $k, $prvkey);
307                 openssl_private_decrypt(Strings::base64UrlDecode($data['iv']), $i, $prvkey);
308
309                 return self::decryptAES256CBC(Strings::base64UrlDecode($data['data']), $k, $i);
310         }
311
312
313         /**
314          * Creates cryptographic secure random digits
315          *
316          * @param string $digits The count of digits
317          * @return int The random Digits
318          *
319          * @throws \Exception In case 'random_int' isn't usable
320          */
321         public static function randomDigits($digits)
322         {
323                 $rn = '';
324
325                 // generating cryptographically secure pseudo-random integers
326                 for ($i = 0; $i < $digits; $i++) {
327                         $rn .= random_int(0, 9);
328                 }
329
330                 return $rn;
331         }
332 }