PuTTY.php 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. <?php
  2. /**
  3. * PuTTY Formatted RSA Key Handler
  4. *
  5. * PHP version 5
  6. *
  7. * @category Crypt
  8. * @package RSA
  9. * @author Jim Wigginton <terrafrost@php.net>
  10. * @copyright 2015 Jim Wigginton
  11. * @license http://www.opensource.org/licenses/mit-license.html MIT License
  12. * @link http://phpseclib.sourceforge.net
  13. */
  14. namespace phpseclib\Crypt\RSA;
  15. use ParagonIE\ConstantTime\Base64;
  16. use ParagonIE\ConstantTime\Hex;
  17. use phpseclib\Crypt\AES;
  18. use phpseclib\Crypt\Hash;
  19. use phpseclib\Math\BigInteger;
  20. /**
  21. * PuTTY Formatted RSA Key Handler
  22. *
  23. * @package RSA
  24. * @author Jim Wigginton <terrafrost@php.net>
  25. * @access public
  26. */
  27. class PuTTY
  28. {
  29. /**
  30. * Default comment
  31. *
  32. * @var string
  33. * @access private
  34. */
  35. static $comment = 'phpseclib-generated-key';
  36. /**
  37. * Sets the default comment
  38. *
  39. * @access public
  40. * @param string $comment
  41. */
  42. static function setComment($comment)
  43. {
  44. self::$comment = str_replace(array("\r", "\n"), '', $comment);
  45. }
  46. /**
  47. * Generate a symmetric key for PuTTY keys
  48. *
  49. * @access public
  50. * @param string $password
  51. * @param string $iv
  52. * @param int $length
  53. * @return string
  54. */
  55. static function generateSymmetricKey($password, $length)
  56. {
  57. $symkey = '';
  58. $sequence = 0;
  59. while (strlen($symkey) < $length) {
  60. $temp = pack('Na*', $sequence++, $password);
  61. $symkey.= Hex::decode(sha1($temp));
  62. }
  63. return substr($symkey, 0, $length);
  64. }
  65. /**
  66. * Break a public or private key down into its constituent components
  67. *
  68. * @access public
  69. * @param string $key
  70. * @param string $password optional
  71. * @return array
  72. */
  73. static function load($key, $password = '')
  74. {
  75. if (!is_string($key)) {
  76. return false;
  77. }
  78. static $one;
  79. if (!isset($one)) {
  80. $one = new BigInteger(1);
  81. }
  82. if (strpos($key, 'BEGIN SSH2 PUBLIC KEY')) {
  83. $data = preg_split('#[\r\n]+#', $key);
  84. $data = array_splice($data, 2, -1);
  85. $data = implode('', $data);
  86. $components = OpenSSH::load($data);
  87. if ($components === false) {
  88. return false;
  89. }
  90. if (!preg_match('#Comment: "(.+)"#', $key, $matches)) {
  91. return false;
  92. }
  93. $components['comment'] = str_replace(array('\\\\', '\"'), array('\\', '"'), $matches[1]);
  94. return $components;
  95. }
  96. $components = array('isPublicKey' => false);
  97. $key = preg_split('#\r\n|\r|\n#', $key);
  98. $type = trim(preg_replace('#PuTTY-User-Key-File-2: (.+)#', '$1', $key[0]));
  99. if ($type != 'ssh-rsa') {
  100. return false;
  101. }
  102. $encryption = trim(preg_replace('#Encryption: (.+)#', '$1', $key[1]));
  103. $components['comment'] = trim(preg_replace('#Comment: (.+)#', '$1', $key[2]));
  104. $publicLength = trim(preg_replace('#Public-Lines: (\d+)#', '$1', $key[3]));
  105. $public = Base64::decode(implode('', array_map('trim', array_slice($key, 4, $publicLength))));
  106. $public = substr($public, 11);
  107. extract(unpack('Nlength', self::_string_shift($public, 4)));
  108. $components['publicExponent'] = new BigInteger(self::_string_shift($public, $length), -256);
  109. extract(unpack('Nlength', self::_string_shift($public, 4)));
  110. $components['modulus'] = new BigInteger(self::_string_shift($public, $length), -256);
  111. $privateLength = trim(preg_replace('#Private-Lines: (\d+)#', '$1', $key[$publicLength + 4]));
  112. $private = Base64::decode(implode('', array_map('trim', array_slice($key, $publicLength + 5, $privateLength))));
  113. switch ($encryption) {
  114. case 'aes256-cbc':
  115. $symkey = static::generateSymmetricKey($password, 32);
  116. $crypto = new AES(AES::MODE_CBC);
  117. }
  118. if ($encryption != 'none') {
  119. $crypto->setKey($symkey);
  120. $crypto->setIV(str_repeat("\0", $crypto->getBlockLength() >> 3));
  121. $crypto->disablePadding();
  122. $private = $crypto->decrypt($private);
  123. if ($private === false) {
  124. return false;
  125. }
  126. }
  127. extract(unpack('Nlength', self::_string_shift($private, 4)));
  128. if (strlen($private) < $length) {
  129. return false;
  130. }
  131. $components['privateExponent'] = new BigInteger(self::_string_shift($private, $length), -256);
  132. extract(unpack('Nlength', self::_string_shift($private, 4)));
  133. if (strlen($private) < $length) {
  134. return false;
  135. }
  136. $components['primes'] = array(1 => new BigInteger(self::_string_shift($private, $length), -256));
  137. extract(unpack('Nlength', self::_string_shift($private, 4)));
  138. if (strlen($private) < $length) {
  139. return false;
  140. }
  141. $components['primes'][] = new BigInteger(self::_string_shift($private, $length), -256);
  142. $temp = $components['primes'][1]->subtract($one);
  143. $components['exponents'] = array(1 => $components['publicExponent']->modInverse($temp));
  144. $temp = $components['primes'][2]->subtract($one);
  145. $components['exponents'][] = $components['publicExponent']->modInverse($temp);
  146. extract(unpack('Nlength', self::_string_shift($private, 4)));
  147. if (strlen($private) < $length) {
  148. return false;
  149. }
  150. $components['coefficients'] = array(2 => new BigInteger(self::_string_shift($private, $length), -256));
  151. return $components;
  152. }
  153. /**
  154. * String Shift
  155. *
  156. * Inspired by array_shift
  157. *
  158. * @param string $string
  159. * @param int $index
  160. * @return string
  161. * @access private
  162. */
  163. static function _string_shift(&$string, $index = 1)
  164. {
  165. $substr = substr($string, 0, $index);
  166. $string = substr($string, $index);
  167. return $substr;
  168. }
  169. /**
  170. * Convert a private key to the appropriate format.
  171. *
  172. * @access public
  173. * @param \phpseclib\Math\BigInteger $n
  174. * @param \phpseclib\Math\BigInteger $e
  175. * @param \phpseclib\Math\BigInteger $d
  176. * @param array $primes
  177. * @param array $exponents
  178. * @param array $coefficients
  179. * @param string $password optional
  180. * @return string
  181. */
  182. static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, $primes, $exponents, $coefficients, $password = '')
  183. {
  184. if (count($primes) != 2) {
  185. return false;
  186. }
  187. $raw = array(
  188. 'modulus' => $n->toBytes(true),
  189. 'publicExponent' => $e->toBytes(true),
  190. 'privateExponent' => $d->toBytes(true),
  191. 'prime1' => $primes[1]->toBytes(true),
  192. 'prime2' => $primes[2]->toBytes(true),
  193. 'exponent1' => $exponents[1]->toBytes(true),
  194. 'exponent2' => $exponents[2]->toBytes(true),
  195. 'coefficient' => $coefficients[2]->toBytes(true)
  196. );
  197. $key = "PuTTY-User-Key-File-2: ssh-rsa\r\nEncryption: ";
  198. $encryption = (!empty($password) || is_string($password)) ? 'aes256-cbc' : 'none';
  199. $key.= $encryption;
  200. $key.= "\r\nComment: " . self::$comment . "\r\n";
  201. $public = pack(
  202. 'Na*Na*Na*',
  203. strlen('ssh-rsa'),
  204. 'ssh-rsa',
  205. strlen($raw['publicExponent']),
  206. $raw['publicExponent'],
  207. strlen($raw['modulus']),
  208. $raw['modulus']
  209. );
  210. $source = pack(
  211. 'Na*Na*Na*Na*',
  212. strlen('ssh-rsa'),
  213. 'ssh-rsa',
  214. strlen($encryption),
  215. $encryption,
  216. strlen(self::$comment),
  217. self::$comment,
  218. strlen($public),
  219. $public
  220. );
  221. $public = Base64::encode($public);
  222. $key.= "Public-Lines: " . ((strlen($public) + 63) >> 6) . "\r\n";
  223. $key.= chunk_split($public, 64);
  224. $private = pack(
  225. 'Na*Na*Na*Na*',
  226. strlen($raw['privateExponent']),
  227. $raw['privateExponent'],
  228. strlen($raw['prime1']),
  229. $raw['prime1'],
  230. strlen($raw['prime2']),
  231. $raw['prime2'],
  232. strlen($raw['coefficient']),
  233. $raw['coefficient']
  234. );
  235. if (empty($password) && !is_string($password)) {
  236. $source.= pack('Na*', strlen($private), $private);
  237. $hashkey = 'putty-private-key-file-mac-key';
  238. } else {
  239. $private.= Random::string(16 - (strlen($private) & 15));
  240. $source.= pack('Na*', strlen($private), $private);
  241. $crypto = new AES();
  242. $crypto->setKey(static::generateSymmetricKey($password, 32));
  243. $crypto->setIV(str_repeat("\0", $crypto->getBlockLength() >> 3));
  244. $crypto->disablePadding();
  245. $private = $crypto->encrypt($private);
  246. $hashkey = 'putty-private-key-file-mac-key' . $password;
  247. }
  248. $private = Base64::encode($private);
  249. $key.= 'Private-Lines: ' . ((strlen($private) + 63) >> 6) . "\r\n";
  250. $key.= chunk_split($private, 64);
  251. $hash = new Hash('sha1');
  252. $hash->setKey(sha1($hashkey, true));
  253. $key.= 'Private-MAC: ' . Hex::encode($hash->hash($source)) . "\r\n";
  254. return $key;
  255. }
  256. /**
  257. * Convert a public key to the appropriate format
  258. *
  259. * @access public
  260. * @param \phpseclib\Math\BigInteger $n
  261. * @param \phpseclib\Math\BigInteger $e
  262. * @return string
  263. */
  264. static function savePublicKey(BigInteger $n, BigInteger $e)
  265. {
  266. $n = $n->toBytes(true);
  267. $e = $e->toBytes(true);
  268. $key = pack(
  269. 'Na*Na*Na*',
  270. strlen('ssh-rsa'),
  271. 'ssh-rsa',
  272. strlen($e),
  273. $e,
  274. strlen($n),
  275. $n
  276. );
  277. $key = "---- BEGIN SSH2 PUBLIC KEY ----\r\n" .
  278. 'Comment: "' . str_replace(array('\\', '"'), array('\\\\', '\"'), self::$comment) . "\"\r\n";
  279. chunk_split(Base64::encode($key), 64) .
  280. '---- END SSH2 PUBLIC KEY ----';
  281. return $key;
  282. }
  283. }