3 * @file src/Util/Crypto.php
5 namespace Friendica\Util;
7 use Friendica\Core\Addon;
8 use Friendica\Core\Config;
17 // supported algorithms are 'sha256', 'sha1'
19 * @param string $data data
20 * @param string $key key
21 * @param string $alg algorithm
24 public static function rsaSign($data, $key, $alg = 'sha256')
26 openssl_sign($data, $sig, $key, (($alg == 'sha1') ? OPENSSL_ALGO_SHA1 : $alg));
31 * @param string $data data
32 * @param string $sig signature
33 * @param string $key key
34 * @param string $alg algorithm
37 public static function rsaVerify($data, $sig, $key, $alg = 'sha256')
39 return openssl_verify($data, $sig, $key, (($alg == 'sha1') ? OPENSSL_ALGO_SHA1 : $alg));
43 * @param string $Der der formatted string
44 * @param string $Private key type optional, default false
47 private static function DerToPem($Der, $Private = false)
50 $Der = base64_encode($Der);
52 $lines = str_split($Der, 65);
53 $body = implode("\n", $lines);
55 $title = $Private ? 'RSA PRIVATE KEY' : 'PUBLIC KEY';
57 $result = "-----BEGIN {$title}-----\n";
58 $result .= $body . "\n";
59 $result .= "-----END {$title}-----\n";
65 * @param string $Der der formatted string
68 private static function DerToRsa($Der)
71 $Der = base64_encode($Der);
73 $lines = str_split($Der, 64);
74 $body = implode("\n", $lines);
76 $title = 'RSA PUBLIC KEY';
78 $result = "-----BEGIN {$title}-----\n";
79 $result .= $body . "\n";
80 $result .= "-----END {$title}-----\n";
86 * @param string $Modulus modulo
87 * @param string $PublicExponent exponent
90 private static function pkcs8Encode($Modulus, $PublicExponent)
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);
101 $bitStringValue = $keySequence->Encode();
102 $bitStringValue = chr(0x00) . $bitStringValue; //Add unused bits byte
103 $bitString = new ASNValue(ASNValue::TAG_BITSTRING);
104 $bitString->Value = $bitStringValue;
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();
115 * @param string $Modulus modulo
116 * @param string $PublicExponent exponent
119 private static function pkcs1Encode($Modulus, $PublicExponent)
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);
130 $bitStringValue = $keySequence->Encode();
131 return $bitStringValue;
135 * @param string $m modulo
136 * @param string $e exponent
139 public static function meToPem($m, $e)
141 $der = self::pkcs8Encode($m, $e);
142 $key = self::DerToPem($der, false);
147 * @param string $key key
148 * @param string $m modulo reference
149 * @param object $e exponent reference
152 private static function pubRsaToMe($key, &$m, &$e)
154 $lines = explode("\n", $key);
156 unset($lines[count($lines)]);
157 $x = base64_decode(implode('', $lines));
159 $r = ASN_BASE::parseASNString($x);
161 $m = base64url_decode($r[0]->asnData[0]->asnData);
162 $e = base64url_decode($r[0]->asnData[1]->asnData);
166 * @param string $key key
169 public static function rsaToPem($key)
171 self::pubRsaToMe($key, $m, $e);
172 return self::meToPem($m, $e);
176 * @param string $key key
179 private static function pemToRsa($key)
181 self::pemToMe($key, $m, $e);
182 return self::meToRsa($m, $e);
186 * @param string $key key
187 * @param string $m modulo reference
188 * @param string $e exponent reference
191 public static function pemToMe($key, &$m, &$e)
193 $lines = explode("\n", $key);
195 unset($lines[count($lines)]);
196 $x = base64_decode(implode('', $lines));
198 $r = ASN_BASE::parseASNString($x);
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);
205 * @param string $m modulo
206 * @param string $e exponent
209 private static function meToRsa($m, $e)
211 $der = self::pkcs1Encode($m, $e);
212 $key = self::DerToRsa($der);
217 * @param integer $bits number of bits
220 public static function newKeypair($bits)
223 'digest_alg' => 'sha1',
224 'private_key_bits' => $bits,
225 'encrypt_key' => false
228 $conf = Config::get('system', 'openssl_conf_file');
230 $openssl_options['config'] = $conf;
232 $result = openssl_pkey_new($openssl_options);
234 if (empty($result)) {
235 logger('new_keypair: failed');
240 $response = ['prvkey' => '', 'pubkey' => ''];
242 openssl_pkey_export($result, $response['prvkey']);
245 $pkey = openssl_pkey_get_details($result);
246 $response['pubkey'] = $pkey["key"];
252 * Encrypt a string with 'aes-256-cbc' cipher method.
254 * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
256 * @param string $data
257 * @param string $key The key used for encryption.
258 * @param string $iv A non-NULL Initialization Vector.
260 * @return string|boolean Encrypted string or false on failure.
262 private static function encryptAES256CBC($data, $key, $iv)
264 return openssl_encrypt($data, 'aes-256-cbc', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0"));
268 * Decrypt a string with 'aes-256-cbc' cipher method.
270 * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
272 * @param string $data
273 * @param string $key The key used for decryption.
274 * @param string $iv A non-NULL Initialization Vector.
276 * @return string|boolean Decrypted string or false on failure.
278 private static function decryptAES256CBC($data, $key, $iv)
280 return openssl_decrypt($data, 'aes-256-cbc', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0"));
284 * Encrypt a string with 'aes-256-ctr' cipher method.
286 * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
288 * @param string $data
289 * @param string $key The key used for encryption.
290 * @param string $iv A non-NULL Initialization Vector.
292 * @return string|boolean Encrypted string or false on failure.
294 private static function encryptAES256CTR($data, $key, $iv)
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"));
302 * Decrypt a string with 'aes-256-ctr' cipher method.
304 * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
306 * @param string $data
307 * @param string $key The key used for decryption.
308 * @param string $iv A non-NULL Initialization Vector.
310 * @return string|boolean Decrypted string or false on failure.
312 private static function decryptAES256CTR($data, $key, $iv)
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"));
321 * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
323 * @param string $data
324 * @param string $pubkey The public key.
325 * @param string $alg The algorithm used for encryption.
329 public static function encapsulate($data, $pubkey, $alg = 'aes256cbc')
331 if ($alg === 'aes256cbc') {
332 return self::encapsulateAes($data, $pubkey);
334 return self::encapsulateOther($data, $pubkey, $alg);
339 * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
342 * @param type $pubkey The public key.
343 * @param type $alg The algorithm used for encryption.
347 private static function encapsulateOther($data, $pubkey, $alg)
350 logger('no key. data: '.$data);
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);
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));
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);
372 $x = ['data' => $data, 'pubkey' => $pubkey, 'alg' => $alg, 'result' => $data];
373 Addon::callHooks('other_encapsulate', $x);
381 * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
383 * @param string $data
384 * @param string $pubkey
388 private static function encapsulateAes($data, $pubkey)
391 logger('aes_encapsulate: no key. data: ' . $data);
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);
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));
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);
415 * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
417 * @param string $data
418 * @param string $prvkey The private key used for decryption.
420 * @return string|boolean The decrypted string or false on failure.
422 public static function unencapsulate($data, $prvkey)
428 $alg = ((array_key_exists('alg', $data)) ? $data['alg'] : 'aes256cbc');
429 if ($alg === 'aes256cbc') {
430 return self::encapsulateAes($data, $prvkey);
432 return self::encapsulateOther($data, $prvkey, $alg);
437 * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
439 * @param string $data
440 * @param string $prvkey The private key used for decryption.
443 * @return string|boolean The decrypted string or false on failure.
445 private static function unencapsulateOther($data, $prvkey, $alg)
447 $fn = 'decrypt' . strtoupper($alg);
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);
453 return self::$fn(base64url_decode($data['data']), $k, $i);
455 $x = ['data' => $data, 'prvkey' => $prvkey, 'alg' => $alg, 'result' => $data];
456 Addon::callHooks('other_unencapsulate', $x);
464 * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
467 * @param string $prvkey The private key used for decryption.
469 * @return string|boolean The decrypted string or false on failure.
471 private static function unencapsulateAes($data, $prvkey)
473 openssl_private_decrypt(base64url_decode($data['key']), $k, $prvkey);
474 openssl_private_decrypt(base64url_decode($data['iv']), $i, $prvkey);
476 return self::decryptAES256CBC(base64url_decode($data['data']), $k, $i);