001: package ch.ethz.ssh2;
002:
003: import java.io.BufferedReader;
004: import java.io.CharArrayReader;
005: import java.io.CharArrayWriter;
006: import java.io.File;
007: import java.io.FileReader;
008: import java.io.IOException;
009: import java.io.RandomAccessFile;
010: import java.net.InetAddress;
011: import java.net.UnknownHostException;
012: import java.security.SecureRandom;
013: import java.util.Iterator;
014: import java.util.LinkedList;
015: import java.util.Vector;
016:
017: import ch.ethz.ssh2.crypto.Base64;
018: import ch.ethz.ssh2.crypto.digest.Digest;
019: import ch.ethz.ssh2.crypto.digest.HMAC;
020: import ch.ethz.ssh2.crypto.digest.MD5;
021: import ch.ethz.ssh2.crypto.digest.SHA1;
022: import ch.ethz.ssh2.signature.DSAPublicKey;
023: import ch.ethz.ssh2.signature.DSASHA1Verify;
024: import ch.ethz.ssh2.signature.RSAPublicKey;
025: import ch.ethz.ssh2.signature.RSASHA1Verify;
026:
027: /**
028: * The <code>KnownHosts</code> class is a handy tool to verify received server hostkeys
029: * based on the information in <code>known_hosts</code> files (the ones used by OpenSSH).
030: * <p>
031: * It offers basically an in-memory database for known_hosts entries, as well as some
032: * helper functions. Entries from a <code>known_hosts</code> file can be loaded at construction time.
033: * It is also possible to add more keys later (e.g., one can parse different
034: * <code>known_hosts<code> files).
035: * <p>
036: * It is a thread safe implementation, therefore, you need only to instantiate one
037: * <code>KnownHosts</code> for your whole application.
038: *
039: * @author Christian Plattner, plattner@inf.ethz.ch
040: * @version $Id: KnownHosts.java,v 1.5 2006/07/30 21:59:29 cplattne Exp $
041: */
042:
043: public class KnownHosts {
044: public static final int HOSTKEY_IS_OK = 0;
045: public static final int HOSTKEY_IS_NEW = 1;
046: public static final int HOSTKEY_HAS_CHANGED = 2;
047:
048: private class KnownHostsEntry {
049: String[] patterns;
050: Object key;
051:
052: KnownHostsEntry(String[] patterns, Object key) {
053: this .patterns = patterns;
054: this .key = key;
055: }
056: }
057:
058: private LinkedList publicKeys = new LinkedList();
059:
060: public KnownHosts() {
061: }
062:
063: public KnownHosts(char[] knownHostsData) throws IOException {
064: initialize(knownHostsData);
065: }
066:
067: public KnownHosts(File knownHosts) throws IOException {
068: initialize(knownHosts);
069: }
070:
071: /**
072: * Adds a single public key entry to the database. Note: this will NOT add the public key
073: * to any physical file (e.g., "~/.ssh/known_hosts") - use <code>addHostkeyToFile()</code> for that purpose.
074: * This method is designed to be used in a {@link ServerHostKeyVerifier}.
075: *
076: * @param hostnames a list of hostname patterns - at least one most be specified. Check out the
077: * OpenSSH sshd man page for a description of the pattern matching algorithm.
078: * @param serverHostKeyAlgorithm as passed to the {@link ServerHostKeyVerifier}.
079: * @param serverHostKey as passed to the {@link ServerHostKeyVerifier}.
080: * @throws IOException
081: */
082: public void addHostkey(String hostnames[],
083: String serverHostKeyAlgorithm, byte[] serverHostKey)
084: throws IOException {
085: if (hostnames == null)
086: throw new IllegalArgumentException(
087: "hostnames may not be null");
088:
089: if ("ssh-rsa".equals(serverHostKeyAlgorithm)) {
090: RSAPublicKey rpk = RSASHA1Verify
091: .decodeSSHRSAPublicKey(serverHostKey);
092:
093: synchronized (publicKeys) {
094: publicKeys.add(new KnownHostsEntry(hostnames, rpk));
095: }
096: } else if ("ssh-dss".equals(serverHostKeyAlgorithm)) {
097: DSAPublicKey dpk = DSASHA1Verify
098: .decodeSSHDSAPublicKey(serverHostKey);
099:
100: synchronized (publicKeys) {
101: publicKeys.add(new KnownHostsEntry(hostnames, dpk));
102: }
103: } else
104: throw new IOException("Unknwon host key type ("
105: + serverHostKeyAlgorithm + ")");
106: }
107:
108: /**
109: * Parses the given known_hosts data and adds entries to the database.
110: *
111: * @param knownHostsData
112: * @throws IOException
113: */
114: public void addHostkeys(char[] knownHostsData) throws IOException {
115: initialize(knownHostsData);
116: }
117:
118: /**
119: * Parses the given known_hosts file and adds entries to the database.
120: *
121: * @param knownHosts
122: * @throws IOException
123: */
124: public void addHostkeys(File knownHosts) throws IOException {
125: initialize(knownHosts);
126: }
127:
128: /**
129: * Generate the hashed representation of the given hostname. Useful for adding entries
130: * with hashed hostnames to a known_hosts file. (see -H option of OpenSSH key-gen).
131: *
132: * @param hostname
133: * @return the hashed representation, e.g., "|1|cDhrv7zwEUV3k71CEPHnhHZezhA=|Xo+2y6rUXo2OIWRAYhBOIijbJMA="
134: */
135: public static final String createHashedHostname(String hostname) {
136: SHA1 sha1 = new SHA1();
137:
138: byte[] salt = new byte[sha1.getDigestLength()];
139:
140: new SecureRandom().nextBytes(salt);
141:
142: byte[] hash = hmacSha1Hash(salt, hostname);
143:
144: String base64_salt = new String(Base64.encode(salt));
145: String base64_hash = new String(Base64.encode(hash));
146:
147: return new String("|1|" + base64_salt + "|" + base64_hash);
148: }
149:
150: private static final byte[] hmacSha1Hash(byte[] salt,
151: String hostname) {
152: SHA1 sha1 = new SHA1();
153:
154: if (salt.length != sha1.getDigestLength())
155: throw new IllegalArgumentException(
156: "Salt has wrong length (" + salt.length + ")");
157:
158: HMAC hmac = new HMAC(sha1, salt, salt.length);
159:
160: hmac.update(hostname.getBytes());
161:
162: byte[] dig = new byte[hmac.getDigestLength()];
163:
164: hmac.digest(dig);
165:
166: return dig;
167: }
168:
169: private final boolean checkHashed(String entry, String hostname) {
170: if (entry.startsWith("|1|") == false)
171: return false;
172:
173: int delim_idx = entry.indexOf('|', 3);
174:
175: if (delim_idx == -1)
176: return false;
177:
178: String salt_base64 = entry.substring(3, delim_idx);
179: String hash_base64 = entry.substring(delim_idx + 1);
180:
181: byte[] salt = null;
182: byte[] hash = null;
183:
184: try {
185: salt = Base64.decode(salt_base64.toCharArray());
186: hash = Base64.decode(hash_base64.toCharArray());
187: } catch (IOException e) {
188: return false;
189: }
190:
191: SHA1 sha1 = new SHA1();
192:
193: if (salt.length != sha1.getDigestLength())
194: return false;
195:
196: byte[] dig = hmacSha1Hash(salt, hostname);
197:
198: for (int i = 0; i < dig.length; i++)
199: if (dig[i] != hash[i])
200: return false;
201:
202: return true;
203: }
204:
205: private int checkKey(String remoteHostname, Object remoteKey) {
206: int result = HOSTKEY_IS_NEW;
207:
208: synchronized (publicKeys) {
209: Iterator i = publicKeys.iterator();
210:
211: while (i.hasNext()) {
212: KnownHostsEntry ke = (KnownHostsEntry) i.next();
213:
214: if (hostnameMatches(ke.patterns, remoteHostname) == false)
215: continue;
216:
217: boolean res = matchKeys(ke.key, remoteKey);
218:
219: if (res == true)
220: return HOSTKEY_IS_OK;
221:
222: result = HOSTKEY_HAS_CHANGED;
223: }
224: }
225: return result;
226: }
227:
228: private Vector getAllKeys(String hostname) {
229: Vector keys = new Vector();
230:
231: synchronized (publicKeys) {
232: Iterator i = publicKeys.iterator();
233:
234: while (i.hasNext()) {
235: KnownHostsEntry ke = (KnownHostsEntry) i.next();
236:
237: if (hostnameMatches(ke.patterns, hostname) == false)
238: continue;
239:
240: keys.addElement(ke.key);
241: }
242: }
243:
244: return keys;
245: }
246:
247: /**
248: * Try to find the preferred order of hostkey algorithms for the given hostname.
249: * Based on the type of hostkey that is present in the internal database
250: * (i.e., either <code>ssh-rsa</code> or <code>ssh-dss</code>)
251: * an ordered list of hostkey algorithms is returned which can be passed
252: * to <code>Connection.setServerHostKeyAlgorithms</code>.
253: *
254: * @param hostname
255: * @return <code>null</code> if no key for the given hostname is present or
256: * there are keys of multiple types present for the given hostname. Otherwise,
257: * an array with hostkey algorithms is returned (i.e., an array of length 2).
258: */
259: public String[] getPreferredServerHostkeyAlgorithmOrder(
260: String hostname) {
261: String[] algos = recommendHostkeyAlgorithms(hostname);
262:
263: if (algos != null)
264: return algos;
265:
266: InetAddress[] ipAdresses = null;
267:
268: try {
269: ipAdresses = InetAddress.getAllByName(hostname);
270: } catch (UnknownHostException e) {
271: return null;
272: }
273:
274: for (int i = 0; i < ipAdresses.length; i++) {
275: algos = recommendHostkeyAlgorithms(ipAdresses[i]
276: .getHostAddress());
277:
278: if (algos != null)
279: return algos;
280: }
281:
282: return null;
283: }
284:
285: private final boolean hostnameMatches(String[] hostpatterns,
286: String hostname) {
287: boolean isMatch = false;
288: boolean negate = false;
289:
290: hostname = hostname.toLowerCase();
291:
292: for (int k = 0; k < hostpatterns.length; k++) {
293: if (hostpatterns[k] == null)
294: continue;
295:
296: String pattern = null;
297:
298: /* In contrast to OpenSSH we also allow negated hash entries (as well as hashed
299: * entries in lines with multiple entries).
300: */
301:
302: if ((hostpatterns[k].length() > 0)
303: && (hostpatterns[k].charAt(0) == '!')) {
304: pattern = hostpatterns[k].substring(1);
305: negate = true;
306: } else {
307: pattern = hostpatterns[k];
308: negate = false;
309: }
310:
311: /* Optimize, no need to check this entry */
312:
313: if ((isMatch) && (negate == false))
314: continue;
315:
316: /* Now compare */
317:
318: if (pattern.charAt(0) == '|') {
319: if (checkHashed(pattern, hostname)) {
320: if (negate)
321: return false;
322: isMatch = true;
323: }
324: } else {
325: pattern = pattern.toLowerCase();
326:
327: if ((pattern.indexOf('?') != -1)
328: || (pattern.indexOf('*') != -1)) {
329: if (pseudoRegex(pattern.toCharArray(), 0, hostname
330: .toCharArray(), 0)) {
331: if (negate)
332: return false;
333: isMatch = true;
334: }
335: } else if (pattern.compareTo(hostname) == 0) {
336: if (negate)
337: return false;
338: isMatch = true;
339: }
340: }
341: }
342:
343: return isMatch;
344: }
345:
346: private void initialize(char[] knownHostsData) throws IOException {
347: BufferedReader br = new BufferedReader(new CharArrayReader(
348: knownHostsData));
349:
350: while (true) {
351: String line = br.readLine();
352:
353: if (line == null)
354: break;
355:
356: line = line.trim();
357:
358: if (line.startsWith("#"))
359: continue;
360:
361: String[] arr = line.split(" ");
362:
363: if (arr.length >= 3) {
364: if ((arr[1].compareTo("ssh-rsa") == 0)
365: || (arr[1].compareTo("ssh-dss") == 0)) {
366: String[] hostnames = arr[0].split(",");
367:
368: byte[] msg = Base64.decode(arr[2].toCharArray());
369:
370: addHostkey(hostnames, arr[1], msg);
371: }
372: }
373: }
374: }
375:
376: private void initialize(File knownHosts) throws IOException {
377: char[] buff = new char[512];
378:
379: CharArrayWriter cw = new CharArrayWriter();
380:
381: knownHosts.createNewFile();
382:
383: FileReader fr = new FileReader(knownHosts);
384:
385: while (true) {
386: int len = fr.read(buff);
387: if (len < 0)
388: break;
389: cw.write(buff, 0, len);
390: }
391:
392: fr.close();
393:
394: initialize(cw.toCharArray());
395: }
396:
397: private final boolean matchKeys(Object key1, Object key2) {
398: if ((key1 instanceof RSAPublicKey)
399: && (key2 instanceof RSAPublicKey)) {
400: RSAPublicKey savedRSAKey = (RSAPublicKey) key1;
401: RSAPublicKey remoteRSAKey = (RSAPublicKey) key2;
402:
403: if (savedRSAKey.getE().equals(remoteRSAKey.getE()) == false)
404: return false;
405:
406: if (savedRSAKey.getN().equals(remoteRSAKey.getN()) == false)
407: return false;
408:
409: return true;
410: }
411:
412: if ((key1 instanceof DSAPublicKey)
413: && (key2 instanceof DSAPublicKey)) {
414: DSAPublicKey savedDSAKey = (DSAPublicKey) key1;
415: DSAPublicKey remoteDSAKey = (DSAPublicKey) key2;
416:
417: if (savedDSAKey.getG().equals(remoteDSAKey.getG()) == false)
418: return false;
419:
420: if (savedDSAKey.getP().equals(remoteDSAKey.getP()) == false)
421: return false;
422:
423: if (savedDSAKey.getQ().equals(remoteDSAKey.getQ()) == false)
424: return false;
425:
426: if (savedDSAKey.getY().equals(remoteDSAKey.getY()) == false)
427: return false;
428:
429: return true;
430: }
431:
432: return false;
433: }
434:
435: private final boolean pseudoRegex(char[] pattern, int i,
436: char[] match, int j) {
437: /* This matching logic is equivalent to the one present in OpenSSH 4.1 */
438:
439: while (true) {
440: /* Are we at the end of the pattern? */
441:
442: if (pattern.length == i)
443: return (match.length == j);
444:
445: if (pattern[i] == '*') {
446: i++;
447:
448: if (pattern.length == i)
449: return true;
450:
451: if ((pattern[i] != '*') && (pattern[i] != '?')) {
452: while (true) {
453: if ((pattern[i] == match[j])
454: && pseudoRegex(pattern, i + 1, match,
455: j + 1))
456: return true;
457: j++;
458: if (match.length == j)
459: return false;
460: }
461: }
462:
463: while (true) {
464: if (pseudoRegex(pattern, i, match, j))
465: return true;
466: j++;
467: if (match.length == j)
468: return false;
469: }
470: }
471:
472: if (match.length == j)
473: return false;
474:
475: if ((pattern[i] != '?') && (pattern[i] != match[j]))
476: return false;
477:
478: i++;
479: j++;
480: }
481: }
482:
483: private String[] recommendHostkeyAlgorithms(String hostname) {
484: String preferredAlgo = null;
485:
486: Vector keys = getAllKeys(hostname);
487:
488: for (int i = 0; i < keys.size(); i++) {
489: String this Algo = null;
490:
491: if (keys.elementAt(i) instanceof RSAPublicKey)
492: this Algo = "ssh-rsa";
493: else if (keys.elementAt(i) instanceof DSAPublicKey)
494: this Algo = "ssh-dss";
495: else
496: continue;
497:
498: if (preferredAlgo != null) {
499: /* If we find different key types, then return null */
500:
501: if (preferredAlgo.compareTo(this Algo) != 0)
502: return null;
503:
504: /* OK, we found the same algo again, optimize */
505:
506: continue;
507: }
508: }
509:
510: /* If we did not find anything that we know of, return null */
511:
512: if (preferredAlgo == null)
513: return null;
514:
515: /* Now put the preferred algo to the start of the array.
516: * You may ask yourself why we do it that way - basically, we could just
517: * return only the preferred algorithm: since we have a saved key of that
518: * type (sent earlier from the remote host), then that should work out.
519: * However, imagine that the server is (for whatever reasons) not offering
520: * that type of hostkey anymore (e.g., "ssh-rsa" was disabled and
521: * now "ssh-dss" is being used). If we then do not let the server send us
522: * a fresh key of the new type, then we shoot ourself into the foot:
523: * the connection cannot be established and hence the user cannot decide
524: * if he/she wants to accept the new key.
525: */
526:
527: if (preferredAlgo.equals("ssh-rsa"))
528: return new String[] { "ssh-rsa", "ssh-dss" };
529:
530: return new String[] { "ssh-dss", "ssh-rsa" };
531: }
532:
533: /**
534: * Checks the internal hostkey database for the given hostkey.
535: * If no matching key can be found, then the hostname is resolved to an IP address
536: * and the search is repeated using that IP address.
537: *
538: * @param hostname the server's hostname, will be matched with all hostname patterns
539: * @param serverHostKeyAlgorithm type of hostkey, either <code>ssh-rsa</code> or <code>ssh-dss</code>
540: * @param serverHostKey the key blob
541: * @return <ul>
542: * <li><code>HOSTKEY_IS_OK</code>: the given hostkey matches an entry for the given hostname</li>
543: * <li><code>HOSTKEY_IS_NEW</code>: no entries found for this hostname and this type of hostkey</li>
544: * <li><code>HOSTKEY_HAS_CHANGED</code>: hostname is known, but with another key of the same type
545: * (man-in-the-middle attack?)</li>
546: * </ul>
547: * @throws IOException if the supplied key blob cannot be parsed or does not match the given hostkey type.
548: */
549: public int verifyHostkey(String hostname,
550: String serverHostKeyAlgorithm, byte[] serverHostKey)
551: throws IOException {
552: Object remoteKey = null;
553:
554: if ("ssh-rsa".equals(serverHostKeyAlgorithm)) {
555: remoteKey = RSASHA1Verify
556: .decodeSSHRSAPublicKey(serverHostKey);
557: } else if ("ssh-dss".equals(serverHostKeyAlgorithm)) {
558: remoteKey = DSASHA1Verify
559: .decodeSSHDSAPublicKey(serverHostKey);
560: } else
561: throw new IllegalArgumentException("Unknown hostkey type "
562: + serverHostKeyAlgorithm);
563:
564: int result = checkKey(hostname, remoteKey);
565:
566: if (result == HOSTKEY_IS_OK)
567: return result;
568:
569: InetAddress[] ipAdresses = null;
570:
571: try {
572: ipAdresses = InetAddress.getAllByName(hostname);
573: } catch (UnknownHostException e) {
574: return result;
575: }
576:
577: for (int i = 0; i < ipAdresses.length; i++) {
578: int newresult = checkKey(ipAdresses[i].getHostAddress(),
579: remoteKey);
580:
581: if (newresult == HOSTKEY_IS_OK)
582: return newresult;
583:
584: if (newresult == HOSTKEY_HAS_CHANGED)
585: result = HOSTKEY_HAS_CHANGED;
586: }
587:
588: return result;
589: }
590:
591: /**
592: * Adds a single public key entry to the a known_hosts file.
593: * This method is designed to be used in a {@link ServerHostKeyVerifier}.
594: *
595: * @param knownHosts the file where the publickey entry will be appended.
596: * @param hostnames a list of hostname patterns - at least one most be specified. Check out the
597: * OpenSSH sshd man page for a description of the pattern matching algorithm.
598: * @param serverHostKeyAlgorithm as passed to the {@link ServerHostKeyVerifier}.
599: * @param serverHostKey as passed to the {@link ServerHostKeyVerifier}.
600: * @throws IOException
601: */
602: public final static void addHostkeyToFile(File knownHosts,
603: String[] hostnames, String serverHostKeyAlgorithm,
604: byte[] serverHostKey) throws IOException {
605: if ((hostnames == null) || (hostnames.length == 0))
606: throw new IllegalArgumentException(
607: "Need at least one hostname specification");
608:
609: if ((serverHostKeyAlgorithm == null) || (serverHostKey == null))
610: throw new IllegalArgumentException();
611:
612: CharArrayWriter writer = new CharArrayWriter();
613:
614: for (int i = 0; i < hostnames.length; i++) {
615: if (i != 0)
616: writer.write(',');
617: writer.write(hostnames[i]);
618: }
619:
620: writer.write(' ');
621: writer.write(serverHostKeyAlgorithm);
622: writer.write(' ');
623: writer.write(Base64.encode(serverHostKey));
624: writer.write("\n");
625:
626: char[] entry = writer.toCharArray();
627:
628: RandomAccessFile raf = new RandomAccessFile(knownHosts, "rw");
629:
630: long len = raf.length();
631:
632: if (len > 0) {
633: raf.seek(len - 1);
634: int last = raf.read();
635: if (last != '\n')
636: raf.write('\n');
637: }
638:
639: raf.write(new String(entry).getBytes());
640: raf.close();
641: }
642:
643: /**
644: * Generates a "raw" fingerprint of a hostkey.
645: *
646: * @param type either "md5" or "sha1"
647: * @param keyType either "ssh-rsa" or "ssh-dss"
648: * @param hostkey the hostkey
649: * @return the raw fingerprint
650: */
651: static final private byte[] rawFingerPrint(String type,
652: String keyType, byte[] hostkey) {
653: Digest dig = null;
654:
655: if ("md5".equals(type)) {
656: dig = new MD5();
657: } else if ("sha1".equals(type)) {
658: dig = new SHA1();
659: } else
660: throw new IllegalArgumentException("Unknown hash type "
661: + type);
662:
663: if ("ssh-rsa".equals(keyType)) {
664: } else if ("ssh-dss".equals(keyType)) {
665: } else
666: throw new IllegalArgumentException("Unknown key type "
667: + keyType);
668:
669: if (hostkey == null)
670: throw new IllegalArgumentException("hostkey is null");
671:
672: dig.update(hostkey);
673: byte[] res = new byte[dig.getDigestLength()];
674: dig.digest(res);
675: return res;
676: }
677:
678: /**
679: * Convert a raw fingerprint to hex representation (XX:YY:ZZ...).
680: * @param fingerprint raw fingerprint
681: * @return the hex representation
682: */
683: static final private String rawToHexFingerprint(byte[] fingerprint) {
684: final char[] alpha = "0123456789abcdef".toCharArray();
685:
686: StringBuffer sb = new StringBuffer();
687:
688: for (int i = 0; i < fingerprint.length; i++) {
689: if (i != 0)
690: sb.append(':');
691: int b = fingerprint[i] & 0xff;
692: sb.append(alpha[b >> 4]);
693: sb.append(alpha[b & 15]);
694: }
695:
696: return sb.toString();
697: }
698:
699: /**
700: * Convert a raw fingerprint to bubblebabble representation.
701: * @param raw raw fingerprint
702: * @return the bubblebabble representation
703: */
704: static final private String rawToBubblebabbleFingerprint(byte[] raw) {
705: final char[] v = "aeiouy".toCharArray();
706: final char[] c = "bcdfghklmnprstvzx".toCharArray();
707:
708: StringBuffer sb = new StringBuffer();
709:
710: int seed = 1;
711:
712: int rounds = (raw.length / 2) + 1;
713:
714: sb.append('x');
715:
716: for (int i = 0; i < rounds; i++) {
717: if (((i + 1) < rounds) || ((raw.length) % 2 != 0)) {
718: sb.append(v[(((raw[2 * i] >> 6) & 3) + seed) % 6]);
719: sb.append(c[(raw[2 * i] >> 2) & 15]);
720: sb.append(v[((raw[2 * i] & 3) + (seed / 6)) % 6]);
721:
722: if ((i + 1) < rounds) {
723: sb.append(c[(((raw[(2 * i) + 1])) >> 4) & 15]);
724: sb.append('-');
725: sb.append(c[(((raw[(2 * i) + 1]))) & 15]);
726: // As long as seed >= 0, seed will be >= 0 afterwards
727: seed = ((seed * 5) + (((raw[2 * i] & 0xff) * 7) + (raw[(2 * i) + 1] & 0xff))) % 36;
728: }
729: } else {
730: sb.append(v[seed % 6]); // seed >= 0, therefore index positive
731: sb.append('x');
732: sb.append(v[seed / 6]);
733: }
734: }
735:
736: sb.append('x');
737:
738: return sb.toString();
739: }
740:
741: /**
742: * Convert a ssh2 key-blob into a human readable hex fingerprint.
743: * Generated fingerprints are identical to those generated by OpenSSH.
744: * <p>
745: * Example fingerprint: d0:cb:76:19:99:5a:03:fc:73:10:70:93:f2:44:63:47.
746:
747: * @param keytype either "ssh-rsa" or "ssh-dss"
748: * @param publickey key blob
749: * @return Hex fingerprint
750: */
751: public final static String createHexFingerprint(String keytype,
752: byte[] publickey) {
753: byte[] raw = rawFingerPrint("md5", keytype, publickey);
754: return rawToHexFingerprint(raw);
755: }
756:
757: /**
758: * Convert a ssh2 key-blob into a human readable bubblebabble fingerprint.
759: * The used bubblebabble algorithm (taken from OpenSSH) generates fingerprints
760: * that are easier to remember for humans.
761: * <p>
762: * Example fingerprint: xofoc-bubuz-cazin-zufyl-pivuk-biduk-tacib-pybur-gonar-hotat-lyxux.
763: *
764: * @param keytype either "ssh-rsa" or "ssh-dss"
765: * @param publickey key data
766: * @return Bubblebabble fingerprint
767: */
768: public final static String createBubblebabbleFingerprint(
769: String keytype, byte[] publickey) {
770: byte[] raw = rawFingerPrint("sha1", keytype, publickey);
771: return rawToBubblebabbleFingerprint(raw);
772: }
773: }
|