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 * @param string $data
255 * @param string $key The key used for encryption.
256 * @param string $iv A non-NULL Initialization Vector.
258 * @return string|boolean Encrypted string or false on failure.
260 private static function encryptAES256CBC($data, $key, $iv)
262 return openssl_encrypt($data, 'aes-256-cbc', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0"));
266 * Decrypt a string with 'aes-256-cbc' cipher method.
268 * @param string $data
269 * @param string $key The key used for decryption.
270 * @param string $iv A non-NULL Initialization Vector.
272 * @return string|boolean Decrypted string or false on failure.
274 private static function decryptAES256CBC($data, $key, $iv)
276 return openssl_decrypt($data, 'aes-256-cbc', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0"));
280 * Encrypt a string with 'aes-256-ctr' cipher method.
282 * @param string $data
283 * @param string $key The key used for encryption.
284 * @param string $iv A non-NULL Initialization Vector.
286 * @return string|boolean Encrypted string or false on failure.
288 private static function encryptAES256CTR($data, $key, $iv)
290 $key = substr($key, 0, 32);
291 $iv = substr($iv, 0, 16);
292 return openssl_encrypt($data, 'aes-256-ctr', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0"));
296 * Decrypt a string with 'aes-256-cbc' cipher method.
298 * @param string $data
299 * @param string $key The key used for decryption.
300 * @param string $iv A non-NULL Initialization Vector.
302 * @return string|boolean Decrypted string or false on failure.
304 private static function decryptAES256CTR($data, $key, $iv)
306 $key = substr($key, 0, 32);
307 $iv = substr($iv, 0, 16);
308 return openssl_decrypt($data, 'aes-256-ctr', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0"));
313 * @param string $data
314 * @param string $pubkey The public key.
315 * @param string $alg The algorithm used for encryption.
319 public static function encapsulate($data, $pubkey, $alg = 'aes256cbc')
321 if ($alg === 'aes256cbc') {
322 return self::encapsulateAes($data, $pubkey);
324 return self::encapsulateOther($data, $pubkey, $alg);
330 * @param type $pubkey The public key.
331 * @param type $alg The algorithm used for encryption.
335 private static function encapsulateOther($data, $pubkey, $alg)
338 logger('no key. data: '.$data);
340 $fn = 'encrypt' . strtoupper($alg);
341 if (method_exists(__CLASS__, $fn)) {
342 // A bit hesitant to use openssl_random_pseudo_bytes() as we know
343 // it has been historically targeted by US agencies for 'weakening'.
344 // It is still arguably better than trying to come up with an
345 // alternative cryptographically secure random generator.
346 // There is little point in using the optional second arg to flag the
347 // assurance of security since it is meaningless if the source algorithms
348 // have been compromised. Also none of this matters if RSA has been
349 // compromised by state actors and evidence is mounting that this has
351 $result = ['encrypted' => true];
352 $key = openssl_random_pseudo_bytes(256);
353 $iv = openssl_random_pseudo_bytes(256);
354 $result['data'] = base64url_encode(self::$fn($data, $key, $iv), true);
356 // log the offending call so we can track it down
357 if (!openssl_public_encrypt($key, $k, $pubkey)) {
358 $x = debug_backtrace();
359 logger('RSA failed. ' . print_r($x[0], true));
362 $result['alg'] = $alg;
363 $result['key'] = base64url_encode($k, true);
364 openssl_public_encrypt($iv, $i, $pubkey);
365 $result['iv'] = base64url_encode($i, true);
369 $x = ['data' => $data, 'pubkey' => $pubkey, 'alg' => $alg, 'result' => $data];
370 Addon::callHooks('other_encapsulate', $x);
378 * @param string $data
379 * @param string $pubkey
383 private static function encapsulateAes($data, $pubkey)
386 logger('aes_encapsulate: no key. data: ' . $data);
389 $key = openssl_random_pseudo_bytes(32);
390 $iv = openssl_random_pseudo_bytes(16);
391 $result = ['encrypted' => true];
392 $result['data'] = base64url_encode(AES256CBC_encrypt($data, $key, $iv), true);
394 // log the offending call so we can track it down
395 if (!openssl_public_encrypt($key, $k, $pubkey)) {
396 $x = debug_backtrace();
397 logger('aes_encapsulate: RSA failed. ' . print_r($x[0], true));
400 $result['alg'] = 'aes256cbc';
401 $result['key'] = base64url_encode($k, true);
402 openssl_public_encrypt($iv, $i, $pubkey);
403 $result['iv'] = base64url_encode($i, true);
410 * @param string $data
411 * @param string $prvkey The private key used for decryption.
413 * @return string|boolean The decrypted string or false on failure.
415 public static function unencapsulate($data, $prvkey)
421 $alg = ((array_key_exists('alg', $data)) ? $data['alg'] : 'aes256cbc');
422 if ($alg === 'aes256cbc') {
423 return self::encapsulateAes($data, $prvkey);
425 return self::encapsulateOther($data, $prvkey, $alg);
430 * @param string $data
431 * @param string $prvkey The private key used for decryption.
434 * @return string|boolean The decrypted string or false on failure.
436 private static function unencapsulateOther($data, $prvkey, $alg)
438 $fn = 'decrypt' . strtoupper($alg);
440 if (method_exists(__CLASS__, $fn)) {
441 openssl_private_decrypt(base64url_decode($data['key']), $k, $prvkey);
442 openssl_private_decrypt(base64url_decode($data['iv']), $i, $prvkey);
444 return self::$fn(base64url_decode($data['data']), $k, $i);
446 $x = ['data' => $data, 'prvkey' => $prvkey, 'alg' => $alg, 'result' => $data];
447 Addon::callHooks('other_unencapsulate', $x);
456 * @param string $prvkey The private key used for decryption.
458 * @return string|boolean The decrypted string or false on failure.
460 private static function unencapsulateAes($data, $prvkey)
462 openssl_private_decrypt(base64url_decode($data['key']), $k, $prvkey);
463 openssl_private_decrypt(base64url_decode($data['iv']), $i, $prvkey);
465 return self::decryptAES256CBC(base64url_decode($data['data']), $k, $i);