3 * @file src/Util/Crypto.php
5 namespace Friendica\Util;
7 use Friendica\Core\Config;
8 use Friendica\Core\Hook;
9 use Friendica\Core\Logger;
18 // supported algorithms are 'sha256', 'sha1'
20 * @param string $data data
21 * @param string $key key
22 * @param string $alg algorithm
25 public static function rsaSign($data, $key, $alg = 'sha256')
28 logger::warning('Empty key parameter', ['callstack' => System::callstack()]);
30 openssl_sign($data, $sig, $key, (($alg == 'sha1') ? OPENSSL_ALGO_SHA1 : $alg));
35 * @param string $data data
36 * @param string $sig signature
37 * @param string $key key
38 * @param string $alg algorithm
41 public static function rsaVerify($data, $sig, $key, $alg = 'sha256')
44 logger::warning('Empty key parameter', ['callstack' => System::callstack()]);
46 return openssl_verify($data, $sig, $key, (($alg == 'sha1') ? OPENSSL_ALGO_SHA1 : $alg));
50 * @param string $Der der formatted string
51 * @param bool $Private key type optional, default false
54 private static function DerToPem($Der, $Private = false)
57 $Der = base64_encode($Der);
59 $lines = str_split($Der, 65);
60 $body = implode("\n", $lines);
62 $title = $Private ? 'RSA PRIVATE KEY' : 'PUBLIC KEY';
64 $result = "-----BEGIN {$title}-----\n";
65 $result .= $body . "\n";
66 $result .= "-----END {$title}-----\n";
72 * @param string $Der der formatted string
75 private static function DerToRsa($Der)
78 $Der = base64_encode($Der);
80 $lines = str_split($Der, 64);
81 $body = implode("\n", $lines);
83 $title = 'RSA PUBLIC KEY';
85 $result = "-----BEGIN {$title}-----\n";
86 $result .= $body . "\n";
87 $result .= "-----END {$title}-----\n";
93 * @param string $Modulus modulo
94 * @param string $PublicExponent exponent
97 private static function pkcs8Encode($Modulus, $PublicExponent)
100 $modulus = new ASNValue(ASNValue::TAG_INTEGER);
101 $modulus->SetIntBuffer($Modulus);
102 $publicExponent = new ASNValue(ASNValue::TAG_INTEGER);
103 $publicExponent->SetIntBuffer($PublicExponent);
104 $keySequenceItems = [$modulus, $publicExponent];
105 $keySequence = new ASNValue(ASNValue::TAG_SEQUENCE);
106 $keySequence->SetSequence($keySequenceItems);
108 $bitStringValue = $keySequence->Encode();
109 $bitStringValue = chr(0x00) . $bitStringValue; //Add unused bits byte
110 $bitString = new ASNValue(ASNValue::TAG_BITSTRING);
111 $bitString->Value = $bitStringValue;
113 $bodyValue = "\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00" . $bitString->Encode();
114 $body = new ASNValue(ASNValue::TAG_SEQUENCE);
115 $body->Value = $bodyValue;
116 //Get DER encoded public key:
117 $PublicDER = $body->Encode();
122 * @param string $Modulus modulo
123 * @param string $PublicExponent exponent
126 private static function pkcs1Encode($Modulus, $PublicExponent)
128 //Encode key sequence
129 $modulus = new ASNValue(ASNValue::TAG_INTEGER);
130 $modulus->SetIntBuffer($Modulus);
131 $publicExponent = new ASNValue(ASNValue::TAG_INTEGER);
132 $publicExponent->SetIntBuffer($PublicExponent);
133 $keySequenceItems = [$modulus, $publicExponent];
134 $keySequence = new ASNValue(ASNValue::TAG_SEQUENCE);
135 $keySequence->SetSequence($keySequenceItems);
137 $bitStringValue = $keySequence->Encode();
138 return $bitStringValue;
142 * @param string $m modulo
143 * @param string $e exponent
146 public static function meToPem($m, $e)
148 $der = self::pkcs8Encode($m, $e);
149 $key = self::DerToPem($der, false);
154 * @param string $key key
155 * @param string $m modulo reference
156 * @param object $e exponent reference
160 private static function pubRsaToMe($key, &$m, &$e)
162 $lines = explode("\n", $key);
164 unset($lines[count($lines)]);
165 $x = base64_decode(implode('', $lines));
167 $r = ASN_BASE::parseASNString($x);
169 $m = Strings::base64UrlDecode($r[0]->asnData[0]->asnData);
170 $e = Strings::base64UrlDecode($r[0]->asnData[1]->asnData);
174 * @param string $key key
178 public static function rsaToPem($key)
180 self::pubRsaToMe($key, $m, $e);
181 return self::meToPem($m, $e);
185 * @param string $key key
189 private static function pemToRsa($key)
191 self::pemToMe($key, $m, $e);
192 return self::meToRsa($m, $e);
196 * @param string $key key
197 * @param string $m modulo reference
198 * @param string $e exponent reference
202 public static function pemToMe($key, &$m, &$e)
204 $lines = explode("\n", $key);
206 unset($lines[count($lines)]);
207 $x = base64_decode(implode('', $lines));
209 $r = ASN_BASE::parseASNString($x);
211 $m = Strings::base64UrlDecode($r[0]->asnData[1]->asnData[0]->asnData[0]->asnData);
212 $e = Strings::base64UrlDecode($r[0]->asnData[1]->asnData[0]->asnData[1]->asnData);
216 * @param string $m modulo
217 * @param string $e exponent
220 private static function meToRsa($m, $e)
222 $der = self::pkcs1Encode($m, $e);
223 $key = self::DerToRsa($der);
228 * @param integer $bits number of bits
230 * @throws \Friendica\Network\HTTPException\InternalServerErrorException
232 public static function newKeypair($bits)
235 'digest_alg' => 'sha1',
236 'private_key_bits' => $bits,
237 'encrypt_key' => false
240 $conf = Config::get('system', 'openssl_conf_file');
242 $openssl_options['config'] = $conf;
244 $result = openssl_pkey_new($openssl_options);
246 if (empty($result)) {
247 Logger::log('new_keypair: failed');
252 $response = ['prvkey' => '', 'pubkey' => ''];
254 openssl_pkey_export($result, $response['prvkey']);
257 $pkey = openssl_pkey_get_details($result);
258 $response['pubkey'] = $pkey["key"];
264 * Encrypt a string with 'aes-256-cbc' cipher method.
266 * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
268 * @param string $data
269 * @param string $key The key used for encryption.
270 * @param string $iv A non-NULL Initialization Vector.
272 * @return string|boolean Encrypted string or false on failure.
274 private static function encryptAES256CBC($data, $key, $iv)
276 return openssl_encrypt($data, 'aes-256-cbc', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0"));
280 * Decrypt a string with 'aes-256-cbc' cipher method.
282 * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
284 * @param string $data
285 * @param string $key The key used for decryption.
286 * @param string $iv A non-NULL Initialization Vector.
288 * @return string|boolean Decrypted string or false on failure.
290 private static function decryptAES256CBC($data, $key, $iv)
292 return openssl_decrypt($data, 'aes-256-cbc', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0"));
296 * Encrypt a string with 'aes-256-ctr' cipher method.
298 * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
300 * @param string $data
301 * @param string $key The key used for encryption.
302 * @param string $iv A non-NULL Initialization Vector.
304 * @return string|boolean Encrypted string or false on failure.
306 private static function encryptAES256CTR($data, $key, $iv)
308 $key = substr($key, 0, 32);
309 $iv = substr($iv, 0, 16);
310 return openssl_encrypt($data, 'aes-256-ctr', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0"));
314 * Decrypt a string with 'aes-256-ctr' cipher method.
316 * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
318 * @param string $data
319 * @param string $key The key used for decryption.
320 * @param string $iv A non-NULL Initialization Vector.
322 * @return string|boolean Decrypted string or false on failure.
324 private static function decryptAES256CTR($data, $key, $iv)
326 $key = substr($key, 0, 32);
327 $iv = substr($iv, 0, 16);
328 return openssl_decrypt($data, 'aes-256-ctr', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0"));
333 * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
335 * @param string $data
336 * @param string $pubkey The public key.
337 * @param string $alg The algorithm used for encryption.
342 public static function encapsulate($data, $pubkey, $alg = 'aes256cbc')
344 if ($alg === 'aes256cbc') {
345 return self::encapsulateAes($data, $pubkey);
347 return self::encapsulateOther($data, $pubkey, $alg);
352 * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
354 * @param string $data
355 * @param string $pubkey The public key.
356 * @param string $alg The algorithm used for encryption.
361 private static function encapsulateOther($data, $pubkey, $alg)
364 Logger::log('no key. data: '.$data);
366 $fn = 'encrypt' . strtoupper($alg);
367 if (method_exists(__CLASS__, $fn)) {
368 $result = ['encrypted' => true];
369 $key = random_bytes(256);
370 $iv = random_bytes(256);
371 $result['data'] = Strings::base64UrlEncode(self::$fn($data, $key, $iv), true);
373 // log the offending call so we can track it down
374 if (!openssl_public_encrypt($key, $k, $pubkey)) {
375 $x = debug_backtrace();
376 Logger::log('RSA failed. ' . print_r($x[0], true));
379 $result['alg'] = $alg;
380 $result['key'] = Strings::base64UrlEncode($k, true);
381 openssl_public_encrypt($iv, $i, $pubkey);
382 $result['iv'] = Strings::base64UrlEncode($i, true);
386 $x = ['data' => $data, 'pubkey' => $pubkey, 'alg' => $alg, 'result' => $data];
387 Hook::callAll('other_encapsulate', $x);
395 * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
397 * @param string $data
398 * @param string $pubkey
403 private static function encapsulateAes($data, $pubkey)
406 Logger::log('aes_encapsulate: no key. data: ' . $data);
409 $key = random_bytes(32);
410 $iv = random_bytes(16);
411 $result = ['encrypted' => true];
412 $result['data'] = Strings::base64UrlEncode(self::encryptAES256CBC($data, $key, $iv), true);
414 // log the offending call so we can track it down
415 if (!openssl_public_encrypt($key, $k, $pubkey)) {
416 $x = debug_backtrace();
417 Logger::log('aes_encapsulate: RSA failed. ' . print_r($x[0], true));
420 $result['alg'] = 'aes256cbc';
421 $result['key'] = Strings::base64UrlEncode($k, true);
422 openssl_public_encrypt($iv, $i, $pubkey);
423 $result['iv'] = Strings::base64UrlEncode($i, true);
430 * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
432 * @param array $data ['iv' => $iv, 'key' => $key, 'alg' => $alg, 'data' => $data]
433 * @param string $prvkey The private key used for decryption.
435 * @return string|boolean The decrypted string or false on failure.
438 public static function unencapsulate(array $data, $prvkey)
444 $alg = ((array_key_exists('alg', $data)) ? $data['alg'] : 'aes256cbc');
445 if ($alg === 'aes256cbc') {
446 return self::encapsulateAes($data['data'], $prvkey);
448 return self::encapsulateOther($data['data'], $prvkey, $alg);
453 * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
456 * @param string $prvkey The private key used for decryption.
459 * @return string|boolean The decrypted string or false on failure.
460 * @throws \Friendica\Network\HTTPException\InternalServerErrorException
462 private static function unencapsulateOther(array $data, $prvkey, $alg)
464 $fn = 'decrypt' . strtoupper($alg);
466 if (method_exists(__CLASS__, $fn)) {
467 openssl_private_decrypt(Strings::base64UrlDecode($data['key']), $k, $prvkey);
468 openssl_private_decrypt(Strings::base64UrlDecode($data['iv']), $i, $prvkey);
470 return self::$fn(Strings::base64UrlDecode($data['data']), $k, $i);
472 $x = ['data' => $data, 'prvkey' => $prvkey, 'alg' => $alg, 'result' => $data];
473 Hook::callAll('other_unencapsulate', $x);
481 * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
484 * @param string $prvkey The private key used for decryption.
486 * @return string|boolean The decrypted string or false on failure.
489 private static function unencapsulateAes($data, $prvkey)
491 openssl_private_decrypt(Strings::base64UrlDecode($data['key']), $k, $prvkey);
492 openssl_private_decrypt(Strings::base64UrlDecode($data['iv']), $i, $prvkey);
494 return self::decryptAES256CBC(Strings::base64UrlDecode($data['data']), $k, $i);
499 * Creates cryptographic secure random digits
501 * @param string $digits The count of digits
502 * @return int The random Digits
504 * @throws \Exception In case 'random_int' isn't usable
506 public static function randomDigits($digits)
510 // generating cryptographically secure pseudo-random integers
511 for ($i = 0; $i < $digits; $i++) {
512 $rn .= random_int(0, 9);