1<?php 2 3/** 4 * Miccrosoft BLOB Formatted RSA Key Handler 5 * 6 * More info: 7 * 8 * https://msdn.microsoft.com/en-us/library/windows/desktop/aa375601(v=vs.85).aspx 9 * 10 * PHP version 5 11 * 12 * @category Crypt 13 * @package RSA 14 * @author Jim Wigginton <terrafrost@php.net> 15 * @copyright 2015 Jim Wigginton 16 * @license http://www.opensource.org/licenses/mit-license.html MIT License 17 * @link http://phpseclib.sourceforge.net 18 */ 19 20namespace phpseclib3\Crypt\RSA\Formats\Keys; 21 22use ParagonIE\ConstantTime\Base64; 23use phpseclib3\Common\Functions\Strings; 24use phpseclib3\Exception\UnsupportedFormatException; 25use phpseclib3\Math\BigInteger; 26 27/** 28 * Microsoft BLOB Formatted RSA Key Handler 29 * 30 * @package RSA 31 * @author Jim Wigginton <terrafrost@php.net> 32 * @access public 33 */ 34abstract class MSBLOB 35{ 36 /** 37 * Public/Private Key Pair 38 * 39 * @access private 40 */ 41 const PRIVATEKEYBLOB = 0x7; 42 /** 43 * Public Key 44 * 45 * @access private 46 */ 47 const PUBLICKEYBLOB = 0x6; 48 /** 49 * Public Key 50 * 51 * @access private 52 */ 53 const PUBLICKEYBLOBEX = 0xA; 54 /** 55 * RSA public key exchange algorithm 56 * 57 * @access private 58 */ 59 const CALG_RSA_KEYX = 0x0000A400; 60 /** 61 * RSA public key exchange algorithm 62 * 63 * @access private 64 */ 65 const CALG_RSA_SIGN = 0x00002400; 66 /** 67 * Public Key 68 * 69 * @access private 70 */ 71 const RSA1 = 0x31415352; 72 /** 73 * Private Key 74 * 75 * @access private 76 */ 77 const RSA2 = 0x32415352; 78 79 /** 80 * Break a public or private key down into its constituent components 81 * 82 * @access public 83 * @param string $key 84 * @param string $password optional 85 * @return array 86 */ 87 public static function load($key, $password = '') 88 { 89 if (!Strings::is_stringable($key)) { 90 throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key)); 91 } 92 93 $key = Base64::decode($key); 94 95 if (!is_string($key)) { 96 throw new \UnexpectedValueException('Base64 decoding produced an error'); 97 } 98 if (strlen($key) < 20) { 99 throw new \UnexpectedValueException('Key appears to be malformed'); 100 } 101 102 // PUBLICKEYSTRUC publickeystruc 103 // https://msdn.microsoft.com/en-us/library/windows/desktop/aa387453(v=vs.85).aspx 104 extract(unpack('atype/aversion/vreserved/Valgo', Strings::shift($key, 8))); 105 /** 106 * @var string $type 107 * @var string $version 108 * @var integer $reserved 109 * @var integer $algo 110 */ 111 switch (ord($type)) { 112 case self::PUBLICKEYBLOB: 113 case self::PUBLICKEYBLOBEX: 114 $publickey = true; 115 break; 116 case self::PRIVATEKEYBLOB: 117 $publickey = false; 118 break; 119 default: 120 throw new \UnexpectedValueException('Key appears to be malformed'); 121 } 122 123 $components = ['isPublicKey' => $publickey]; 124 125 // https://msdn.microsoft.com/en-us/library/windows/desktop/aa375549(v=vs.85).aspx 126 switch ($algo) { 127 case self::CALG_RSA_KEYX: 128 case self::CALG_RSA_SIGN: 129 break; 130 default: 131 throw new \UnexpectedValueException('Key appears to be malformed'); 132 } 133 134 // RSAPUBKEY rsapubkey 135 // https://msdn.microsoft.com/en-us/library/windows/desktop/aa387685(v=vs.85).aspx 136 // could do V for pubexp but that's unsigned 32-bit whereas some PHP installs only do signed 32-bit 137 extract(unpack('Vmagic/Vbitlen/a4pubexp', Strings::shift($key, 12))); 138 /** 139 * @var integer $magic 140 * @var integer $bitlen 141 * @var string $pubexp 142 */ 143 switch ($magic) { 144 case self::RSA2: 145 $components['isPublicKey'] = false; 146 // fall-through 147 case self::RSA1: 148 break; 149 default: 150 throw new \UnexpectedValueException('Key appears to be malformed'); 151 } 152 153 $baseLength = $bitlen / 16; 154 if (strlen($key) != 2 * $baseLength && strlen($key) != 9 * $baseLength) { 155 throw new \UnexpectedValueException('Key appears to be malformed'); 156 } 157 158 $components[$components['isPublicKey'] ? 'publicExponent' : 'privateExponent'] = new BigInteger(strrev($pubexp), 256); 159 // BYTE modulus[rsapubkey.bitlen/8] 160 $components['modulus'] = new BigInteger(strrev(Strings::shift($key, $bitlen / 8)), 256); 161 162 if ($publickey) { 163 return $components; 164 } 165 166 $components['isPublicKey'] = false; 167 168 // BYTE prime1[rsapubkey.bitlen/16] 169 $components['primes'] = [1 => new BigInteger(strrev(Strings::shift($key, $bitlen / 16)), 256)]; 170 // BYTE prime2[rsapubkey.bitlen/16] 171 $components['primes'][] = new BigInteger(strrev(Strings::shift($key, $bitlen / 16)), 256); 172 // BYTE exponent1[rsapubkey.bitlen/16] 173 $components['exponents'] = [1 => new BigInteger(strrev(Strings::shift($key, $bitlen / 16)), 256)]; 174 // BYTE exponent2[rsapubkey.bitlen/16] 175 $components['exponents'][] = new BigInteger(strrev(Strings::shift($key, $bitlen / 16)), 256); 176 // BYTE coefficient[rsapubkey.bitlen/16] 177 $components['coefficients'] = [2 => new BigInteger(strrev(Strings::shift($key, $bitlen / 16)), 256)]; 178 if (isset($components['privateExponent'])) { 179 $components['publicExponent'] = $components['privateExponent']; 180 } 181 // BYTE privateExponent[rsapubkey.bitlen/8] 182 $components['privateExponent'] = new BigInteger(strrev(Strings::shift($key, $bitlen / 8)), 256); 183 184 return $components; 185 } 186 187 /** 188 * Convert a private key to the appropriate format. 189 * 190 * @access public 191 * @param \phpseclib3\Math\BigInteger $n 192 * @param \phpseclib3\Math\BigInteger $e 193 * @param \phpseclib3\Math\BigInteger $d 194 * @param array $primes 195 * @param array $exponents 196 * @param array $coefficients 197 * @param string $password optional 198 * @return string 199 */ 200 public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, $password = '') 201 { 202 if (count($primes) != 2) { 203 throw new \InvalidArgumentException('MSBLOB does not support multi-prime RSA keys'); 204 } 205 206 if (!empty($password) && is_string($password)) { 207 throw new UnsupportedFormatException('MSBLOB private keys do not support encryption'); 208 } 209 210 $n = strrev($n->toBytes()); 211 $e = str_pad(strrev($e->toBytes()), 4, "\0"); 212 $key = pack('aavV', chr(self::PRIVATEKEYBLOB), chr(2), 0, self::CALG_RSA_KEYX); 213 $key .= pack('VVa*', self::RSA2, 8 * strlen($n), $e); 214 $key .= $n; 215 $key .= strrev($primes[1]->toBytes()); 216 $key .= strrev($primes[2]->toBytes()); 217 $key .= strrev($exponents[1]->toBytes()); 218 $key .= strrev($exponents[2]->toBytes()); 219 $key .= strrev($coefficients[2]->toBytes()); 220 $key .= strrev($d->toBytes()); 221 222 return Base64::encode($key); 223 } 224 225 /** 226 * Convert a public key to the appropriate format 227 * 228 * @access public 229 * @param \phpseclib3\Math\BigInteger $n 230 * @param \phpseclib3\Math\BigInteger $e 231 * @return string 232 */ 233 public static function savePublicKey(BigInteger $n, BigInteger $e) 234 { 235 $n = strrev($n->toBytes()); 236 $e = str_pad(strrev($e->toBytes()), 4, "\0"); 237 $key = pack('aavV', chr(self::PUBLICKEYBLOB), chr(2), 0, self::CALG_RSA_KEYX); 238 $key .= pack('VVa*', self::RSA1, 8 * strlen($n), $e); 239 $key .= $n; 240 241 return Base64::encode($key); 242 } 243} 244