OSDN Git Service

Initial import from http://code.google.com/p/connectbot/ (r416)
[android-x86/packages-apps-ConnectBot.git] / src / org / connectbot / util / PubkeyUtils.java
1 /*
2         ConnectBot: simple, powerful, open-source SSH client for Android
3         Copyright (C) 2007-2008 Kenny Root, Jeffrey Sharkey
4
5         This program is free software: you can redistribute it and/or modify
6         it under the terms of the GNU General Public License as published by
7         the Free Software Foundation, either version 3 of the License, or
8         (at your option) any later version.
9
10         This program is distributed in the hope that it will be useful,
11         but WITHOUT ANY WARRANTY; without even the implied warranty of
12         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13         GNU General Public License for more details.
14
15         You should have received a copy of the GNU General Public License
16         along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 package org.connectbot.util;
20
21 import java.io.IOException;
22 import java.math.BigInteger;
23 import java.security.AlgorithmParameters;
24 import java.security.InvalidAlgorithmParameterException;
25 import java.security.InvalidKeyException;
26 import java.security.Key;
27 import java.security.KeyFactory;
28 import java.security.KeyPair;
29 import java.security.MessageDigest;
30 import java.security.NoSuchAlgorithmException;
31 import java.security.PrivateKey;
32 import java.security.PublicKey;
33 import java.security.SecureRandom;
34 import java.security.interfaces.DSAParams;
35 import java.security.interfaces.DSAPrivateKey;
36 import java.security.interfaces.DSAPublicKey;
37 import java.security.interfaces.RSAPrivateCrtKey;
38 import java.security.interfaces.RSAPrivateKey;
39 import java.security.interfaces.RSAPublicKey;
40 import java.security.spec.DSAPublicKeySpec;
41 import java.security.spec.InvalidKeySpecException;
42 import java.security.spec.InvalidParameterSpecException;
43 import java.security.spec.KeySpec;
44 import java.security.spec.PKCS8EncodedKeySpec;
45 import java.security.spec.RSAPublicKeySpec;
46 import java.security.spec.X509EncodedKeySpec;
47 import java.util.Arrays;
48
49 import javax.crypto.BadPaddingException;
50 import javax.crypto.Cipher;
51 import javax.crypto.EncryptedPrivateKeyInfo;
52 import javax.crypto.IllegalBlockSizeException;
53 import javax.crypto.NoSuchPaddingException;
54 import javax.crypto.SecretKeyFactory;
55 import javax.crypto.spec.PBEKeySpec;
56 import javax.crypto.spec.PBEParameterSpec;
57 import javax.crypto.spec.SecretKeySpec;
58
59 import android.util.Log;
60
61 import com.trilead.ssh2.crypto.Base64;
62 import com.trilead.ssh2.signature.DSASHA1Verify;
63 import com.trilead.ssh2.signature.RSASHA1Verify;
64
65 public class PubkeyUtils {
66         public static final String PKCS8_START = "-----BEGIN PRIVATE KEY-----";
67         public static final String PKCS8_END = "-----END PRIVATE KEY-----";
68
69         // Size in bytes of salt to use.
70         private static final int SALT_SIZE = 8;
71
72         // Number of iterations for password hashing. PKCS#5 recommends 1000
73         private static final int ITERATIONS = 1000;
74
75         public static String formatKey(Key key){
76                 String algo = key.getAlgorithm();
77                 String fmt = key.getFormat();
78                 byte[] encoded = key.getEncoded();
79                 return "Key[algorithm=" + algo + ", format=" + fmt +
80                         ", bytes=" + encoded.length + "]";
81         }
82
83         public static String describeKey(Key key, boolean encrypted) {
84                 String desc = null;
85                 if (key instanceof RSAPublicKey) {
86                         int bits = ((RSAPublicKey)key).getModulus().bitLength();
87                         desc = "RSA " + String.valueOf(bits) + "-bit";
88                 } else if (key instanceof DSAPublicKey) {
89                         desc = "DSA 1024-bit";
90                 } else {
91                         desc = "Unknown Key Type";
92                 }
93
94                 if (encrypted)
95                         desc += " (encrypted)";
96
97                 return desc;
98         }
99
100         public static byte[] sha256(byte[] data) throws NoSuchAlgorithmException {
101                 return MessageDigest.getInstance("SHA-256").digest(data);
102         }
103
104         public static byte[] cipher(int mode, byte[] data, byte[] secret) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
105                 SecretKeySpec secretKeySpec = new SecretKeySpec(sha256(secret), "AES");
106                 Cipher c = Cipher.getInstance("AES");
107                 c.init(mode, secretKeySpec);
108                 return c.doFinal(data);
109         }
110
111         public static byte[] encrypt(byte[] cleartext, String secret) throws Exception {
112                 byte[] salt = new byte[SALT_SIZE];
113
114                 byte[] ciphertext = Encryptor.encrypt(salt, ITERATIONS, secret, cleartext);
115
116                 byte[] complete = new byte[salt.length + ciphertext.length];
117
118                 System.arraycopy(salt, 0, complete, 0, salt.length);
119                 System.arraycopy(ciphertext, 0, complete, salt.length, ciphertext.length);
120
121                 Arrays.fill(salt, (byte) 0x00);
122                 Arrays.fill(ciphertext, (byte) 0x00);
123
124                 return complete;
125         }
126
127         public static byte[] decrypt(byte[] complete, String secret) throws Exception {
128                 try {
129                         byte[] salt = new byte[SALT_SIZE];
130                         byte[] ciphertext = new byte[complete.length - salt.length];
131
132                         System.arraycopy(complete, 0, salt, 0, salt.length);
133                         System.arraycopy(complete, salt.length, ciphertext, 0, ciphertext.length);
134
135                         return Encryptor.decrypt(salt, ITERATIONS, secret, ciphertext);
136                 } catch (Exception e) {
137                         Log.d("decrypt", "Could not decrypt with new method", e);
138                         // We might be using the old encryption method.
139                         return cipher(Cipher.DECRYPT_MODE, complete, secret.getBytes());
140                 }
141         }
142
143         public static byte[] getEncodedPublic(PublicKey pk) {
144                 return new X509EncodedKeySpec(pk.getEncoded()).getEncoded();
145         }
146
147         public static byte[] getEncodedPrivate(PrivateKey pk) {
148                 return new PKCS8EncodedKeySpec(pk.getEncoded()).getEncoded();
149         }
150
151         public static byte[] getEncodedPrivate(PrivateKey pk, String secret) throws Exception {
152                 if (secret.length() > 0)
153                         return encrypt(getEncodedPrivate(pk), secret);
154                 else
155                         return getEncodedPrivate(pk);
156         }
157
158         public static PrivateKey decodePrivate(byte[] encoded, String keyType) throws NoSuchAlgorithmException, InvalidKeySpecException {
159                 PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(encoded);
160                 KeyFactory kf = KeyFactory.getInstance(keyType);
161                 return kf.generatePrivate(privKeySpec);
162         }
163
164         public static PrivateKey decodePrivate(byte[] encoded, String keyType, String secret) throws Exception {
165                 if (secret != null && secret.length() > 0)
166                         return decodePrivate(decrypt(encoded, secret), keyType);
167                 else
168                         return decodePrivate(encoded, keyType);
169         }
170
171         public static PublicKey decodePublic(byte[] encoded, String keyType) throws NoSuchAlgorithmException, InvalidKeySpecException {
172                 X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(encoded);
173                 KeyFactory kf = KeyFactory.getInstance(keyType);
174                 return kf.generatePublic(pubKeySpec);
175         }
176
177         public static KeyPair recoverKeyPair(byte[] encoded) throws NoSuchAlgorithmException, InvalidKeySpecException {
178                 KeySpec privKeySpec = new PKCS8EncodedKeySpec(encoded);
179                 KeySpec pubKeySpec;
180
181                 PrivateKey priv;
182                 PublicKey pub;
183                 KeyFactory kf;
184                 try {
185                         kf = KeyFactory.getInstance(PubkeyDatabase.KEY_TYPE_RSA);
186                         priv = kf.generatePrivate(privKeySpec);
187
188                         pubKeySpec = new RSAPublicKeySpec(((RSAPrivateCrtKey) priv)
189                                         .getModulus(), ((RSAPrivateCrtKey) priv)
190                                         .getPublicExponent());
191
192                         pub = kf.generatePublic(pubKeySpec);
193                 } catch (ClassCastException e) {
194                         kf = KeyFactory.getInstance(PubkeyDatabase.KEY_TYPE_DSA);
195                         priv = kf.generatePrivate(privKeySpec);
196
197                         DSAParams params = ((DSAPrivateKey) priv).getParams();
198
199                         // Calculate public key Y
200                         BigInteger y = params.getG().modPow(((DSAPrivateKey) priv).getX(),
201                                         params.getP());
202
203                         pubKeySpec = new DSAPublicKeySpec(y, params.getP(), params.getQ(),
204                                         params.getG());
205
206                         pub = kf.generatePublic(pubKeySpec);
207                 }
208
209                 return new KeyPair(pub, priv);
210         }
211
212         /*
213          * Trilead compatibility methods
214          */
215
216         public static Object convertToTrilead(PublicKey pk) {
217                 if (pk instanceof RSAPublicKey) {
218                         return new com.trilead.ssh2.signature.RSAPublicKey(
219                                         ((RSAPublicKey) pk).getPublicExponent(),
220                                         ((RSAPublicKey) pk).getModulus());
221                 } else if (pk instanceof DSAPublicKey) {
222                         DSAParams dp = ((DSAPublicKey) pk).getParams();
223                         return new com.trilead.ssh2.signature.DSAPublicKey(
224                                                 dp.getP(), dp.getQ(), dp.getG(), ((DSAPublicKey) pk).getY());
225                 }
226
227                 throw new IllegalArgumentException("PublicKey is not RSA or DSA format");
228         }
229
230         public static Object convertToTrilead(PrivateKey priv, PublicKey pub) {
231                 if (priv instanceof RSAPrivateKey) {
232                         return new com.trilead.ssh2.signature.RSAPrivateKey(
233                                         ((RSAPrivateKey) priv).getPrivateExponent(),
234                                         ((RSAPublicKey) pub).getPublicExponent(),
235                                         ((RSAPrivateKey) priv).getModulus());
236                 } else if (priv instanceof DSAPrivateKey) {
237                         DSAParams dp = ((DSAPrivateKey) priv).getParams();
238                         return new com.trilead.ssh2.signature.DSAPrivateKey(
239                                                 dp.getP(), dp.getQ(), dp.getG(), ((DSAPublicKey) pub).getY(),
240                                                 ((DSAPrivateKey) priv).getX());
241                 }
242
243                 throw new IllegalArgumentException("Key is not RSA or DSA format");
244         }
245
246         /*
247          * OpenSSH compatibility methods
248          */
249
250         public static String convertToOpenSSHFormat(PublicKey pk, String origNickname) throws IOException, InvalidKeyException {
251                 String nickname = origNickname;
252                 if (nickname == null)
253                         nickname = "connectbot@android";
254
255                 if (pk instanceof RSAPublicKey) {
256                         String data = "ssh-rsa ";
257                         data += String.valueOf(Base64.encode(RSASHA1Verify.encodeSSHRSAPublicKey(
258                                         (com.trilead.ssh2.signature.RSAPublicKey)convertToTrilead(pk))));
259                         return data + " " + nickname;
260                 } else if (pk instanceof DSAPublicKey) {
261                         String data = "ssh-dss ";
262                         data += String.valueOf(Base64.encode(DSASHA1Verify.encodeSSHDSAPublicKey(
263                                         (com.trilead.ssh2.signature.DSAPublicKey)convertToTrilead(pk))));
264                         return data + " " + nickname;
265                 }
266
267                 throw new InvalidKeyException("Unknown key type");
268         }
269
270         /*
271          * OpenSSH compatibility methods
272          */
273
274         /**
275          * @param trileadKey
276          * @return OpenSSH-encoded pubkey
277          */
278         public static byte[] extractOpenSSHPublic(Object trileadKey) {
279                 try {
280                         if (trileadKey instanceof com.trilead.ssh2.signature.RSAPrivateKey)
281                                 return RSASHA1Verify.encodeSSHRSAPublicKey(
282                                                 ((com.trilead.ssh2.signature.RSAPrivateKey) trileadKey).getPublicKey());
283                         else if (trileadKey instanceof com.trilead.ssh2.signature.DSAPrivateKey)
284                                 return DSASHA1Verify.encodeSSHDSAPublicKey(
285                                                 ((com.trilead.ssh2.signature.DSAPrivateKey) trileadKey).getPublicKey());
286                         else
287                                 return null;
288                 } catch (IOException e) {
289                         return null;
290                 }
291         }
292
293         public static String exportPEM(PrivateKey key, String secret) throws NoSuchAlgorithmException, InvalidParameterSpecException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, InvalidKeySpecException, IllegalBlockSizeException, IOException {
294                 StringBuilder sb = new StringBuilder();
295
296                 byte[] data = key.getEncoded();
297
298                 sb.append(PKCS8_START);
299                 sb.append('\n');
300
301                 if (secret != null) {
302                         byte[] salt = new byte[8];
303                         SecureRandom random = new SecureRandom();
304                         random.nextBytes(salt);
305
306                         PBEParameterSpec defParams = new PBEParameterSpec(salt, 1);
307                         AlgorithmParameters params = AlgorithmParameters.getInstance(key.getAlgorithm());
308
309                         params.init(defParams);
310
311                         PBEKeySpec pbeSpec = new PBEKeySpec(secret.toCharArray());
312
313                         SecretKeyFactory keyFact = SecretKeyFactory.getInstance(key.getAlgorithm());
314                         Cipher cipher = Cipher.getInstance(key.getAlgorithm());
315                         cipher.init(Cipher.WRAP_MODE, keyFact.generateSecret(pbeSpec), params);
316
317                         byte[] wrappedKey = cipher.wrap(key);
318
319                         EncryptedPrivateKeyInfo pinfo = new EncryptedPrivateKeyInfo(params, wrappedKey);
320
321                         data = pinfo.getEncoded();
322
323                         sb.append("Proc-Type: 4,ENCRYPTED\n");
324                         sb.append("DEK-Info: DES-EDE3-CBC,");
325                         sb.append(encodeHex(salt));
326                         sb.append("\n\n");
327                 }
328
329                 int i = sb.length();
330                 sb.append(Base64.encode(data));
331                 for (i += 63; i < sb.length(); i += 64) {
332                         sb.insert(i, "\n");
333                 }
334
335                 sb.append('\n');
336                 sb.append(PKCS8_END);
337                 sb.append('\n');
338
339                 return sb.toString();
340         }
341
342         final static private char hexDigit[] = { '0', '1', '2', '3', '4', '5', '6',
343                         '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
344         private static String encodeHex(byte[] bytes) {
345                 char[] hex = new char[bytes.length * 2];
346
347                 int i = 0;
348                 for (byte b : bytes) {
349                         hex[i++] = hexDigit[(b >> 4) & 0x0f];
350                         hex[i++] = hexDigit[b & 0x0f];
351                 }
352
353                 return new String(hex);
354         }
355 }