[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

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

   1  <?php
   2  
   3  /**
   4   * PKCS1 Formatted Key Handler
   5   *
   6   * PHP version 5
   7   *
   8   * @author    Jim Wigginton <terrafrost@php.net>
   9   * @copyright 2015 Jim Wigginton
  10   * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
  11   * @link      http://phpseclib.sourceforge.net
  12   */
  13  
  14  namespace phpseclib3\Crypt\Common\Formats\Keys;
  15  
  16  use phpseclib3\Common\Functions\Strings;
  17  use phpseclib3\Crypt\AES;
  18  use phpseclib3\Crypt\DES;
  19  use phpseclib3\Crypt\Random;
  20  use phpseclib3\Crypt\TripleDES;
  21  use phpseclib3\Exception\UnsupportedAlgorithmException;
  22  use phpseclib3\File\ASN1;
  23  
  24  /**
  25   * PKCS1 Formatted Key Handler
  26   *
  27   * @author  Jim Wigginton <terrafrost@php.net>
  28   */
  29  abstract class PKCS1 extends PKCS
  30  {
  31      /**
  32       * Default encryption algorithm
  33       *
  34       * @var string
  35       */
  36      private static $defaultEncryptionAlgorithm = 'AES-128-CBC';
  37  
  38      /**
  39       * Sets the default encryption algorithm
  40       *
  41       * @param string $algo
  42       */
  43      public static function setEncryptionAlgorithm($algo)
  44      {
  45          self::$defaultEncryptionAlgorithm = $algo;
  46      }
  47  
  48      /**
  49       * Returns the mode constant corresponding to the mode string
  50       *
  51       * @param string $mode
  52       * @return int
  53       * @throws \UnexpectedValueException if the block cipher mode is unsupported
  54       */
  55      private static function getEncryptionMode($mode)
  56      {
  57          switch ($mode) {
  58              case 'CBC':
  59              case 'ECB':
  60              case 'CFB':
  61              case 'OFB':
  62              case 'CTR':
  63                  return $mode;
  64          }
  65          throw new \UnexpectedValueException('Unsupported block cipher mode of operation');
  66      }
  67  
  68      /**
  69       * Returns a cipher object corresponding to a string
  70       *
  71       * @param string $algo
  72       * @return string
  73       * @throws \UnexpectedValueException if the encryption algorithm is unsupported
  74       */
  75      private static function getEncryptionObject($algo)
  76      {
  77          $modes = '(CBC|ECB|CFB|OFB|CTR)';
  78          switch (true) {
  79              case preg_match("#^AES-(128|192|256)-$modes$#", $algo, $matches):
  80                  $cipher = new AES(self::getEncryptionMode($matches[2]));
  81                  $cipher->setKeyLength($matches[1]);
  82                  return $cipher;
  83              case preg_match("#^DES-EDE3-$modes$#", $algo, $matches):
  84                  return new TripleDES(self::getEncryptionMode($matches[1]));
  85              case preg_match("#^DES-$modes$#", $algo, $matches):
  86                  return new DES(self::getEncryptionMode($matches[1]));
  87              default:
  88                  throw new UnsupportedAlgorithmException($algo . ' is not a supported algorithm');
  89          }
  90      }
  91  
  92      /**
  93       * Generate a symmetric key for PKCS#1 keys
  94       *
  95       * @param string $password
  96       * @param string $iv
  97       * @param int $length
  98       * @return string
  99       */
 100      private static function generateSymmetricKey($password, $iv, $length)
 101      {
 102          $symkey = '';
 103          $iv = substr($iv, 0, 8);
 104          while (strlen($symkey) < $length) {
 105              $symkey .= md5($symkey . $password . $iv, true);
 106          }
 107          return substr($symkey, 0, $length);
 108      }
 109  
 110      /**
 111       * Break a public or private key down into its constituent components
 112       *
 113       * @param string $key
 114       * @param string $password optional
 115       * @return array
 116       */
 117      protected static function load($key, $password)
 118      {
 119          if (!Strings::is_stringable($key)) {
 120              throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
 121          }
 122  
 123          /* Although PKCS#1 proposes a format that public and private keys can use, encrypting them is
 124             "outside the scope" of PKCS#1.  PKCS#1 then refers you to PKCS#12 and PKCS#15 if you're wanting to
 125             protect private keys, however, that's not what OpenSSL* does.  OpenSSL protects private keys by adding
 126             two new "fields" to the key - DEK-Info and Proc-Type.  These fields are discussed here:
 127  
 128             http://tools.ietf.org/html/rfc1421#section-4.6.1.1
 129             http://tools.ietf.org/html/rfc1421#section-4.6.1.3
 130  
 131             DES-EDE3-CBC as an algorithm, however, is not discussed anywhere, near as I can tell.
 132             DES-CBC and DES-EDE are discussed in RFC1423, however, DES-EDE3-CBC isn't, nor is its key derivation
 133             function.  As is, the definitive authority on this encoding scheme isn't the IETF but rather OpenSSL's
 134             own implementation.  ie. the implementation *is* the standard and any bugs that may exist in that
 135             implementation are part of the standard, as well.
 136  
 137             * OpenSSL is the de facto standard.  It's utilized by OpenSSH and other projects */
 138          if (preg_match('#DEK-Info: (.+),(.+)#', $key, $matches)) {
 139              $iv = Strings::hex2bin(trim($matches[2]));
 140              // remove the Proc-Type / DEK-Info sections as they're no longer needed
 141              $key = preg_replace('#^(?:Proc-Type|DEK-Info): .*#m', '', $key);
 142              $ciphertext = ASN1::extractBER($key);
 143              if ($ciphertext === false) {
 144                  $ciphertext = $key;
 145              }
 146              $crypto = self::getEncryptionObject($matches[1]);
 147              $crypto->setKey(self::generateSymmetricKey($password, $iv, $crypto->getKeyLength() >> 3));
 148              $crypto->setIV($iv);
 149              $key = $crypto->decrypt($ciphertext);
 150          } else {
 151              if (self::$format != self::MODE_DER) {
 152                  $decoded = ASN1::extractBER($key);
 153                  if ($decoded !== false) {
 154                      $key = $decoded;
 155                  } elseif (self::$format == self::MODE_PEM) {
 156                      throw new \UnexpectedValueException('Expected base64-encoded PEM format but was unable to decode base64 text');
 157                  }
 158              }
 159          }
 160  
 161          return $key;
 162      }
 163  
 164      /**
 165       * Wrap a private key appropriately
 166       *
 167       * @param string $key
 168       * @param string $type
 169       * @param string $password
 170       * @param array $options optional
 171       * @return string
 172       */
 173      protected static function wrapPrivateKey($key, $type, $password, array $options = [])
 174      {
 175          if (empty($password) || !is_string($password)) {
 176              return "-----BEGIN $type PRIVATE KEY-----\r\n" .
 177                     chunk_split(Strings::base64_encode($key), 64) .
 178                     "-----END $type PRIVATE KEY-----";
 179          }
 180  
 181          $encryptionAlgorithm = isset($options['encryptionAlgorithm']) ? $options['encryptionAlgorithm'] : self::$defaultEncryptionAlgorithm;
 182  
 183          $cipher = self::getEncryptionObject($encryptionAlgorithm);
 184          $iv = Random::string($cipher->getBlockLength() >> 3);
 185          $cipher->setKey(self::generateSymmetricKey($password, $iv, $cipher->getKeyLength() >> 3));
 186          $cipher->setIV($iv);
 187          $iv = strtoupper(Strings::bin2hex($iv));
 188          return "-----BEGIN $type PRIVATE KEY-----\r\n" .
 189                 "Proc-Type: 4,ENCRYPTED\r\n" .
 190                 "DEK-Info: " . $encryptionAlgorithm . ",$iv\r\n" .
 191                 "\r\n" .
 192                 chunk_split(Strings::base64_encode($cipher->encrypt($key)), 64) .
 193                 "-----END $type PRIVATE KEY-----";
 194      }
 195  
 196      /**
 197       * Wrap a public key appropriately
 198       *
 199       * @param string $key
 200       * @param string $type
 201       * @return string
 202       */
 203      protected static function wrapPublicKey($key, $type)
 204      {
 205          return "-----BEGIN $type PUBLIC KEY-----\r\n" .
 206                 chunk_split(Strings::base64_encode($key), 64) .
 207                 "-----END $type PUBLIC KEY-----";
 208      }
 209  }