PKCS8.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609
  1. <?php
  2. /**
  3. * PKCS#8 Formatted Key Handler
  4. *
  5. * PHP version 5
  6. *
  7. * Used by PHP's openssl_public_encrypt() and openssl's rsautl (when -pubin is set)
  8. *
  9. * Processes keys with the following headers:
  10. *
  11. * -----BEGIN ENCRYPTED PRIVATE KEY-----
  12. * -----BEGIN PRIVATE KEY-----
  13. * -----BEGIN PUBLIC KEY-----
  14. *
  15. * Analogous to ssh-keygen's pkcs8 format (as specified by -m). Although PKCS8
  16. * is specific to private keys it's basically creating a DER-encoded wrapper
  17. * for keys. This just extends that same concept to public keys (much like ssh-keygen)
  18. *
  19. * @category Crypt
  20. * @package Common
  21. * @author Jim Wigginton <terrafrost@php.net>
  22. * @copyright 2015 Jim Wigginton
  23. * @license http://www.opensource.org/licenses/mit-license.html MIT License
  24. * @link http://phpseclib.sourceforge.net
  25. */
  26. namespace phpseclib\Crypt\Common\Keys;
  27. use ParagonIE\ConstantTime\Base64;
  28. use phpseclib\Crypt\DES;
  29. use phpseclib\Crypt\RC2;
  30. use phpseclib\Crypt\RC4;
  31. use phpseclib\Crypt\AES;
  32. use phpseclib\Crypt\TripleDES;
  33. use phpseclib\Crypt\Common\BlockCipher;
  34. use phpseclib\Crypt\Random;
  35. use phpseclib\Math\BigInteger;
  36. use phpseclib\File\ASN1;
  37. use phpseclib\File\ASN1\Maps;
  38. use phpseclib\Exception\UnsupportedAlgorithmException;
  39. /**
  40. * PKCS#8 Formatted Key Handler
  41. *
  42. * @package Common
  43. * @author Jim Wigginton <terrafrost@php.net>
  44. * @access public
  45. */
  46. abstract class PKCS8 extends PKCS
  47. {
  48. /**
  49. * Default encryption algorithm
  50. *
  51. * @var string
  52. * @access private
  53. */
  54. private static $defaultEncryptionAlgorithm = 'id-PBES2';
  55. /**
  56. * Default encryption scheme
  57. *
  58. * Only used when defaultEncryptionAlgorithm is id-PBES2
  59. *
  60. * @var string
  61. * @access private
  62. */
  63. private static $defaultEncryptionScheme = 'aes128-CBC-PAD';
  64. /**
  65. * Default PRF
  66. *
  67. * Only used when defaultEncryptionAlgorithm is id-PBES2
  68. *
  69. * @var string
  70. * @access private
  71. */
  72. private static $defaultPRF = 'id-hmacWithSHA256';
  73. /**
  74. * Default Iteration Count
  75. *
  76. * @var int
  77. * @access private
  78. */
  79. private static $defaultIterationCount = 2048;
  80. /**
  81. * OIDs loaded
  82. *
  83. * @var bool
  84. * @access private
  85. */
  86. private static $oidsLoaded = false;
  87. /**
  88. * Sets the default encryption algorithm
  89. *
  90. * @access public
  91. * @param string $algo
  92. */
  93. public static function setEncryptionAlgorithm($algo)
  94. {
  95. self::$defaultEncryptionAlgorithm = $algo;
  96. }
  97. /**
  98. * Sets the default encryption algorithm for PBES2
  99. *
  100. * @access public
  101. * @param string $algo
  102. */
  103. public static function setEncryptionScheme($algo)
  104. {
  105. self::$defaultEncryptionScheme = $algo;
  106. }
  107. /**
  108. * Sets the iteration count
  109. *
  110. * @access public
  111. * @param int $count
  112. */
  113. public static function setIterationCount($count)
  114. {
  115. self::$defaultIterationCount = $count;
  116. }
  117. /**
  118. * Sets the PRF for PBES2
  119. *
  120. * @access public
  121. * @param string $algo
  122. */
  123. public static function setPRF($algo)
  124. {
  125. self::$defaultPRF = $algo;
  126. }
  127. /**
  128. * Returns a SymmetricKey object based on a PBES1 $algo
  129. *
  130. * @access public
  131. * @param string $algo
  132. */
  133. private static function getPBES1EncryptionObject($algo)
  134. {
  135. $algo = preg_match('#^pbeWith(?:MD2|MD5|SHA1|SHA)And(.*?)-CBC$#', $algo, $matches) ?
  136. $matches[1] :
  137. substr($algo, 13); // strlen('pbeWithSHAAnd') == 13
  138. switch ($algo) {
  139. case 'DES':
  140. $cipher = new DES(BlockCipher::MODE_CBC);
  141. break;
  142. case 'RC2':
  143. $cipher = new RC2(BlockCipher::MODE_CBC);
  144. break;
  145. case '3-KeyTripleDES':
  146. $cipher = new TripleDES(BlockCipher::MODE_CBC);
  147. break;
  148. case '2-KeyTripleDES':
  149. $cipher = new TripleDES(BlockCipher::MODE_CBC);
  150. $cipher->setKeyLength(128);
  151. break;
  152. case '128BitRC2':
  153. $cipher = new RC2(BlockCipher::MODE_CBC);
  154. $cipher->setKeyLength(128);
  155. break;
  156. case '40BitRC2':
  157. $cipher = new RC2(BlockCipher::MODE_CBC);
  158. $cipher->setKeyLength(40);
  159. break;
  160. case '128BitRC4':
  161. $cipher = new RC4();
  162. $cipher->setKeyLength(128);
  163. break;
  164. case '40BitRC4':
  165. $cipher = new RC4();
  166. $cipher->setKeyLength(40);
  167. break;
  168. default:
  169. throw new UnsupportedAlgorithmException("$algo is not a supported algorithm");
  170. }
  171. return $cipher;
  172. }
  173. /**
  174. * Returns a hash based on a PBES1 $algo
  175. *
  176. * @access public
  177. * @param string $algo
  178. */
  179. private static function getPBES1Hash($algo)
  180. {
  181. if (preg_match('#^pbeWith(MD2|MD5|SHA1|SHA)And.*?-CBC$#', $algo, $matches)) {
  182. return $matches[1] == 'SHA' ? 'sha1' : $matches[1];
  183. }
  184. return 'sha1';
  185. }
  186. /**
  187. * Returns a KDF baesd on a PBES1 $algo
  188. *
  189. * @access public
  190. * @param string $algo
  191. */
  192. private static function getPBES1KDF($algo)
  193. {
  194. switch ($algo) {
  195. case 'pbeWithMD2AndDES-CBC':
  196. case 'pbeWithMD2AndRC2-CBC':
  197. case 'pbeWithMD5AndDES-CBC':
  198. case 'pbeWithMD5AndRC2-CBC':
  199. case 'pbeWithSHA1AndDES-CBC':
  200. case 'pbeWithSHA1AndRC2-CBC':
  201. return 'pbkdf1';
  202. }
  203. return 'pkcs12';
  204. }
  205. /**
  206. * Returns a SymmetricKey object baesd on a PBES2 $algo
  207. *
  208. * @access public
  209. * @param string $algo
  210. */
  211. private static function getPBES2EncryptionObject($algo)
  212. {
  213. switch ($algo) {
  214. case 'desCBC':
  215. $cipher = new TripleDES(BlockCipher::MODE_CBC);
  216. break;
  217. case 'des-EDE3-CBC':
  218. $cipher = new TripleDES(BlockCipher::MODE_CBC);
  219. break;
  220. case 'rc2CBC':
  221. $cipher = new RC2(BlockCipher::MODE_CBC);
  222. // in theory this can be changed
  223. $cipher->setKeyLength(128);
  224. break;
  225. case 'rc5-CBC-PAD':
  226. throw new UnsupportedAlgorithmException('rc5-CBC-PAD is not supported for PBES2 PKCS#8 keys');
  227. case 'aes128-CBC-PAD':
  228. case 'aes192-CBC-PAD':
  229. case 'aes256-CBC-PAD':
  230. $cipher = new AES(BlockCipher::MODE_CBC);
  231. $cipher->setKeyLength(substr($algo, 3, 3));
  232. break;
  233. default:
  234. throw new UnsupportedAlgorithmException("$algo is not supported");
  235. }
  236. return $cipher;
  237. }
  238. /**
  239. * Initialize static variables
  240. *
  241. * @access private
  242. */
  243. private static function initialize_static_variables()
  244. {
  245. if (!static::$childOIDsLoaded) {
  246. ASN1::loadOIDs([static::OID_VALUE => static::OID_NAME]);
  247. static::$childOIDsLoaded = true;
  248. }
  249. if (!self::$oidsLoaded) {
  250. // from https://tools.ietf.org/html/rfc2898
  251. ASN1::loadOIDs([
  252. // PBES1 encryption schemes
  253. '1.2.840.113549.1.5.1' => 'pbeWithMD2AndDES-CBC',
  254. '1.2.840.113549.1.5.4' => 'pbeWithMD2AndRC2-CBC',
  255. '1.2.840.113549.1.5.3' => 'pbeWithMD5AndDES-CBC',
  256. '1.2.840.113549.1.5.6' => 'pbeWithMD5AndRC2-CBC',
  257. '1.2.840.113549.1.5.10'=> 'pbeWithSHA1AndDES-CBC',
  258. '1.2.840.113549.1.5.11'=> 'pbeWithSHA1AndRC2-CBC',
  259. // from PKCS#12:
  260. // https://tools.ietf.org/html/rfc7292
  261. '1.2.840.113549.1.12.1.1' => 'pbeWithSHAAnd128BitRC4',
  262. '1.2.840.113549.1.12.1.2' => 'pbeWithSHAAnd40BitRC4',
  263. '1.2.840.113549.1.12.1.3' => 'pbeWithSHAAnd3-KeyTripleDES-CBC',
  264. '1.2.840.113549.1.12.1.4' => 'pbeWithSHAAnd2-KeyTripleDES-CBC',
  265. '1.2.840.113549.1.12.1.5' => 'pbeWithSHAAnd128BitRC2-CBC',
  266. '1.2.840.113549.1.12.1.6' => 'pbeWithSHAAnd40BitRC2-CBC',
  267. '1.2.840.113549.1.5.12' => 'id-PBKDF2',
  268. '1.2.840.113549.1.5.13' => 'id-PBES2',
  269. '1.2.840.113549.1.5.14' => 'id-PBMAC1',
  270. // from PKCS#5 v2.1:
  271. // http://www.rsa.com/rsalabs/pkcs/files/h11302-wp-pkcs5v2-1-password-based-cryptography-standard.pdf
  272. '1.2.840.113549.2.7' => 'id-hmacWithSHA1',
  273. '1.2.840.113549.2.8' => 'id-hmacWithSHA224',
  274. '1.2.840.113549.2.9' => 'id-hmacWithSHA256',
  275. '1.2.840.113549.2.10'=> 'id-hmacWithSHA384',
  276. '1.2.840.113549.2.11'=> 'id-hmacWithSHA512',
  277. '1.2.840.113549.2.12'=> 'id-hmacWithSHA512-224',
  278. '1.2.840.113549.2.13'=> 'id-hmacWithSHA512-256',
  279. '1.3.14.3.2.7' => 'desCBC',
  280. '1.2.840.113549.3.7' => 'des-EDE3-CBC',
  281. '1.2.840.113549.3.2' => 'rc2CBC',
  282. '1.2.840.113549.3.9' => 'rc5-CBC-PAD',
  283. '2.16.840.1.101.3.4.1.2' => 'aes128-CBC-PAD',
  284. '2.16.840.1.101.3.4.1.22'=> 'aes192-CBC-PAD',
  285. '2.16.840.1.101.3.4.1.42'=> 'aes256-CBC-PAD'
  286. ]);
  287. self::$oidsLoaded = true;
  288. }
  289. }
  290. /**
  291. * Break a public or private key down into its constituent components
  292. *
  293. * @access public
  294. * @param string $key
  295. * @param string $password optional
  296. * @return array
  297. */
  298. protected static function load($key, $password = '')
  299. {
  300. self::initialize_static_variables();
  301. if (!is_string($key)) {
  302. return false;
  303. }
  304. if (self::$format != self::MODE_DER) {
  305. $decoded = ASN1::extractBER($key);
  306. if ($decoded !== false) {
  307. $key = $decoded;
  308. } elseif (self::$format == self::MODE_PEM) {
  309. return false;
  310. }
  311. }
  312. $decoded = ASN1::decodeBER($key);
  313. if (empty($decoded)) {
  314. return false;
  315. }
  316. $meta = [];
  317. $decrypted = ASN1::asn1map($decoded[0], Maps\EncryptedPrivateKeyInfo::MAP);
  318. if (strlen($password) && is_array($decrypted)) {
  319. $algorithm = $decrypted['encryptionAlgorithm']['algorithm'];
  320. switch ($algorithm) {
  321. // PBES1
  322. case 'pbeWithMD2AndDES-CBC':
  323. case 'pbeWithMD2AndRC2-CBC':
  324. case 'pbeWithMD5AndDES-CBC':
  325. case 'pbeWithMD5AndRC2-CBC':
  326. case 'pbeWithSHA1AndDES-CBC':
  327. case 'pbeWithSHA1AndRC2-CBC':
  328. case 'pbeWithSHAAnd3-KeyTripleDES-CBC':
  329. case 'pbeWithSHAAnd2-KeyTripleDES-CBC':
  330. case 'pbeWithSHAAnd128BitRC2-CBC':
  331. case 'pbeWithSHAAnd40BitRC2-CBC':
  332. case 'pbeWithSHAAnd128BitRC4':
  333. case 'pbeWithSHAAnd40BitRC4':
  334. $cipher = self::getPBES1EncryptionObject($algorithm);
  335. $hash = self::getPBES1Hash($algorithm);
  336. $kdf = self::getPBES1KDF($algorithm);
  337. $meta['meta']['algorithm'] = $algorithm;
  338. $temp = ASN1::decodeBER($decrypted['encryptionAlgorithm']['parameters']);
  339. extract(ASN1::asn1map($temp[0], Maps\PBEParameter::MAP));
  340. $iterationCount = (int) $iterationCount->toString();
  341. $cipher->setPassword($password, $kdf, $hash, $salt, $iterationCount);
  342. $key = $cipher->decrypt($decrypted['encryptedData']);
  343. $decoded = ASN1::decodeBER($key);
  344. if (empty($decoded)) {
  345. return false;
  346. }
  347. break;
  348. case 'id-PBES2':
  349. $meta['meta']['algorithm'] = $algorithm;
  350. $temp = ASN1::decodeBER($decrypted['encryptionAlgorithm']['parameters']);
  351. $temp = ASN1::asn1map($temp[0], Maps\PBES2params::MAP);
  352. extract($temp);
  353. $cipher = self::getPBES2EncryptionObject($encryptionScheme['algorithm']);
  354. $meta['meta']['cipher'] = $encryptionScheme['algorithm'];
  355. $temp = ASN1::decodeBER($decrypted['encryptionAlgorithm']['parameters']);
  356. $temp = ASN1::asn1map($temp[0], Maps\PBES2params::MAP);
  357. extract($temp);
  358. if (!$cipher instanceof RC2) {
  359. $cipher->setIV($encryptionScheme['parameters']['octetString']);
  360. } else {
  361. $temp = ASN1::decodeBER($encryptionScheme['parameters']);
  362. extract(ASN1::asn1map($temp[0], Maps\RC2CBCParameter::MAP));
  363. $effectiveKeyLength = (int) $rc2ParametersVersion->toString();
  364. switch ($effectiveKeyLength) {
  365. case 160:
  366. $effectiveKeyLength = 40;
  367. break;
  368. case 120:
  369. $effectiveKeyLength = 64;
  370. break;
  371. case 58:
  372. $effectiveKeyLength = 128;
  373. break;
  374. //default: // should be >= 256
  375. }
  376. $cipher->setIV($iv);
  377. $cipher->setKeyLength($effectiveKeyLength);
  378. }
  379. $meta['meta']['keyDerivationFunc'] = $keyDerivationFunc['algorithm'];
  380. switch ($keyDerivationFunc['algorithm']) {
  381. case 'id-PBKDF2':
  382. $temp = ASN1::decodeBER($keyDerivationFunc['parameters']);
  383. $prf = ['algorithm' => 'id-hmacWithSHA1'];
  384. $params = ASN1::asn1map($temp[0], Maps\PBKDF2params::MAP);
  385. extract($params);
  386. $meta['meta']['prf'] = $prf['algorithm'];
  387. $hash = str_replace('-', '/', substr($prf['algorithm'], 11));
  388. $params = [
  389. $password,
  390. 'pbkdf2',
  391. $hash,
  392. $salt,
  393. (int) $iterationCount->toString()
  394. ];
  395. if (isset($keyLength)) {
  396. $params[] = (int) $keyLength->toString();
  397. }
  398. call_user_func_array([$cipher, 'setPassword'], $params);
  399. $key = $cipher->decrypt($decrypted['encryptedData']);
  400. $decoded = ASN1::decodeBER($key);
  401. if (empty($decoded)) {
  402. return false;
  403. }
  404. break;
  405. default:
  406. throw new UnsupportedAlgorithmException('Only PBKDF2 is supported for PBES2 PKCS#8 keys');
  407. }
  408. break;
  409. case 'id-PBMAC1':
  410. //$temp = ASN1::decodeBER($decrypted['encryptionAlgorithm']['parameters']);
  411. //$value = ASN1::asn1map($temp[0], Maps\PBMAC1params::MAP);
  412. // since i can't find any implementation that does PBMAC1 it is unsupported
  413. throw new UnsupportedAlgorithmException('Only PBES1 and PBES2 PKCS#8 keys are supported.');
  414. // at this point we'll assume that the key conforms to PublicKeyInfo
  415. }
  416. }
  417. $private = ASN1::asn1map($decoded[0], Maps\PrivateKeyInfo::MAP);
  418. if (is_array($private)) {
  419. return $private['privateKeyAlgorithm']['algorithm'] == static::OID_NAME ?
  420. $private + $meta :
  421. false;
  422. }
  423. // EncryptedPrivateKeyInfo and PublicKeyInfo have largely identical "signatures". the only difference
  424. // is that the former has an octet string and the later has a bit string. the first byte of a bit
  425. // string represents the number of bits in the last byte that are to be ignored but, currently,
  426. // bit strings wanting a non-zero amount of bits trimmed are not supported
  427. $public = ASN1::asn1map($decoded[0], Maps\PublicKeyInfo::MAP);
  428. if (is_array($public)) {
  429. if ($public['publicKey'][0] != "\0" || $public['publicKeyAlgorithm']['algorithm'] != static::OID_NAME) {
  430. return false;
  431. }
  432. $public['publicKey'] = substr($public['publicKey'], 1);
  433. return $public;
  434. }
  435. return false;
  436. }
  437. /**
  438. * Wrap a private key appropriately
  439. *
  440. * @access public
  441. * @param string $key
  442. * @param string $attr
  443. * @param mixed $params
  444. * @param string $password
  445. * @return string
  446. */
  447. protected static function wrapPrivateKey($key, $attr, $params, $password)
  448. {
  449. self::initialize_static_variables();
  450. $key = [
  451. 'version' => 'v1',
  452. 'privateKeyAlgorithm' => [
  453. 'algorithm' => static::OID_NAME,
  454. 'parameters' => $params
  455. ],
  456. 'privateKey' => $key
  457. ];
  458. if (!empty($attr)) {
  459. $key['attributes'] = $attr;
  460. }
  461. $key = ASN1::encodeDER($key, Maps\PrivateKeyInfo::MAP);
  462. if (!empty($password) && is_string($password)) {
  463. $salt = Random::string(8);
  464. $iterationCount = self::$defaultIterationCount;
  465. if (self::$defaultEncryptionAlgorithm == 'id-PBES2') {
  466. $crypto = self::getPBES2EncryptionObject(self::$defaultEncryptionScheme);
  467. $hash = str_replace('-', '/', substr(self::$defaultPRF, 11));
  468. $kdf = 'pbkdf2';
  469. $iv = Random::string($crypto->getBlockLength() >> 3);
  470. $PBKDF2params = [
  471. 'salt' => $salt,
  472. 'iterationCount' => $iterationCount,
  473. 'prf' => ['algorithm' => self::$defaultPRF, 'parameters' => null]
  474. ];
  475. $PBKDF2params = ASN1::encodeDER($PBKDF2params, Maps\PBKDF2params::MAP);
  476. if (!$crypto instanceof RC2) {
  477. $params = ['octetString' => $iv];
  478. } else {
  479. $params = [
  480. 'rc2ParametersVersion' => 58,
  481. 'iv' => $iv
  482. ];
  483. $params = ASN1::encodeDER($params, Maps\RC2CBCParameter::MAP);
  484. $params = new ASN1\Element($params);
  485. }
  486. $params = [
  487. 'keyDerivationFunc' => [
  488. 'algorithm' => 'id-PBKDF2',
  489. 'parameters' => new ASN1\Element($PBKDF2params)
  490. ],
  491. 'encryptionScheme' => [
  492. 'algorithm' => self::$defaultEncryptionScheme,
  493. 'parameters' => $params
  494. ]
  495. ];
  496. $params = ASN1::encodeDER($params, Maps\PBES2params::MAP);
  497. $crypto->setIV($iv);
  498. } else {
  499. $crypto = self::getPBES1EncryptionObject(self::$defaultEncryptionAlgorithm);
  500. $hash = self::getPBES1Hash(self::$defaultEncryptionAlgorithm);
  501. $kdf = self::getPBES1KDF(self::$defaultEncryptionAlgorithm);
  502. $params = [
  503. 'salt' => $salt,
  504. 'iterationCount' => $iterationCount
  505. ];
  506. $params = ASN1::encodeDER($params, Maps\PBEParameter::MAP);
  507. }
  508. $crypto->setPassword($password, $kdf, $hash, $salt, $iterationCount);
  509. $key = $crypto->encrypt($key);
  510. $key = [
  511. 'encryptionAlgorithm' => [
  512. 'algorithm' => self::$defaultEncryptionAlgorithm,
  513. 'parameters' => new ASN1\Element($params)
  514. ],
  515. 'encryptedData' => $key
  516. ];
  517. $key = ASN1::encodeDER($key, Maps\EncryptedPrivateKeyInfo::MAP);
  518. return "-----BEGIN ENCRYPTED PRIVATE KEY-----\r\n" .
  519. chunk_split(Base64::encode($key), 64) .
  520. "-----END ENCRYPTED PRIVATE KEY-----";
  521. }
  522. return "-----BEGIN PRIVATE KEY-----\r\n" .
  523. chunk_split(Base64::encode($key), 64) .
  524. "-----END PRIVATE KEY-----";
  525. }
  526. /**
  527. * Wrap a public key appropriately
  528. *
  529. * @access public
  530. * @param string $key
  531. * @param mixed $params
  532. * @return string
  533. */
  534. protected static function wrapPublicKey($key, $params)
  535. {
  536. self::initialize_static_variables();
  537. $key = [
  538. 'publicKeyAlgorithm' => [
  539. 'algorithm' => static::OID_NAME,
  540. 'parameters' => $params
  541. ],
  542. 'publicKey' => "\0" . $key
  543. ];
  544. $key = ASN1::encodeDER($key, Maps\PublicKeyInfo::MAP);
  545. return "-----BEGIN PUBLIC KEY-----\r\n" .
  546. chunk_split(Base64::encode($key), 64) .
  547. "-----END PUBLIC KEY-----";
  548. }
  549. }