21 private static function parseHeaderAndBody($line)
23 $res = explode(
': ', $line, 2);
24 if (count($res) !== 2) {
25 return array(null, null);
30 private static function parsePPK($data)
36 str_replace(array(
"\r",
"\n\n"),
"\n", $data)
44 throw new \InvalidArgumentException(
'Invalid PPK');
46 list($header, $body) = self::parseHeaderAndBody($data[$pos++]);
47 if ($header ===
'PuTTY-User-Key-File-2') {
48 $metadata[
'type'] = $body;
49 } elseif (!strncasecmp($header,
'PuTTY-User-Key-File-', 20)) {
50 throw new \InvalidArgumentException(
'PuTTY key format too new');
52 throw new \InvalidArgumentException(
'not a PuTTY SSH-2 private key');
57 throw new \InvalidArgumentException(
'Invalid PPK');
59 list($header, $body) = self::parseHeaderAndBody($data[$pos++]);
60 if ($header !==
'Encryption' || !in_array($body, array(
'none',
'aes256-cbc'))) {
61 throw new \InvalidArgumentException(
'Invalid PPK');
63 $metadata[
'cipher'] = $body;
67 throw new \InvalidArgumentException(
'Invalid PPK');
69 list($header, $body) = self::parseHeaderAndBody($data[$pos++]);
70 if ($header !==
'Comment') {
71 throw new \InvalidArgumentException(
'Invalid PPK');
73 $metadata[
'comment'] = $body;
77 throw new \InvalidArgumentException(
'Invalid PPK');
79 list($header, $body) = self::parseHeaderAndBody($data[$pos++]);
80 if ($header !==
'Public-Lines' || !$body || !ctype_digit($body)) {
81 throw new \InvalidArgumentException(
'Invalid PPK');
84 if ($len < $pos + $lines) {
85 throw new \InvalidArgumentException(
'Invalid PPK');
87 $publicKey = array_slice($data, $pos, $lines);
88 $metadata[
'pub_key'] = base64_decode(implode(
'', $publicKey));
93 throw new \InvalidArgumentException(
'Invalid PPK');
95 list($header, $body) = self::parseHeaderAndBody($data[$pos++]);
96 if ($header !==
'Private-Lines' || !$body || !ctype_digit($body)) {
97 throw new \InvalidArgumentException(
'Invalid PPK');
100 if ($len < $pos + $lines) {
101 throw new \InvalidArgumentException(
'Invalid PPK');
103 $metadata[
'priv_key'] = base64_decode(
104 implode(
'', array_slice($data, $pos, $lines))
110 throw new \InvalidArgumentException(
'Invalid PPK');
112 list($header, $body) = self::parseHeaderAndBody($data[$pos++]);
113 if ($header !==
'Private-MAC' || !$body || !ctype_xdigit($body)) {
114 throw new \InvalidArgumentException(
'Invalid PPK');
116 $metadata[
'mac'] = pack(
'H*', $body);
122 public static function loadPublic($data)
124 if (!is_string($data)) {
125 throw new \InvalidArgumentException();
128 $metadata = self::parsePPK($data);
129 $decoder = new \fpoirotte\Pssht\Wire\Decoder();
130 $decoder->getBuffer()->push($metadata[
'pub_key']);
132 $algos = \fpoirotte\pssht\Algorithms::factory();
133 $cls = $algos->getClass(
'Key', $decoder->decodeString());
135 throw new \InvalidArgumentException();
137 return $cls::unserialize($decoder);
140 public static function loadPrivate($data, $passphrase =
'')
142 if (!is_string($data)) {
143 throw new \InvalidArgumentException();
146 if (!is_string($passphrase)) {
147 throw new \InvalidArgumentException();
150 $metadata = self::parsePPK($data);
153 if ($metadata[
'cipher'] !==
'none') {
155 if ($passphrase ===
'' ||
156 strlen($metadata[
'priv_key']) % $blockSize) {
157 throw new \InvalidArgumentException(
'Invalid PPK');
161 sha1(
"\x00\x00\x00\x00" . $passphrase,
true) .
162 sha1(
"\x00\x00\x00\x01" . $passphrase,
true);
164 $res = mcrypt_module_open(
173 substr($key, 0, mcrypt_enc_get_key_size($res)),
174 str_repeat(
"\x00", mcrypt_enc_get_iv_size($res))
177 $metadata[
'priv_key'] = mdecrypt_generic($res, $metadata[
'priv_key']);
178 mcrypt_generic_deinit($res);
179 mcrypt_module_close($res);
187 $metadata[
'comment'],
188 $metadata[
'pub_key'],
189 $metadata[
'priv_key'],
191 foreach ($fields as $value) {
192 $blob .= pack(
'N', strlen($value)) . $value;
194 $key =
'putty-private-key-file-mac-key';
195 if ($metadata[
'cipher'] !==
'none' && $passphrase !==
'') {
198 $mac = hash_hmac(
'sha1', $blob, sha1($key,
true),
true);
199 if ($mac !== $metadata[
'mac']) {
201 $metadata[
'priv_key'] = $blob = $key = $passphrase = null;
202 if ($metadata[
'cipher'] !==
'none') {
203 throw new \InvalidArgumentException(
'Wrong passphrase');
205 throw new \InvalidArgumentException(
'MAC failed');
210 $decoder = new \fpoirotte\Pssht\Wire\Decoder();
211 $decoder->getBuffer()->push($type[
'priv_key']);
212 switch ($metadata[
'type']) {
215 $private = $decoder->decodeMpint();
220 $private = $decoder->decodeMpint();
224 $metadata[
'priv_key'] = null;
225 throw new \InvalidArgumentException();
228 if ($private === null) {
229 $metadata[
'priv_key'] = null;
230 throw new \InvalidArgumentException();
234 $decoder = new \fpoirotte\Pssht\Wire\Decoder();
235 $decoder->getBuffer()->push($metadata[
'pub_key']);
237 $algos = \fpoirotte\pssht\Algorithms::factory();
238 $cls = $algos->getClass(
'Key', $decoder->decodeString());
240 $metadata[
'priv_key'] = null;
241 throw new \InvalidArgumentException();
243 return $cls::unserialize($decoder, $private);