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