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