[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/ -> PKCS8.php (source)

   1  <?php
   2  
   3  /**
   4   * PKCS#8 Formatted Key Handler
   5   *
   6   * PHP version 5
   7   *
   8   * Used by PHP's openssl_public_encrypt() and openssl's rsautl (when -pubin is set)
   9   *
  10   * Processes keys with the following headers:
  11   *
  12   * -----BEGIN ENCRYPTED PRIVATE KEY-----
  13   * -----BEGIN PRIVATE KEY-----
  14   * -----BEGIN PUBLIC KEY-----
  15   *
  16   * Analogous to ssh-keygen's pkcs8 format (as specified by -m). Although PKCS8
  17   * is specific to private keys it's basically creating a DER-encoded wrapper
  18   * for keys. This just extends that same concept to public keys (much like ssh-keygen)
  19   *
  20   * @author    Jim Wigginton <terrafrost@php.net>
  21   * @copyright 2015 Jim Wigginton
  22   * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
  23   * @link      http://phpseclib.sourceforge.net
  24   */
  25  
  26  namespace phpseclib3\Crypt\Common\Formats\Keys;
  27  
  28  use phpseclib3\Common\Functions\Strings;
  29  use phpseclib3\Crypt\AES;
  30  use phpseclib3\Crypt\DES;
  31  use phpseclib3\Crypt\Random;
  32  use phpseclib3\Crypt\RC2;
  33  use phpseclib3\Crypt\RC4;
  34  use phpseclib3\Crypt\TripleDES;
  35  use phpseclib3\Exception\InsufficientSetupException;
  36  use phpseclib3\Exception\UnsupportedAlgorithmException;
  37  use phpseclib3\File\ASN1;
  38  use phpseclib3\File\ASN1\Maps;
  39  
  40  /**
  41   * PKCS#8 Formatted Key Handler
  42   *
  43   * @author  Jim Wigginton <terrafrost@php.net>
  44   */
  45  abstract class PKCS8 extends PKCS
  46  {
  47      /**
  48       * Default encryption algorithm
  49       *
  50       * @var string
  51       */
  52      private static $defaultEncryptionAlgorithm = 'id-PBES2';
  53  
  54      /**
  55       * Default encryption scheme
  56       *
  57       * Only used when defaultEncryptionAlgorithm is id-PBES2
  58       *
  59       * @var string
  60       */
  61      private static $defaultEncryptionScheme = 'aes128-CBC-PAD';
  62  
  63      /**
  64       * Default PRF
  65       *
  66       * Only used when defaultEncryptionAlgorithm is id-PBES2
  67       *
  68       * @var string
  69       */
  70      private static $defaultPRF = 'id-hmacWithSHA256';
  71  
  72      /**
  73       * Default Iteration Count
  74       *
  75       * @var int
  76       */
  77      private static $defaultIterationCount = 2048;
  78  
  79      /**
  80       * OIDs loaded
  81       *
  82       * @var bool
  83       */
  84      private static $oidsLoaded = false;
  85  
  86      /**
  87       * Sets the default encryption algorithm
  88       *
  89       * @param string $algo
  90       */
  91      public static function setEncryptionAlgorithm($algo)
  92      {
  93          self::$defaultEncryptionAlgorithm = $algo;
  94      }
  95  
  96      /**
  97       * Sets the default encryption algorithm for PBES2
  98       *
  99       * @param string $algo
 100       */
 101      public static function setEncryptionScheme($algo)
 102      {
 103          self::$defaultEncryptionScheme = $algo;
 104      }
 105  
 106      /**
 107       * Sets the iteration count
 108       *
 109       * @param int $count
 110       */
 111      public static function setIterationCount($count)
 112      {
 113          self::$defaultIterationCount = $count;
 114      }
 115  
 116      /**
 117       * Sets the PRF for PBES2
 118       *
 119       * @param string $algo
 120       */
 121      public static function setPRF($algo)
 122      {
 123          self::$defaultPRF = $algo;
 124      }
 125  
 126      /**
 127       * Returns a SymmetricKey object based on a PBES1 $algo
 128       *
 129       * @return \phpseclib3\Crypt\Common\SymmetricKey
 130       * @param string $algo
 131       */
 132      private static function getPBES1EncryptionObject($algo)
 133      {
 134          $algo = preg_match('#^pbeWith(?:MD2|MD5|SHA1|SHA)And(.*?)-CBC$#', $algo, $matches) ?
 135              $matches[1] :
 136              substr($algo, 13); // strlen('pbeWithSHAAnd') == 13
 137  
 138          switch ($algo) {
 139              case 'DES':
 140                  $cipher = new DES('cbc');
 141                  break;
 142              case 'RC2':
 143                  $cipher = new RC2('cbc');
 144                  $cipher->setKeyLength(64);
 145                  break;
 146              case '3-KeyTripleDES':
 147                  $cipher = new TripleDES('cbc');
 148                  break;
 149              case '2-KeyTripleDES':
 150                  $cipher = new TripleDES('cbc');
 151                  $cipher->setKeyLength(128);
 152                  break;
 153              case '128BitRC2':
 154                  $cipher = new RC2('cbc');
 155                  $cipher->setKeyLength(128);
 156                  break;
 157              case '40BitRC2':
 158                  $cipher = new RC2('cbc');
 159                  $cipher->setKeyLength(40);
 160                  break;
 161              case '128BitRC4':
 162                  $cipher = new RC4();
 163                  $cipher->setKeyLength(128);
 164                  break;
 165              case '40BitRC4':
 166                  $cipher = new RC4();
 167                  $cipher->setKeyLength(40);
 168                  break;
 169              default:
 170                  throw new UnsupportedAlgorithmException("$algo is not a supported algorithm");
 171          }
 172  
 173          return $cipher;
 174      }
 175  
 176      /**
 177       * Returns a hash based on a PBES1 $algo
 178       *
 179       * @return string
 180       * @param string $algo
 181       */
 182      private static function getPBES1Hash($algo)
 183      {
 184          if (preg_match('#^pbeWith(MD2|MD5|SHA1|SHA)And.*?-CBC$#', $algo, $matches)) {
 185              return $matches[1] == 'SHA' ? 'sha1' : $matches[1];
 186          }
 187  
 188          return 'sha1';
 189      }
 190  
 191      /**
 192       * Returns a KDF baesd on a PBES1 $algo
 193       *
 194       * @return string
 195       * @param string $algo
 196       */
 197      private static function getPBES1KDF($algo)
 198      {
 199          switch ($algo) {
 200              case 'pbeWithMD2AndDES-CBC':
 201              case 'pbeWithMD2AndRC2-CBC':
 202              case 'pbeWithMD5AndDES-CBC':
 203              case 'pbeWithMD5AndRC2-CBC':
 204              case 'pbeWithSHA1AndDES-CBC':
 205              case 'pbeWithSHA1AndRC2-CBC':
 206                  return 'pbkdf1';
 207          }
 208  
 209          return 'pkcs12';
 210      }
 211  
 212      /**
 213       * Returns a SymmetricKey object baesd on a PBES2 $algo
 214       *
 215       * @return SymmetricKey
 216       * @param string $algo
 217       */
 218      private static function getPBES2EncryptionObject($algo)
 219      {
 220          switch ($algo) {
 221              case 'desCBC':
 222                  $cipher = new DES('cbc');
 223                  break;
 224              case 'des-EDE3-CBC':
 225                  $cipher = new TripleDES('cbc');
 226                  break;
 227              case 'rc2CBC':
 228                  $cipher = new RC2('cbc');
 229                  // in theory this can be changed
 230                  $cipher->setKeyLength(128);
 231                  break;
 232              case 'rc5-CBC-PAD':
 233                  throw new UnsupportedAlgorithmException('rc5-CBC-PAD is not supported for PBES2 PKCS#8 keys');
 234              case 'aes128-CBC-PAD':
 235              case 'aes192-CBC-PAD':
 236              case 'aes256-CBC-PAD':
 237                  $cipher = new AES('cbc');
 238                  $cipher->setKeyLength(substr($algo, 3, 3));
 239                  break;
 240              default:
 241                  throw new UnsupportedAlgorithmException("$algo is not supported");
 242          }
 243  
 244          return $cipher;
 245      }
 246  
 247      /**
 248       * Initialize static variables
 249       *
 250       */
 251      private static function initialize_static_variables()
 252      {
 253          if (!isset(static::$childOIDsLoaded)) {
 254              throw new InsufficientSetupException('This class should not be called directly');
 255          }
 256  
 257          if (!static::$childOIDsLoaded) {
 258              ASN1::loadOIDs(is_array(static::OID_NAME) ?
 259                  array_combine(static::OID_NAME, static::OID_VALUE) :
 260                  [static::OID_NAME => static::OID_VALUE]);
 261              static::$childOIDsLoaded = true;
 262          }
 263          if (!self::$oidsLoaded) {
 264              // from https://tools.ietf.org/html/rfc2898
 265              ASN1::loadOIDs([
 266                 // PBES1 encryption schemes
 267                 'pbeWithMD2AndDES-CBC' => '1.2.840.113549.1.5.1',
 268                 'pbeWithMD2AndRC2-CBC' => '1.2.840.113549.1.5.4',
 269                 'pbeWithMD5AndDES-CBC' => '1.2.840.113549.1.5.3',
 270                 'pbeWithMD5AndRC2-CBC' => '1.2.840.113549.1.5.6',
 271                 'pbeWithSHA1AndDES-CBC' => '1.2.840.113549.1.5.10',
 272                 'pbeWithSHA1AndRC2-CBC' => '1.2.840.113549.1.5.11',
 273  
 274                 // from PKCS#12:
 275                 // https://tools.ietf.org/html/rfc7292
 276                 'pbeWithSHAAnd128BitRC4' => '1.2.840.113549.1.12.1.1',
 277                 'pbeWithSHAAnd40BitRC4' => '1.2.840.113549.1.12.1.2',
 278                 'pbeWithSHAAnd3-KeyTripleDES-CBC' => '1.2.840.113549.1.12.1.3',
 279                 'pbeWithSHAAnd2-KeyTripleDES-CBC' => '1.2.840.113549.1.12.1.4',
 280                 'pbeWithSHAAnd128BitRC2-CBC' => '1.2.840.113549.1.12.1.5',
 281                 'pbeWithSHAAnd40BitRC2-CBC' => '1.2.840.113549.1.12.1.6',
 282  
 283                 'id-PBKDF2' => '1.2.840.113549.1.5.12',
 284                 'id-PBES2' => '1.2.840.113549.1.5.13',
 285                 'id-PBMAC1' => '1.2.840.113549.1.5.14',
 286  
 287                 // from PKCS#5 v2.1:
 288                 // http://www.rsa.com/rsalabs/pkcs/files/h11302-wp-pkcs5v2-1-password-based-cryptography-standard.pdf
 289                 'id-hmacWithSHA1' => '1.2.840.113549.2.7',
 290                 'id-hmacWithSHA224' => '1.2.840.113549.2.8',
 291                 'id-hmacWithSHA256' => '1.2.840.113549.2.9',
 292                 'id-hmacWithSHA384' => '1.2.840.113549.2.10',
 293                 'id-hmacWithSHA512' => '1.2.840.113549.2.11',
 294                 'id-hmacWithSHA512-224' => '1.2.840.113549.2.12',
 295                 'id-hmacWithSHA512-256' => '1.2.840.113549.2.13',
 296  
 297                 'desCBC'       => '1.3.14.3.2.7',
 298                 'des-EDE3-CBC' => '1.2.840.113549.3.7',
 299                 'rc2CBC' => '1.2.840.113549.3.2',
 300                 'rc5-CBC-PAD' => '1.2.840.113549.3.9',
 301  
 302                 'aes128-CBC-PAD' => '2.16.840.1.101.3.4.1.2',
 303                 'aes192-CBC-PAD' => '2.16.840.1.101.3.4.1.22',
 304                 'aes256-CBC-PAD' => '2.16.840.1.101.3.4.1.42'
 305              ]);
 306              self::$oidsLoaded = true;
 307          }
 308      }
 309  
 310      /**
 311       * Break a public or private key down into its constituent components
 312       *
 313       * @param string $key
 314       * @param string $password optional
 315       * @return array
 316       */
 317      protected static function load($key, $password = '')
 318      {
 319          if (!Strings::is_stringable($key)) {
 320              throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
 321          }
 322  
 323          $isPublic = strpos($key, 'PUBLIC') !== false;
 324          $isPrivate = strpos($key, 'PRIVATE') !== false;
 325  
 326          $decoded = self::preParse($key);
 327  
 328          $meta = [];
 329  
 330          $decrypted = ASN1::asn1map($decoded[0], Maps\EncryptedPrivateKeyInfo::MAP);
 331          if (strlen($password) && is_array($decrypted)) {
 332              $algorithm = $decrypted['encryptionAlgorithm']['algorithm'];
 333              switch ($algorithm) {
 334                  // PBES1
 335                  case 'pbeWithMD2AndDES-CBC':
 336                  case 'pbeWithMD2AndRC2-CBC':
 337                  case 'pbeWithMD5AndDES-CBC':
 338                  case 'pbeWithMD5AndRC2-CBC':
 339                  case 'pbeWithSHA1AndDES-CBC':
 340                  case 'pbeWithSHA1AndRC2-CBC':
 341                  case 'pbeWithSHAAnd3-KeyTripleDES-CBC':
 342                  case 'pbeWithSHAAnd2-KeyTripleDES-CBC':
 343                  case 'pbeWithSHAAnd128BitRC2-CBC':
 344                  case 'pbeWithSHAAnd40BitRC2-CBC':
 345                  case 'pbeWithSHAAnd128BitRC4':
 346                  case 'pbeWithSHAAnd40BitRC4':
 347                      $cipher = self::getPBES1EncryptionObject($algorithm);
 348                      $hash = self::getPBES1Hash($algorithm);
 349                      $kdf = self::getPBES1KDF($algorithm);
 350  
 351                      $meta['meta']['algorithm'] = $algorithm;
 352  
 353                      $temp = ASN1::decodeBER($decrypted['encryptionAlgorithm']['parameters']);
 354                      if (!$temp) {
 355                          throw new \RuntimeException('Unable to decode BER');
 356                      }
 357                      extract(ASN1::asn1map($temp[0], Maps\PBEParameter::MAP));
 358                      $iterationCount = (int) $iterationCount->toString();
 359                      $cipher->setPassword($password, $kdf, $hash, $salt, $iterationCount);
 360                      $key = $cipher->decrypt($decrypted['encryptedData']);
 361                      $decoded = ASN1::decodeBER($key);
 362                      if (!$decoded) {
 363                          throw new \RuntimeException('Unable to decode BER 2');
 364                      }
 365  
 366                      break;
 367                  case 'id-PBES2':
 368                      $meta['meta']['algorithm'] = $algorithm;
 369  
 370                      $temp = ASN1::decodeBER($decrypted['encryptionAlgorithm']['parameters']);
 371                      if (!$temp) {
 372                          throw new \RuntimeException('Unable to decode BER');
 373                      }
 374                      $temp = ASN1::asn1map($temp[0], Maps\PBES2params::MAP);
 375                      extract($temp);
 376  
 377                      $cipher = self::getPBES2EncryptionObject($encryptionScheme['algorithm']);
 378                      $meta['meta']['cipher'] = $encryptionScheme['algorithm'];
 379  
 380                      $temp = ASN1::decodeBER($decrypted['encryptionAlgorithm']['parameters']);
 381                      if (!$temp) {
 382                          throw new \RuntimeException('Unable to decode BER');
 383                      }
 384                      $temp = ASN1::asn1map($temp[0], Maps\PBES2params::MAP);
 385                      extract($temp);
 386  
 387                      if (!$cipher instanceof RC2) {
 388                          $cipher->setIV($encryptionScheme['parameters']['octetString']);
 389                      } else {
 390                          $temp = ASN1::decodeBER($encryptionScheme['parameters']);
 391                          if (!$temp) {
 392                              throw new \RuntimeException('Unable to decode BER');
 393                          }
 394                          extract(ASN1::asn1map($temp[0], Maps\RC2CBCParameter::MAP));
 395                          $effectiveKeyLength = (int) $rc2ParametersVersion->toString();
 396                          switch ($effectiveKeyLength) {
 397                              case 160:
 398                                  $effectiveKeyLength = 40;
 399                                  break;
 400                              case 120:
 401                                  $effectiveKeyLength = 64;
 402                                  break;
 403                              case 58:
 404                                  $effectiveKeyLength = 128;
 405                                  break;
 406                              //default: // should be >= 256
 407                          }
 408                          $cipher->setIV($iv);
 409                          $cipher->setKeyLength($effectiveKeyLength);
 410                      }
 411  
 412                      $meta['meta']['keyDerivationFunc'] = $keyDerivationFunc['algorithm'];
 413                      switch ($keyDerivationFunc['algorithm']) {
 414                          case 'id-PBKDF2':
 415                              $temp = ASN1::decodeBER($keyDerivationFunc['parameters']);
 416                              if (!$temp) {
 417                                  throw new \RuntimeException('Unable to decode BER');
 418                              }
 419                              $prf = ['algorithm' => 'id-hmacWithSHA1'];
 420                              $params = ASN1::asn1map($temp[0], Maps\PBKDF2params::MAP);
 421                              extract($params);
 422                              $meta['meta']['prf'] = $prf['algorithm'];
 423                              $hash = str_replace('-', '/', substr($prf['algorithm'], 11));
 424                              $params = [
 425                                  $password,
 426                                  'pbkdf2',
 427                                  $hash,
 428                                  $salt,
 429                                  (int) $iterationCount->toString()
 430                              ];
 431                              if (isset($keyLength)) {
 432                                  $params[] = (int) $keyLength->toString();
 433                              }
 434                              $cipher->setPassword(...$params);
 435                              $key = $cipher->decrypt($decrypted['encryptedData']);
 436                              $decoded = ASN1::decodeBER($key);
 437                              if (!$decoded) {
 438                                  throw new \RuntimeException('Unable to decode BER 3');
 439                              }
 440                              break;
 441                          default:
 442                              throw new UnsupportedAlgorithmException('Only PBKDF2 is supported for PBES2 PKCS#8 keys');
 443                      }
 444                      break;
 445                  case 'id-PBMAC1':
 446                      //$temp = ASN1::decodeBER($decrypted['encryptionAlgorithm']['parameters']);
 447                      //$value = ASN1::asn1map($temp[0], Maps\PBMAC1params::MAP);
 448                      // since i can't find any implementation that does PBMAC1 it is unsupported
 449                      throw new UnsupportedAlgorithmException('Only PBES1 and PBES2 PKCS#8 keys are supported.');
 450                  // at this point we'll assume that the key conforms to PublicKeyInfo
 451              }
 452          }
 453  
 454          $private = ASN1::asn1map($decoded[0], Maps\OneAsymmetricKey::MAP);
 455          if (is_array($private)) {
 456              if ($isPublic) {
 457                  throw new \UnexpectedValueException('Human readable string claims public key but DER encoded string claims private key');
 458              }
 459  
 460              if (isset($private['privateKeyAlgorithm']['parameters']) && !$private['privateKeyAlgorithm']['parameters'] instanceof ASN1\Element && isset($decoded[0]['content'][1]['content'][1])) {
 461                  $temp = $decoded[0]['content'][1]['content'][1];
 462                  $private['privateKeyAlgorithm']['parameters'] = new ASN1\Element(substr($key, $temp['start'], $temp['length']));
 463              }
 464              if (is_array(static::OID_NAME)) {
 465                  if (!in_array($private['privateKeyAlgorithm']['algorithm'], static::OID_NAME)) {
 466                      throw new UnsupportedAlgorithmException($private['privateKeyAlgorithm']['algorithm'] . ' is not a supported key type');
 467                  }
 468              } else {
 469                  if ($private['privateKeyAlgorithm']['algorithm'] != static::OID_NAME) {
 470                      throw new UnsupportedAlgorithmException('Only ' . static::OID_NAME . ' keys are supported; this is a ' . $private['privateKeyAlgorithm']['algorithm'] . ' key');
 471                  }
 472              }
 473              if (isset($private['publicKey'])) {
 474                  if ($private['publicKey'][0] != "\0") {
 475                      throw new \UnexpectedValueException('The first byte of the public key should be null - not ' . bin2hex($private['publicKey'][0]));
 476                  }
 477                  $private['publicKey'] = substr($private['publicKey'], 1);
 478              }
 479              return $private + $meta;
 480          }
 481  
 482          // EncryptedPrivateKeyInfo and PublicKeyInfo have largely identical "signatures". the only difference
 483          // is that the former has an octet string and the later has a bit string. the first byte of a bit
 484          // string represents the number of bits in the last byte that are to be ignored but, currently,
 485          // bit strings wanting a non-zero amount of bits trimmed are not supported
 486          $public = ASN1::asn1map($decoded[0], Maps\PublicKeyInfo::MAP);
 487  
 488          if (is_array($public)) {
 489              if ($isPrivate) {
 490                  throw new \UnexpectedValueException('Human readable string claims private key but DER encoded string claims public key');
 491              }
 492  
 493              if ($public['publicKey'][0] != "\0") {
 494                  throw new \UnexpectedValueException('The first byte of the public key should be null - not ' . bin2hex($public['publicKey'][0]));
 495              }
 496              if (is_array(static::OID_NAME)) {
 497                  if (!in_array($public['publicKeyAlgorithm']['algorithm'], static::OID_NAME)) {
 498                      throw new UnsupportedAlgorithmException($public['publicKeyAlgorithm']['algorithm'] . ' is not a supported key type');
 499                  }
 500              } else {
 501                  if ($public['publicKeyAlgorithm']['algorithm'] != static::OID_NAME) {
 502                      throw new UnsupportedAlgorithmException('Only ' . static::OID_NAME . ' keys are supported; this is a ' . $public['publicKeyAlgorithm']['algorithm'] . ' key');
 503                  }
 504              }
 505              if (isset($public['publicKeyAlgorithm']['parameters']) && !$public['publicKeyAlgorithm']['parameters'] instanceof ASN1\Element && isset($decoded[0]['content'][0]['content'][1])) {
 506                  $temp = $decoded[0]['content'][0]['content'][1];
 507                  $public['publicKeyAlgorithm']['parameters'] = new ASN1\Element(substr($key, $temp['start'], $temp['length']));
 508              }
 509              $public['publicKey'] = substr($public['publicKey'], 1);
 510              return $public;
 511          }
 512  
 513          throw new \RuntimeException('Unable to parse using either OneAsymmetricKey or PublicKeyInfo ASN1 maps');
 514      }
 515  
 516      /**
 517       * Wrap a private key appropriately
 518       *
 519       * @param string $key
 520       * @param string $attr
 521       * @param mixed $params
 522       * @param string $password
 523       * @param string $oid optional
 524       * @param string $publicKey optional
 525       * @param array $options optional
 526       * @return string
 527       */
 528      protected static function wrapPrivateKey($key, $attr, $params, $password, $oid = null, $publicKey = '', array $options = [])
 529      {
 530          self::initialize_static_variables();
 531  
 532          $key = [
 533              'version' => 'v1',
 534              'privateKeyAlgorithm' => [
 535                  'algorithm' => is_string(static::OID_NAME) ? static::OID_NAME : $oid
 536               ],
 537              'privateKey' => $key
 538          ];
 539          if ($oid != 'id-Ed25519' && $oid != 'id-Ed448') {
 540              $key['privateKeyAlgorithm']['parameters'] = $params;
 541          }
 542          if (!empty($attr)) {
 543              $key['attributes'] = $attr;
 544          }
 545          if (!empty($publicKey)) {
 546              $key['version'] = 'v2';
 547              $key['publicKey'] = $publicKey;
 548          }
 549          $key = ASN1::encodeDER($key, Maps\OneAsymmetricKey::MAP);
 550          if (!empty($password) && is_string($password)) {
 551              $salt = Random::string(8);
 552  
 553              $iterationCount = isset($options['iterationCount']) ? $options['iterationCount'] : self::$defaultIterationCount;
 554              $encryptionAlgorithm = isset($options['encryptionAlgorithm']) ? $options['encryptionAlgorithm'] : self::$defaultEncryptionAlgorithm;
 555              $encryptionScheme = isset($options['encryptionScheme']) ? $options['encryptionScheme'] : self::$defaultEncryptionScheme;
 556              $prf = isset($options['PRF']) ? $options['PRF'] : self::$defaultPRF;
 557  
 558              if ($encryptionAlgorithm == 'id-PBES2') {
 559                  $crypto = self::getPBES2EncryptionObject($encryptionScheme);
 560                  $hash = str_replace('-', '/', substr($prf, 11));
 561                  $kdf = 'pbkdf2';
 562                  $iv = Random::string($crypto->getBlockLength() >> 3);
 563  
 564                  $PBKDF2params = [
 565                      'salt' => $salt,
 566                      'iterationCount' => $iterationCount,
 567                      'prf' => ['algorithm' => $prf, 'parameters' => null]
 568                  ];
 569                  $PBKDF2params = ASN1::encodeDER($PBKDF2params, Maps\PBKDF2params::MAP);
 570  
 571                  if (!$crypto instanceof RC2) {
 572                      $params = ['octetString' => $iv];
 573                  } else {
 574                      $params = [
 575                          'rc2ParametersVersion' => 58,
 576                          'iv' => $iv
 577                      ];
 578                      $params = ASN1::encodeDER($params, Maps\RC2CBCParameter::MAP);
 579                      $params = new ASN1\Element($params);
 580                  }
 581  
 582                  $params = [
 583                      'keyDerivationFunc' => [
 584                          'algorithm' => 'id-PBKDF2',
 585                          'parameters' => new ASN1\Element($PBKDF2params)
 586                      ],
 587                      'encryptionScheme' => [
 588                          'algorithm' => $encryptionScheme,
 589                          'parameters' => $params
 590                      ]
 591                  ];
 592                  $params = ASN1::encodeDER($params, Maps\PBES2params::MAP);
 593  
 594                  $crypto->setIV($iv);
 595              } else {
 596                  $crypto = self::getPBES1EncryptionObject($encryptionAlgorithm);
 597                  $hash = self::getPBES1Hash($encryptionAlgorithm);
 598                  $kdf = self::getPBES1KDF($encryptionAlgorithm);
 599  
 600                  $params = [
 601                      'salt' => $salt,
 602                      'iterationCount' => $iterationCount
 603                  ];
 604                  $params = ASN1::encodeDER($params, Maps\PBEParameter::MAP);
 605              }
 606              $crypto->setPassword($password, $kdf, $hash, $salt, $iterationCount);
 607              $key = $crypto->encrypt($key);
 608  
 609              $key = [
 610                  'encryptionAlgorithm' => [
 611                      'algorithm' => $encryptionAlgorithm,
 612                      'parameters' => new ASN1\Element($params)
 613                  ],
 614                  'encryptedData' => $key
 615              ];
 616  
 617              $key = ASN1::encodeDER($key, Maps\EncryptedPrivateKeyInfo::MAP);
 618  
 619              return "-----BEGIN ENCRYPTED PRIVATE KEY-----\r\n" .
 620                     chunk_split(Strings::base64_encode($key), 64) .
 621                     "-----END ENCRYPTED PRIVATE KEY-----";
 622          }
 623  
 624          return "-----BEGIN PRIVATE KEY-----\r\n" .
 625                 chunk_split(Strings::base64_encode($key), 64) .
 626                 "-----END PRIVATE KEY-----";
 627      }
 628  
 629      /**
 630       * Wrap a public key appropriately
 631       *
 632       * @param string $key
 633       * @param mixed $params
 634       * @param string $oid
 635       * @return string
 636       */
 637      protected static function wrapPublicKey($key, $params, $oid = null)
 638      {
 639          self::initialize_static_variables();
 640  
 641          $key = [
 642              'publicKeyAlgorithm' => [
 643                  'algorithm' => is_string(static::OID_NAME) ? static::OID_NAME : $oid
 644              ],
 645              'publicKey' => "\0" . $key
 646          ];
 647  
 648          if ($oid != 'id-Ed25519' && $oid != 'id-Ed448') {
 649              $key['publicKeyAlgorithm']['parameters'] = $params;
 650          }
 651  
 652          $key = ASN1::encodeDER($key, Maps\PublicKeyInfo::MAP);
 653  
 654          return "-----BEGIN PUBLIC KEY-----\r\n" .
 655                 chunk_split(Strings::base64_encode($key), 64) .
 656                 "-----END PUBLIC KEY-----";
 657      }
 658  
 659      /**
 660       * Perform some preliminary parsing of the key
 661       *
 662       * @param string $key
 663       * @return array
 664       */
 665      private static function preParse(&$key)
 666      {
 667          self::initialize_static_variables();
 668  
 669          if (self::$format != self::MODE_DER) {
 670              $decoded = ASN1::extractBER($key);
 671              if ($decoded !== false) {
 672                  $key = $decoded;
 673              } elseif (self::$format == self::MODE_PEM) {
 674                  throw new \UnexpectedValueException('Expected base64-encoded PEM format but was unable to decode base64 text');
 675              }
 676          }
 677  
 678          $decoded = ASN1::decodeBER($key);
 679          if (!$decoded) {
 680              throw new \RuntimeException('Unable to decode BER');
 681          }
 682  
 683          return $decoded;
 684      }
 685  
 686      /**
 687       * Returns the encryption parameters used by the key
 688       *
 689       * @param string $key
 690       * @return array
 691       */
 692      public static function extractEncryptionAlgorithm($key)
 693      {
 694          if (!Strings::is_stringable($key)) {
 695              throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
 696          }
 697  
 698          $decoded = self::preParse($key);
 699  
 700          $r = ASN1::asn1map($decoded[0], ASN1\Maps\EncryptedPrivateKeyInfo::MAP);
 701          if (!is_array($r)) {
 702              throw new \RuntimeException('Unable to parse using EncryptedPrivateKeyInfo map');
 703          }
 704  
 705          if ($r['encryptionAlgorithm']['algorithm'] == 'id-PBES2') {
 706              $decoded = ASN1::decodeBER($r['encryptionAlgorithm']['parameters']->element);
 707              if (!$decoded) {
 708                  throw new \RuntimeException('Unable to decode BER');
 709              }
 710              $r['encryptionAlgorithm']['parameters'] = ASN1::asn1map($decoded[0], ASN1\Maps\PBES2params::MAP);
 711  
 712              $kdf = &$r['encryptionAlgorithm']['parameters']['keyDerivationFunc'];
 713              switch ($kdf['algorithm']) {
 714                  case 'id-PBKDF2':
 715                      $decoded = ASN1::decodeBER($kdf['parameters']->element);
 716                      if (!$decoded) {
 717                          throw new \RuntimeException('Unable to decode BER');
 718                      }
 719                      $kdf['parameters'] = ASN1::asn1map($decoded[0], Maps\PBKDF2params::MAP);
 720              }
 721          }
 722  
 723          return $r['encryptionAlgorithm'];
 724      }
 725  }