package net.i2p.imagegen; /* * * Translated to Java and modified from: * * gnutls lib/extras/randomart.c * */ /* $OpenBSD: key.c,v 1.98 2011/10/18 04:58:26 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. * Copyright (c) 2008 Alexander von Gernler. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ import net.i2p.data.DataHelper; /** * Draw an ASCII-Art representing the fingerprint so human brain can * profit from its built-in pattern recognition ability. * This technique is called "random art" and can be found in some * scientific publications like this original paper: * * "Hash Visualization: a New Technique to improve Real-World Security", * Perrig A. and Song D., 1999, International Workshop on Cryptographic * Techniques and E-Commerce (CrypTEC '99) * sparrow.ece.cmu.edu/~adrian/projects/validation/validation.pdf * * The subject came up in a talk by Dan Kaminsky, too. * * If you see the picture is different, the key is different. * If the picture looks the same, you still know nothing. * * The algorithm used here is a worm crawling over a discrete plane, * leaving a trace (augmenting the field) everywhere it goes. * Movement is taken from dgst_raw 2bit-wise. Bumping into walls * makes the respective movement vector be ignored for this turn. * Graphs are not unambiguous, because circles in graphs can be * walked in either direction. * * @since 0.9.25 */ public class RandomArt { /* * Field sizes for the random art. Have to be odd, so the starting point * can be in the exact middle of the picture, and FLDBASE should be >=8 . * Else pictures would be too dense, and drawing the frame would * fail, too, because the key type would not fit in anymore. */ private static final int FLDBASE = 8; private static final int FLDSIZE_Y = FLDBASE + 1; private static final int FLDSIZE_X = FLDBASE * 2 + 1; /* * Chars to be used after each other every time the worm * intersects with itself. Matter of taste. */ private static final String A_augmentation_string = " .o+=*BOX@%&#/^SE"; // https://en.wikipedia.org/wiki/Miscellaneous_Symbols private static final String U_augmentation_string = " \u2600\u2601\u2602\u2603" + "\u2604\u2605\u2606\u2607" + "\u2608\u2609\u260a\u260b" + "\u260c\u260d\u260e\u260f"; private static final char A_BOX_TOP = '-'; private static final char A_BOX_BOTTOM = '-'; private static final char A_BOX_LEFT = '|'; private static final char A_BOX_RIGHT = '|'; private static final char A_BOX_TL = '+'; private static final char A_BOX_TR = '+'; private static final char A_BOX_BL = '+'; private static final char A_BOX_BR = '+'; // https://en.wikipedia.org/wiki/Box-drawing_characters // these are the thin singles private static final char U_BOX_TOP = '\u2500'; private static final char U_BOX_BOTTOM = '\u2500'; private static final char U_BOX_LEFT = '\u2502'; private static final char U_BOX_RIGHT = '\u2502'; private static final char U_BOX_TL = '\u250c'; private static final char U_BOX_TR = '\u2510'; private static final char U_BOX_BL = '\u2514'; private static final char U_BOX_BR = '\u2518'; private static final int BASE = 0x778899; /** * @param dgst_raw the data to be visualized, recommend 64 bytes or less * @param key_type output in the first line, recommend 6 chars or less * @param key_size output in the first line * @param prefix if non-null, prepend to every line */ public static String gnutls_key_fingerprint_randomart(final byte[] dgst_raw, final String key_type, final int key_size, final String prefix, final boolean unicode, final boolean html) { final String augmentation_string = unicode ? U_augmentation_string : A_augmentation_string; final char BOX_TOP = unicode ? U_BOX_TOP : A_BOX_TOP; final char BOX_BOTTOM = unicode ? U_BOX_BOTTOM : A_BOX_BOTTOM; final char BOX_LEFT = unicode ? U_BOX_LEFT : A_BOX_LEFT; final char BOX_RIGHT = unicode ? U_BOX_RIGHT : A_BOX_RIGHT; final char BOX_TL = unicode ? U_BOX_TL : A_BOX_TL; final char BOX_TR = unicode ? U_BOX_TR : A_BOX_TR; final char BOX_BL = unicode ? U_BOX_BL : A_BOX_BL; final char BOX_BR = unicode ? U_BOX_BR : A_BOX_BR; final String NL = System.getProperty("line.separator"); final int dgst_raw_len = dgst_raw.length; final byte[][] field = new byte[FLDSIZE_X][FLDSIZE_Y]; final byte[][] color = new byte[FLDSIZE_X][FLDSIZE_Y]; final int len = augmentation_string.length() - 1; int prefix_len = 0; if (prefix != null) prefix_len = prefix.length(); int x = FLDSIZE_X / 2; int y = FLDSIZE_Y / 2; /* process raw key */ for (int i = 0; i < dgst_raw_len; i++) { int input; /* each byte conveys four 2-bit move commands */ input = dgst_raw[i]; for (int b = 0; b < 4; b++) { /* evaluate 2 bit, rest is shifted later */ x += ((input & 0x1) != 0) ? 1 : -1; y += ((input & 0x2) != 0) ? 1 : -1; /* assure we are still in bounds */ x = Math.max(x, 0); y = Math.max(y, 0); x = Math.min(x, FLDSIZE_X - 1); y = Math.min(y, FLDSIZE_Y - 1); /* augment the field */ if ((field[x][y] & 0xff) < len - 2) field[x][y]++; color[x][y] = (byte) i; input = input >> 2; } } /* mark starting point and end point */ field[FLDSIZE_X / 2][FLDSIZE_Y / 2] = (byte) (len - 1); field[x][y] = (byte) len; final String size_txt; if (key_size > 0) size_txt = String.format(" %4d", key_size); else size_txt = ""; /* fill in retval */ StringBuilder retval = new StringBuilder(1024); long base = 0; if (html) { // Pick a color base. We use the first 3 bytes of the data, // but normalize by 75% since we're designed for a white background. int clen = Math.min(3, dgst_raw_len); byte[] cbase = new byte[clen]; for (int i = 0; i < clen; i++) { cbase[i] = (byte) ((dgst_raw[i] & 0xff) * 5 / 8); } base = DataHelper.fromLong(cbase, 0, clen); retval.append("<font color=\"#") .append(getColor(base, 0)) .append("\"><pre>\n"); } if (prefix_len > 0) retval.append(String.format("%s" + BOX_TL + BOX_TOP + BOX_TOP + "[%4s%s]", prefix, key_type, size_txt)); else retval.append(String.format("" + BOX_TL + BOX_TOP + BOX_TOP + "[%4s%s]", key_type, size_txt)); /* output upper border */ for (int i = 0; i < FLDSIZE_X - Math.max(key_type.length(), 4) - 9; i++) retval.append(BOX_TOP); retval.append(BOX_TR); retval.append(NL); if (prefix_len > 0) { retval.append(prefix); } /* output content */ for (y = 0; y < FLDSIZE_Y; y++) { retval.append(BOX_LEFT); for (x = 0; x < FLDSIZE_X; x++) { int idx = Math.min(field[x][y], len); if (html && idx != 0) retval.append("<span style=\"color: #") .append(getColor(base, color[x][y] & 0xff)) .append("\">"); retval.append(augmentation_string.charAt(idx)); if (html && idx != 0) retval.append("</span>"); } retval.append(BOX_RIGHT); retval.append(NL); if (prefix_len > 0) { retval.append(prefix); } } /* output lower border */ retval.append(BOX_BL); for (int i = 0; i < FLDSIZE_X; i++) retval.append(BOX_BOTTOM); retval.append(BOX_BR); retval.append(NL); if (html) retval.append("</pre></font>\n"); return retval.toString(); } private static String getColor(long base, int mod) { if (mod != 0) { //base += mod * 16; //base += mod * 16 * 256; base += mod * 5 * 256 * 256; } if (base > 0xffffff || base < 0) base &= 0xffffff; return String.format("%06x", base); } public static void main(String[] args) { try { boolean uni = true; boolean html = true; if (html) System.out.println("<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"></head><body>"); byte[] b = new byte[16]; net.i2p.util.RandomSource.getInstance().nextBytes(b); String art = gnutls_key_fingerprint_randomart(b, "SHA", 128, null, uni, html); System.out.println(art); System.out.println(""); b = new byte[32]; for (int i = 0; i < 5; i++) { net.i2p.util.RandomSource.getInstance().nextBytes(b); art = gnutls_key_fingerprint_randomart(b, "XSHA", 256, null, uni, html); System.out.println(art); System.out.println(""); } b = new byte[48]; net.i2p.util.RandomSource.getInstance().nextBytes(b); art = gnutls_key_fingerprint_randomart(b, "XXSHA", 384, null, uni, html); System.out.println(art); System.out.println(""); b = new byte[64]; net.i2p.util.RandomSource.getInstance().nextBytes(b); art = gnutls_key_fingerprint_randomart(b, "XXXSHA", 512, null, uni, html); System.out.println(art); if (html) System.out.println("</body></html>"); } catch (RuntimeException e) { e.printStackTrace(); } } }