import java.io.*; import java.util.*; import java.security.*; import java.security.interfaces.*; import java.security.spec.*; import javax.crypto.*; import javax.crypto.spec.*; import javax.crypto.interfaces.*; import java.awt.Toolkit; import java.awt.datatransfer.*; /** * hmac - a little utility to compute HMACs on data; * the data and key may be obtained from files, from * the command line, or from the clipboard. Note that * this little program is really only suitable for * files of very modest length, because it attempts * to load the entire file into a byte array. *

* This utility accepts command-line options, and * prints HMAC values in hex. *

* For more information */ public class hmac { private File keyFile; private String keyString; private byte [] keyBytes; private File dataFile; private String dataString; private byte [] dataBytes; protected Clipboard clip; protected boolean verbose = false; protected boolean noHex = false; protected boolean reverse = false; protected String alg = DEFAULT_ALG; public static final int MIN_LENGTH = 8; public static final int BUF_LENGTH = 256; public static final String DEFAULT_ALG = "HMacMD5"; /** * Send a message to the user, with an exception. */ public static final void message(String s, Exception e) { System.err.println("hmac: " + s); if (e != null) { System.err.println("\texception was: " + e); e.printStackTrace(System.err); } } /** * Send a message to the user, no exception. */ public static final void message(String s) { message(s,null); } /** * Return true if the input argument character is * a digit, a space, or A-F. */ public static final boolean isHexStringChar(char c) { return (Character.isDigit(c) || Character.isWhitespace(c) || (("0123456789abcdefABCDEF".indexOf(c)) >= 0)); } /** * Return true if the argument string seems to be a * Hex data string, like "a0 13 2f ". Whitespace is * ignored. */ public static final boolean isHex(String sampleData) { for(int i = 0; i < sampleData.length(); i++) { if (!isHexStringChar(sampleData.charAt(i))) return false; } return true; } /** * Return true if the argument byte array seems to be a * Hex data string, like "a0 13 2f ". Only check as far * as a supplied length; if it seems to be hex for that * many (ascii) bytes, then return true. */ public static final boolean isHex(byte [] sampleData, int len) { for(int i = 0; i < len; i++) { if (!isHexStringChar((char)(sampleData[i]))) return false; } return true; } static final String hexDigitChars = "0123456789abcdef"; /** * Convert a hex string into an array of bytes. * The hex string can be all digits, or 1-octet * groups separated by blanks, or any mix thereof. * * @param str String to be converted */ public static final byte [] hexToByteArray(String str, boolean rev) { StringBuffer acc = new StringBuffer(str.length() + 1); int cx, rp, ff, val; char [] s = new char[str.length()]; str.toLowerCase().getChars(0, str.length(), s, 0); for(cx = str.length() - 1, ff = 0; cx >= 0; cx--) { if (hexDigitChars.indexOf(s[cx]) >= 0) { acc.append(s[cx]); ff++; } else { if ((ff % 2) > 0) acc.append('0'); ff = 0; } } if ((ff % 2) > 0) acc.append('0'); //System.out.println("Intermediate SB value is '" + acc.toString() + "'"); byte [] ret = new byte[acc.length() / 2]; for(cx = 0, rp = ret.length - 1; cx < acc.length(); cx++, rp--) { val = hexDigitChars.indexOf(acc.charAt(cx)); cx++; val += 16 * hexDigitChars.indexOf(acc.charAt(cx)); ret[rp] = (byte)val; } if (rev) { byte tmp; int fx, bx; for(fx = 0, bx = ret.length - 1; fx < (ret.length / 2); fx++, bx--) { tmp = ret[bx]; ret[bx] = ret[fx]; ret[fx] = tmp; } } return ret; } /** * Convert a byte array to a hex string of the format * "1f 30 b7". */ public static final String byteArrayToHex(byte [] a) { int hn, ln, cx; StringBuffer buf = new StringBuffer(a.length * 2); for(cx = 0; cx < a.length; cx++) { hn = ((int)(a[cx]) & 0x00ff) / 16; ln = ((int)(a[cx]) & 0x000f); buf.append(hexDigitChars.charAt(hn)); buf.append(hexDigitChars.charAt(ln)); buf.append(' '); } return buf.toString(); } /** * Accept a file name, and read that file as a string or * as raw data (read first N bytes to check it out). * If the file can't be read, then return null. If * we can't read at least minValidLength bytes, then * we declare that the file is invalid and return null. * All the real work is done in readDataStream. * * @see readDataStream */ public byte [] readDataFile(File f, int minValidLength) { InputStream fr = null; byte [] buf = null; int cc; try { if (f.getName().equals("-")) { fr = System.in; } else { fr = new FileInputStream(f); } } catch (IOException ie) { message("Could not read file " + f, ie); return null; } if (verbose) message("Reading file data from " + f); return readDataStream(fr, minValidLength); } public byte[] readDataStream(InputStream is, int minValidLength) { int cc; byte [] buf = new byte[BUF_LENGTH]; is = new BufferedInputStream(is); try { cc = is.read(buf, 0, minValidLength); if (cc < minValidLength) return null; } catch (IOException ie) { message("Could not read initial data", ie); try { is.close(); } catch (Exception e) { } return null; } boolean ishex = (noHex)?(false):(isHex(buf, cc)); ByteArrayOutputStream baos = new ByteArrayOutputStream(); baos.write(buf, 0, cc); while (cc > 0) { try { cc = is.read(buf); if (cc > 0) baos.write(buf, 0, cc); } catch (IOException ie) { message("Error read stream data", ie); try { is.close(); } catch (Exception e) { } return null; } } try { is.close(); } catch (IOException ie2) { } byte [] result; result = baos.toByteArray(); if (verbose) message("Read " + result.length + " bytes"); if (ishex) { result = hexToByteArray(new String(result), reverse); } return result; } /** * Read hex data from the clipboard and process it into * a byte array. */ public byte [] readDataClipboard() { byte [] result = null; try { if (clip == null) { clip = Toolkit.getDefaultToolkit().getSystemClipboard(); } Transferable contents; contents = clip.getContents(null); if (contents != null) { StringReader sr; sr = (StringReader)(contents.getTransferData(DataFlavor.plainTextFlavor)); StringBuffer sb = new StringBuffer(); char [] buf = new char[BUF_LENGTH]; int cc; do { cc = sr.read(buf, 0, BUF_LENGTH); if (cc > 0) sb.append(buf, 0, cc); } while(cc > 0); String s = sb.toString(); if (verbose) message("Got clipboard data: " + s); result = s.getBytes(); boolean ishex = (noHex)?(false):(isHex(result, 16)); if (ishex) { result = hexToByteArray(s, reverse); } } } catch (Exception te) { message("Transfer clipboard problem", te); } return result; } String [] usageLines = { "java hmac [options]", "", "where options are the following:", "\t-k keydata key data as a hex string", "\t-kc get key data as hex string from the clipboard", "\t-kf file get key data from specified file", "\t-d data data to hash as a hex string", "\t-dc get data to hash as hex string from the clipboard", "\t-df file get data to hash from specified file", "\t-a alg use specified HMAC algorithm, {HMacMD5, HMacSHA1}", "\t (default is HMacMD5)", "\t-r interpret hex strings in reverse", "\t (as strings rather than numbers)", "\t-B treat ALL files as binary data", "\t-v print verbose messages", "", "If a file name is given as '-' then read standard input. If no -d", "options are given, then we read standard input.", "", "Options are interpreted in the order given, including -r and -B;", "use them with care.", "", "For files and standard input, the program will inspect", "the data and decide whether it seems to", "be binary data or hex data, unless -B is given.", "", "Here are some example command lines:", " java hmac -kc -df capture1", " java hmac -k 1f031b78a0993d42 -df - -a HMacSHA1