3 * @copyright Copyright (C) 2010-2023, the Friendica project
5 * @license GNU AGPL version 3 or any later version
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.
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.
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/>.
22 namespace Friendica\Util;
24 use Friendica\Core\Hook;
25 use Friendica\Core\Logger;
26 use Friendica\Core\System;
28 use phpseclib3\Crypt\PublicKeyLoader;
35 // supported algorithms are 'sha256', 'sha1'
37 * @param string $data data
38 * @param string $key key
39 * @param string $alg algorithm
42 public static function rsaSign($data, $key, $alg = 'sha256')
45 Logger::warning('Empty key parameter', ['callstack' => System::callstack()]);
47 openssl_sign($data, $sig, $key, (($alg == 'sha1') ? OPENSSL_ALGO_SHA1 : $alg));
52 * @param string $data data
53 * @param string $sig signature
54 * @param string $key key
55 * @param string $alg algorithm
58 public static function rsaVerify($data, $sig, $key, $alg = 'sha256')
61 Logger::warning('Empty key parameter', ['callstack' => System::callstack()]);
63 return openssl_verify($data, $sig, $key, (($alg == 'sha1') ? OPENSSL_ALGO_SHA1 : $alg));
67 * Transform RSA public keys to standard PEM output
69 * @param string $key A RSA public key
71 * @return string The PEM output of this key
73 public static function rsaToPem(string $key)
75 return (string)PublicKeyLoader::load($key);
79 * @param integer $bits number of bits
81 * @throws \Friendica\Network\HTTPException\InternalServerErrorException
83 public static function newKeypair($bits)
86 'digest_alg' => 'sha1',
87 'private_key_bits' => $bits,
88 'encrypt_key' => false
91 $conf = DI::config()->get('system', 'openssl_conf_file');
93 $openssl_options['config'] = $conf;
95 $result = openssl_pkey_new($openssl_options);
98 Logger::notice('new_keypair: failed');
103 $response = ['prvkey' => '', 'pubkey' => ''];
105 openssl_pkey_export($result, $response['prvkey']);
108 $pkey = openssl_pkey_get_details($result);
109 $response['pubkey'] = $pkey["key"];
115 * Encrypt a string with 'aes-256-cbc' cipher method.
117 * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
119 * @param string $data
120 * @param string $key The key used for encryption.
121 * @param string $iv A non-NULL Initialization Vector.
123 * @return string|boolean Encrypted string or false on failure.
125 private static function encryptAES256CBC($data, $key, $iv)
127 return openssl_encrypt($data, 'aes-256-cbc', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0"));
131 * Decrypt a string with 'aes-256-cbc' cipher method.
133 * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
135 * @param string $data
136 * @param string $key The key used for decryption.
137 * @param string $iv A non-NULL Initialization Vector.
139 * @return string|boolean Decrypted string or false on failure.
141 private static function decryptAES256CBC($data, $key, $iv)
143 return openssl_decrypt($data, 'aes-256-cbc', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0"));
148 * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
150 * @param string $data
151 * @param string $pubkey The public key.
152 * @param string $alg The algorithm used for encryption.
157 public static function encapsulate($data, $pubkey, $alg = 'aes256cbc')
159 if ($alg === 'aes256cbc') {
160 return self::encapsulateAes($data, $pubkey);
162 return self::encapsulateOther($data, $pubkey, $alg);
167 * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
169 * @param string $data
170 * @param string $pubkey The public key.
171 * @param string $alg The algorithm used for encryption.
176 private static function encapsulateOther($data, $pubkey, $alg)
179 Logger::notice('no key. data: '.$data);
181 $fn = 'encrypt' . strtoupper($alg);
182 if (method_exists(__CLASS__, $fn)) {
183 $result = ['encrypted' => true];
184 $key = random_bytes(256);
185 $iv = random_bytes(256);
186 $result['data'] = Strings::base64UrlEncode(self::$fn($data, $key, $iv), true);
188 // log the offending call so we can track it down
189 if (!openssl_public_encrypt($key, $k, $pubkey)) {
190 $x = debug_backtrace();
191 Logger::notice('RSA failed', ['trace' => $x[0]]);
194 $result['alg'] = $alg;
195 $result['key'] = Strings::base64UrlEncode($k, true);
196 openssl_public_encrypt($iv, $i, $pubkey);
197 $result['iv'] = Strings::base64UrlEncode($i, true);
201 $x = ['data' => $data, 'pubkey' => $pubkey, 'alg' => $alg, 'result' => $data];
202 Hook::callAll('other_encapsulate', $x);
210 * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
212 * @param string $data
213 * @param string $pubkey
218 private static function encapsulateAes($data, $pubkey)
221 Logger::notice('aes_encapsulate: no key. data: ' . $data);
224 $key = random_bytes(32);
225 $iv = random_bytes(16);
226 $result = ['encrypted' => true];
227 $result['data'] = Strings::base64UrlEncode(self::encryptAES256CBC($data, $key, $iv), true);
229 // log the offending call so we can track it down
230 if (!openssl_public_encrypt($key, $k, $pubkey)) {
231 $x = debug_backtrace();
232 Logger::notice('aes_encapsulate: RSA failed.', ['data' => $x[0]]);
235 $result['alg'] = 'aes256cbc';
236 $result['key'] = Strings::base64UrlEncode($k, true);
237 openssl_public_encrypt($iv, $i, $pubkey);
238 $result['iv'] = Strings::base64UrlEncode($i, true);
245 * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
247 * @param array $data ['iv' => $iv, 'key' => $key, 'alg' => $alg, 'data' => $data]
248 * @param string $prvkey The private key used for decryption.
250 * @return string|boolean The decrypted string or false on failure.
253 public static function unencapsulate(array $data, $prvkey)
259 $alg = $data['alg'] ?? 'aes256cbc';
260 if ($alg === 'aes256cbc') {
261 return self::unencapsulateAes($data['data'], $prvkey);
264 return self::unencapsulateOther($data, $prvkey, $alg);
269 * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
272 * @param string $prvkey The private key used for decryption.
275 * @return string|boolean The decrypted string or false on failure.
276 * @throws \Friendica\Network\HTTPException\InternalServerErrorException
278 private static function unencapsulateOther(array $data, $prvkey, $alg)
280 $fn = 'decrypt' . strtoupper($alg);
282 if (method_exists(__CLASS__, $fn)) {
283 openssl_private_decrypt(Strings::base64UrlDecode($data['key']), $k, $prvkey);
284 openssl_private_decrypt(Strings::base64UrlDecode($data['iv']), $i, $prvkey);
286 return self::$fn(Strings::base64UrlDecode($data['data']), $k, $i);
288 $x = ['data' => $data, 'prvkey' => $prvkey, 'alg' => $alg, 'result' => $data];
289 Hook::callAll('other_unencapsulate', $x);
297 * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
300 * @param string $prvkey The private key used for decryption.
302 * @return string|boolean The decrypted string or false on failure.
305 private static function unencapsulateAes($data, $prvkey)
307 openssl_private_decrypt(Strings::base64UrlDecode($data['key']), $k, $prvkey);
308 openssl_private_decrypt(Strings::base64UrlDecode($data['iv']), $i, $prvkey);
310 return self::decryptAES256CBC(Strings::base64UrlDecode($data['data']), $k, $i);
315 * Creates cryptographic secure random digits
317 * @param string $digits The count of digits
318 * @return int The random Digits
320 * @throws \Exception In case 'random_int' isn't usable
322 public static function randomDigits($digits)
326 // generating cryptographically secure pseudo-random integers
327 for ($i = 0; $i < $digits; $i++) {
328 $rn .= random_int(0, 9);