]> git.mxchange.org Git - friendica.git/blob - src/Util/Crypto.php
Merge pull request #7575 from nupplaphil/bug/friendica-7299
[friendica.git] / src / Util / Crypto.php
1 <?php
2 /**
3  * @file src/Util/Crypto.php
4  */
5 namespace Friendica\Util;
6
7 use ASN_BASE;
8 use ASNValue;
9 use Friendica\Core\Config;
10 use Friendica\Core\Hook;
11 use Friendica\Core\Logger;
12 use Friendica\Core\System;
13
14 /**
15  * @brief Crypto class
16  */
17 class Crypto
18 {
19         // supported algorithms are 'sha256', 'sha1'
20         /**
21          * @param string $data data
22          * @param string $key  key
23          * @param string $alg  algorithm
24          * @return string
25          */
26         public static function rsaSign($data, $key, $alg = 'sha256')
27         {
28                 if (empty($key)) {
29                         Logger::warning('Empty key parameter', ['callstack' => System::callstack()]);
30                 }
31                 openssl_sign($data, $sig, $key, (($alg == 'sha1') ? OPENSSL_ALGO_SHA1 : $alg));
32                 return $sig;
33         }
34
35         /**
36          * @param string $data data
37          * @param string $sig  signature
38          * @param string $key  key
39          * @param string $alg  algorithm
40          * @return boolean
41          */
42         public static function rsaVerify($data, $sig, $key, $alg = 'sha256')
43         {
44                 if (empty($key)) {
45                         Logger::warning('Empty key parameter', ['callstack' => System::callstack()]);
46                 }
47                 return openssl_verify($data, $sig, $key, (($alg == 'sha1') ? OPENSSL_ALGO_SHA1 : $alg));
48         }
49
50         /**
51          * @param string $Der     der formatted string
52          * @param bool   $Private key type optional, default false
53          * @return string
54          */
55         private static function DerToPem($Der, $Private = false)
56         {
57                 //Encode:
58                 $Der = base64_encode($Der);
59                 //Split lines:
60                 $lines = str_split($Der, 65);
61                 $body = implode("\n", $lines);
62                 //Get title:
63                 $title = $Private ? 'RSA PRIVATE KEY' : 'PUBLIC KEY';
64                 //Add wrapping:
65                 $result = "-----BEGIN {$title}-----\n";
66                 $result .= $body . "\n";
67                 $result .= "-----END {$title}-----\n";
68
69                 return $result;
70         }
71
72         /**
73          * @param string $Der der formatted string
74          * @return string
75          */
76         private static function DerToRsa($Der)
77         {
78                 //Encode:
79                 $Der = base64_encode($Der);
80                 //Split lines:
81                 $lines = str_split($Der, 64);
82                 $body = implode("\n", $lines);
83                 //Get title:
84                 $title = 'RSA PUBLIC KEY';
85                 //Add wrapping:
86                 $result = "-----BEGIN {$title}-----\n";
87                 $result .= $body . "\n";
88                 $result .= "-----END {$title}-----\n";
89
90                 return $result;
91         }
92
93         /**
94          * @param string $Modulus        modulo
95          * @param string $PublicExponent exponent
96          * @return string
97          */
98         private static function pkcs8Encode($Modulus, $PublicExponent)
99         {
100                 //Encode key sequence
101                 $modulus = new ASNValue(ASNValue::TAG_INTEGER);
102                 $modulus->SetIntBuffer($Modulus);
103                 $publicExponent = new ASNValue(ASNValue::TAG_INTEGER);
104                 $publicExponent->SetIntBuffer($PublicExponent);
105                 $keySequenceItems = [$modulus, $publicExponent];
106                 $keySequence = new ASNValue(ASNValue::TAG_SEQUENCE);
107                 $keySequence->SetSequence($keySequenceItems);
108                 //Encode bit string
109                 $bitStringValue = $keySequence->Encode();
110                 $bitStringValue = chr(0x00) . $bitStringValue; //Add unused bits byte
111                 $bitString = new ASNValue(ASNValue::TAG_BITSTRING);
112                 $bitString->Value = $bitStringValue;
113                 //Encode body
114                 $bodyValue = "\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00" . $bitString->Encode();
115                 $body = new ASNValue(ASNValue::TAG_SEQUENCE);
116                 $body->Value = $bodyValue;
117                 //Get DER encoded public key:
118                 $PublicDER = $body->Encode();
119                 return $PublicDER;
120         }
121
122         /**
123          * @param string $Modulus        modulo
124          * @param string $PublicExponent exponent
125          * @return string
126          */
127         private static function pkcs1Encode($Modulus, $PublicExponent)
128         {
129                 //Encode key sequence
130                 $modulus = new ASNValue(ASNValue::TAG_INTEGER);
131                 $modulus->SetIntBuffer($Modulus);
132                 $publicExponent = new ASNValue(ASNValue::TAG_INTEGER);
133                 $publicExponent->SetIntBuffer($PublicExponent);
134                 $keySequenceItems = [$modulus, $publicExponent];
135                 $keySequence = new ASNValue(ASNValue::TAG_SEQUENCE);
136                 $keySequence->SetSequence($keySequenceItems);
137                 //Encode bit string
138                 $bitStringValue = $keySequence->Encode();
139                 return $bitStringValue;
140         }
141
142         /**
143          * @param string $m modulo
144          * @param string $e exponent
145          * @return string
146          */
147         public static function meToPem($m, $e)
148         {
149                 $der = self::pkcs8Encode($m, $e);
150                 $key = self::DerToPem($der, false);
151                 return $key;
152         }
153
154         /**
155          * @param string $key key
156          * @param string $m   modulo reference
157          * @param object $e   exponent reference
158          * @return void
159          * @throws \Exception
160          */
161         private static function pubRsaToMe($key, &$m, &$e)
162         {
163                 $lines = explode("\n", $key);
164                 unset($lines[0]);
165                 unset($lines[count($lines)]);
166                 $x = base64_decode(implode('', $lines));
167
168                 $r = ASN_BASE::parseASNString($x);
169
170                 $m = Strings::base64UrlDecode($r[0]->asnData[0]->asnData);
171                 $e = Strings::base64UrlDecode($r[0]->asnData[1]->asnData);
172         }
173
174         /**
175          * @param string $key key
176          * @return string
177          * @throws \Exception
178          */
179         public static function rsaToPem($key)
180         {
181                 self::pubRsaToMe($key, $m, $e);
182                 return self::meToPem($m, $e);
183         }
184
185         /**
186          * @param string $key key
187          * @return string
188          * @throws \Exception
189          */
190         private static function pemToRsa($key)
191         {
192                 self::pemToMe($key, $m, $e);
193                 return self::meToRsa($m, $e);
194         }
195
196         /**
197          * @param string $key key
198          * @param string $m   modulo reference
199          * @param string $e   exponent reference
200          * @return void
201          * @throws \Exception
202          */
203         public static function pemToMe($key, &$m, &$e)
204         {
205                 $lines = explode("\n", $key);
206                 unset($lines[0]);
207                 unset($lines[count($lines)]);
208                 $x = base64_decode(implode('', $lines));
209
210                 $r = ASN_BASE::parseASNString($x);
211
212                 if (isset($r[0])) {
213                         $m = Strings::base64UrlDecode($r[0]->asnData[1]->asnData[0]->asnData[0]->asnData);
214                         $e = Strings::base64UrlDecode($r[0]->asnData[1]->asnData[0]->asnData[1]->asnData);
215                 }
216         }
217
218         /**
219          * @param string $m modulo
220          * @param string $e exponent
221          * @return string
222          */
223         private static function meToRsa($m, $e)
224         {
225                 $der = self::pkcs1Encode($m, $e);
226                 $key = self::DerToRsa($der);
227                 return $key;
228         }
229
230         /**
231          * @param integer $bits number of bits
232          * @return mixed
233          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
234          */
235         public static function newKeypair($bits)
236         {
237                 $openssl_options = [
238                         'digest_alg'       => 'sha1',
239                         'private_key_bits' => $bits,
240                         'encrypt_key'      => false
241                 ];
242
243                 $conf = Config::get('system', 'openssl_conf_file');
244                 if ($conf) {
245                         $openssl_options['config'] = $conf;
246                 }
247                 $result = openssl_pkey_new($openssl_options);
248
249                 if (empty($result)) {
250                         Logger::log('new_keypair: failed');
251                         return false;
252                 }
253
254                 // Get private key
255                 $response = ['prvkey' => '', 'pubkey' => ''];
256
257                 openssl_pkey_export($result, $response['prvkey']);
258
259                 // Get public key
260                 $pkey = openssl_pkey_get_details($result);
261                 $response['pubkey'] = $pkey["key"];
262
263                 return $response;
264         }
265
266         /**
267          * Encrypt a string with 'aes-256-cbc' cipher method.
268          * 
269          * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
270          * 
271          * @param string $data
272          * @param string $key   The key used for encryption.
273          * @param string $iv    A non-NULL Initialization Vector.
274          * 
275          * @return string|boolean Encrypted string or false on failure.
276          */
277         private static function encryptAES256CBC($data, $key, $iv)
278         {
279                 return openssl_encrypt($data, 'aes-256-cbc', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0"));
280         }
281
282         /**
283          * Decrypt a string with 'aes-256-cbc' cipher method.
284          * 
285          * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
286          * 
287          * @param string $data
288          * @param string $key   The key used for decryption.
289          * @param string $iv    A non-NULL Initialization Vector.
290          * 
291          * @return string|boolean Decrypted string or false on failure.
292          */
293         private static function decryptAES256CBC($data, $key, $iv)
294         {
295                 return openssl_decrypt($data, 'aes-256-cbc', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0"));
296         }
297
298         /**
299          * Encrypt a string with 'aes-256-ctr' cipher method.
300          * 
301          * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
302          * 
303          * @param string $data
304          * @param string $key   The key used for encryption.
305          * @param string $iv    A non-NULL Initialization Vector.
306          * 
307          * @return string|boolean Encrypted string or false on failure.
308          */
309         private static function encryptAES256CTR($data, $key, $iv)
310         {
311                 $key = substr($key, 0, 32);
312                 $iv = substr($iv, 0, 16);
313                 return openssl_encrypt($data, 'aes-256-ctr', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0"));
314         }
315
316         /**
317          * Decrypt a string with 'aes-256-ctr' cipher method.
318          * 
319          * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
320          * 
321          * @param string $data
322          * @param string $key   The key used for decryption.
323          * @param string $iv    A non-NULL Initialization Vector.
324          * 
325          * @return string|boolean Decrypted string or false on failure.
326          */
327         private static function decryptAES256CTR($data, $key, $iv)
328         {
329                 $key = substr($key, 0, 32);
330                 $iv = substr($iv, 0, 16);
331                 return openssl_decrypt($data, 'aes-256-ctr', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0"));
332         }
333
334         /**
335          *
336          * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
337          *
338          * @param string $data
339          * @param string $pubkey The public key.
340          * @param string $alg    The algorithm used for encryption.
341          *
342          * @return array
343          * @throws \Exception
344          */
345         public static function encapsulate($data, $pubkey, $alg = 'aes256cbc')
346         {
347                 if ($alg === 'aes256cbc') {
348                         return self::encapsulateAes($data, $pubkey);
349                 }
350                 return self::encapsulateOther($data, $pubkey, $alg);
351         }
352
353         /**
354          *
355          * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
356          *
357          * @param string $data
358          * @param string $pubkey The public key.
359          * @param string $alg    The algorithm used for encryption.
360          *
361          * @return array
362          * @throws \Exception
363          */
364         private static function encapsulateOther($data, $pubkey, $alg)
365         {
366                 if (!$pubkey) {
367                         Logger::log('no key. data: '.$data);
368                 }
369                 $fn = 'encrypt' . strtoupper($alg);
370                 if (method_exists(__CLASS__, $fn)) {
371                         $result = ['encrypted' => true];
372                         $key = random_bytes(256);
373                         $iv  = random_bytes(256);
374                         $result['data'] = Strings::base64UrlEncode(self::$fn($data, $key, $iv), true);
375
376                         // log the offending call so we can track it down
377                         if (!openssl_public_encrypt($key, $k, $pubkey)) {
378                                 $x = debug_backtrace();
379                                 Logger::log('RSA failed. ' . print_r($x[0], true));
380                         }
381
382                         $result['alg'] = $alg;
383                         $result['key'] = Strings::base64UrlEncode($k, true);
384                         openssl_public_encrypt($iv, $i, $pubkey);
385                         $result['iv'] = Strings::base64UrlEncode($i, true);
386
387                         return $result;
388                 } else {
389                         $x = ['data' => $data, 'pubkey' => $pubkey, 'alg' => $alg, 'result' => $data];
390                         Hook::callAll('other_encapsulate', $x);
391
392                         return $x['result'];
393                 }
394         }
395
396         /**
397          *
398          * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
399          *
400          * @param string $data
401          * @param string $pubkey
402          *
403          * @return array
404          * @throws \Exception
405          */
406         private static function encapsulateAes($data, $pubkey)
407         {
408                 if (!$pubkey) {
409                         Logger::log('aes_encapsulate: no key. data: ' . $data);
410                 }
411
412                 $key = random_bytes(32);
413                 $iv  = random_bytes(16);
414                 $result = ['encrypted' => true];
415                 $result['data'] = Strings::base64UrlEncode(self::encryptAES256CBC($data, $key, $iv), true);
416
417                 // log the offending call so we can track it down
418                 if (!openssl_public_encrypt($key, $k, $pubkey)) {
419                         $x = debug_backtrace();
420                         Logger::log('aes_encapsulate: RSA failed. ' . print_r($x[0], true));
421                 }
422
423                 $result['alg'] = 'aes256cbc';
424                 $result['key'] = Strings::base64UrlEncode($k, true);
425                 openssl_public_encrypt($iv, $i, $pubkey);
426                 $result['iv'] = Strings::base64UrlEncode($i, true);
427
428                 return $result;
429         }
430
431         /**
432          *
433          * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
434          *
435          * @param array $data ['iv' => $iv, 'key' => $key, 'alg' => $alg, 'data' => $data]
436          * @param string $prvkey The private key used for decryption.
437          *
438          * @return string|boolean The decrypted string or false on failure.
439          * @throws \Exception
440          */
441         public static function unencapsulate(array $data, $prvkey)
442         {
443                 if (!$data) {
444                         return;
445                 }
446
447                 $alg = ((array_key_exists('alg', $data)) ? $data['alg'] : 'aes256cbc');
448                 if ($alg === 'aes256cbc') {
449                         return self::encapsulateAes($data['data'], $prvkey);
450                 }
451                 return self::encapsulateOther($data['data'], $prvkey, $alg);
452         }
453
454         /**
455          *
456          * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
457          *
458          * @param array $data
459          * @param string $prvkey The private key used for decryption.
460          * @param string $alg
461          *
462          * @return string|boolean The decrypted string or false on failure.
463          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
464          */
465         private static function unencapsulateOther(array $data, $prvkey, $alg)
466         {
467                 $fn = 'decrypt' . strtoupper($alg);
468
469                 if (method_exists(__CLASS__, $fn)) {
470                         openssl_private_decrypt(Strings::base64UrlDecode($data['key']), $k, $prvkey);
471                         openssl_private_decrypt(Strings::base64UrlDecode($data['iv']), $i, $prvkey);
472
473                         return self::$fn(Strings::base64UrlDecode($data['data']), $k, $i);
474                 } else {
475                         $x = ['data' => $data, 'prvkey' => $prvkey, 'alg' => $alg, 'result' => $data];
476                         Hook::callAll('other_unencapsulate', $x);
477
478                         return $x['result'];
479                 }
480         }
481
482         /**
483          *
484          * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
485          *
486          * @param array  $data
487          * @param string $prvkey The private key used for decryption.
488          *
489          * @return string|boolean The decrypted string or false on failure.
490          * @throws \Exception
491          */
492         private static function unencapsulateAes($data, $prvkey)
493         {
494                 openssl_private_decrypt(Strings::base64UrlDecode($data['key']), $k, $prvkey);
495                 openssl_private_decrypt(Strings::base64UrlDecode($data['iv']), $i, $prvkey);
496
497                 return self::decryptAES256CBC(Strings::base64UrlDecode($data['data']), $k, $i);
498         }
499
500
501         /**
502          * Creates cryptographic secure random digits
503          *
504          * @param string $digits The count of digits
505          * @return int The random Digits
506          *
507          * @throws \Exception In case 'random_int' isn't usable
508          */
509         public static function randomDigits($digits)
510         {
511                 $rn = '';
512
513                 // generating cryptographically secure pseudo-random integers
514                 for ($i = 0; $i < $digits; $i++) {
515                         $rn .= random_int(0, 9);
516                 }
517
518                 return $rn;
519         }
520 }