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