// -*- mode: JDE; c-file-style: \"stroustrup\"; c-basic-offset: 2; -*- /*********************************************************************** * @(#)Crypter.java * * Requires: java 1.1.5 (or higher) * * No Copyright (!c) * * Crypter is a wrapper applet, that provides the interface for doing * online en/de-cryption with your web-browser. * * It imports only one non-standard class: CryptoModule.java * This has to provide the field * * public String keyNameString * * and the two methods: * * public boolean[] encrypt(boolean[] message) * public boolean[] decrypt(boolean[] cryptoText) * * So you can easily replace it with a crypto algorithm of your coice. * (At the moment, this is just hard wired.) * This file just contains the wrapper. All cryptographic stuff is going * on in CryptoModule.java * * References: * * [CS98] Ronald Cramer and Victor Shoup: A practical public key crypto * system provably secure against adaptive chosen ciphertext attack * in proceedings of Crypto 1998, LNCS 1462, p.13ff * */ import java.net.*; import java.awt.*; import java.awt.event.*; import java.io.File; import java.io.InputStreamReader; import java.io.FileInputStream; import java.io.OutputStream; import java.io.FileOutputStream; import java.io.OutputStreamWriter; // ==== Here is the Cryptographic Algorithm: // import CryptoModule; // (not necessary to import it, since its in the same directory) // ======================================== /************************************************************* * Contains all global constants *************************************************************/ class CONST { final static String version = "2.1." + new String("$Revision: 1.15 $").substring(13).replace('$',' '); public static boolean truncateAfterTERMINATOR = true; public static boolean ignoreLeftoverBits = true; public static boolean verbose = false; public static int bitsPerByte = 8; } /************************************************************ * This is the wrapper that allows to to communicate with the * en/de-cryption algorithm implemented in CryptoModule.java * * It will call en/decryption * * @author M. Oliver Möller * @begun 99/09/26 * @version $Revision: 1.15 $ $Date: 2004/11/10 08:53:30 $ ************************************************************/ public class Crypter extends java.applet.Applet implements Runnable { private static Color lightGray = new Color(170,170,170); private static Listener listener; private static Base64Handler b64; private static Label topLabel; private CryptoModule cm; public String dummy; /** * Initialize the applet. */ public void init() { b64 = new Base64Handler(); cm = new CryptoModule(); resize(900,800); this.setLayout(new BorderLayout()); setBackground(lightGray); listener = new Listener(); //listener.show(); topLabel = new Label("-- Crypter " + CONST.version + " - Key: " + extractKeyName(cm.keyNameString) + " --", Label.CENTER); add(topLabel, "North"); add(listener,"Center"); } /** * Receive an input */ public void setMessageText(String s){ listener.inputArea.setText(s); } /** * Return current cryto text */ public String getCryptoText(){ return listener.outputArea.getText(); } /** * Return the crypto text * replace newlines with javascript equivalent */ public String getCryptoTextEscaped(){ return escapeNewlines(listener.outputArea.getText()); } /// //////////////////////////////////////// /// Auxillary /// //////////////////////////////////////// /** * Don't show the listener * (or, if already displayed, make invisible) */ public void hideInterface(){ listener.setVisible(false); this.setVisible(false); } /** * Parse String with Key Name */ private String extractKeyName(String s){ String res = s; while( (res.length() > 0) && !res.startsWith("Key-Name:")) res = res.substring(1); if( res.length() == 0 ) res = "*unknown*"; else { res = res.substring(9).trim(); res = res.substring(0,res.indexOf('\n')); } return res; } /** * replace NL by html equivalents */ private static String escapeNewlines(String s){ StringBuffer res = new StringBuffer(); char c; for(int i = 0; i < s.length(); i++){ c = s.charAt(i); if( c == '\n' ) res.append(" \n\r"); else res.append(c); } return res.toString(); } /** * Start Encryption */ public void callEncrypt(){ listener.messageLabel.setText("Encryption started (please wait)"); listener.outputArea.setText(""); repaint(); listener.outputArea.setText(cm.keyNameString + b64.bits2base64(cm.encrypt(b64.string2bitsPad(listener.inputArea.getText(), true)))); listener.messageLabel.setText("--- Encryption finished."); } /// //////////////////////////////////////// /// React to Buttons /// //////////////////////////////////////// public boolean action(Event evt, Object what) { if(evt.target instanceof Button){ String buttonName = (String)what; if( buttonName.equalsIgnoreCase("Clear Message")){ listener.inputArea.setText(""); listener.messageLabel.setText("Message Text Cleared."); } else if( buttonName.equalsIgnoreCase("Clear Crypto")){ listener.outputArea.setText(""); listener.messageLabel.setText("Cryto Text Cleared."); } else if( buttonName.equalsIgnoreCase("Encrypt")){ callEncrypt(); } else if (buttonName.equalsIgnoreCase("Decrypt")){ listener.messageLabel.setText("Decryption started (please wait)"); listener.inputArea.setText(""); repaint(); listener.inputArea.setText(// "Derived Message Text:\n\n" + b64.bits2stringTruncate(cm.decrypt(b64.base642bits(listener.outputArea.getText())), CONST.truncateAfterTERMINATOR)); listener.messageLabel.setText("--- Decryption finished."); } else if (buttonName.equalsIgnoreCase("Email Cryptotext")){ listener.messageLabel.setText("Email does not work yet. Please copy output Window and send it that way."); } } repaint(); return true; } public void update(Graphics g) { paint(g); } public void run() { this.repaint(); } public void stop() { } // -- COMMAND LINE EXECUTION --------------------------------------------- public static void main(String argv []){ try { Base64Handler b64 = new Base64Handler(); CryptoModule cm = new CryptoModule(); if( (argv.length < 2) || (argv.length > 4)) throw new Exception("ERROR: illegal number of arguments."); int argCount = 0; String command = argv[argCount++]; if((argv[argCount]).startsWith("-")){ int nBits = - (new Integer(argv[argCount])).intValue(); if( (nBits < 7) || (nBits > 17)){ throw new Exception("ERROR: number of bits set to an illegal value (" + nBits + ")."); } System.err.println("** using " + nBits + " bits per charakter."); CONST.bitsPerByte = nBits; argCount++; } String inFileName = argv[argCount++]; String outFileName = ""; String clearTextCharset; String inCharset; String outCharset; if(argv.length > argCount) outFileName= argv[argCount]; switch(CONST.bitsPerByte){ case 7: clearTextCharset = "US-ASCII"; break; case 8: clearTextCharset = "ISO-8859-1"; break; default: clearTextCharset = "UTF-16BE"; } StringBuffer inString = new StringBuffer(); String outString = "*"; char c; char[] cs = new char[1]; int i = 0; InputStreamReader in ; OutputStream dos; OutputStreamWriter dosw; // -- determine int/out charsets ------------------------- if( command.equals("e") || command.equals("encrypt")) { inCharset = clearTextCharset; outCharset = "US-ASCII"; } else if( command.equals("d") || command.equals("decrypt") || command.equals("u") || command.equals("untruncated-decrypt") ){ inCharset = "US-ASCII"; outCharset = clearTextCharset; } else throw new Exception("ERROR: Unknown command >>" + command + "<<"); // -- read input ----------------------------------------- in = new InputStreamReader(new FileInputStream(new File(inFileName)), inCharset); System.err.println("** Reading stream with encoding " + in.getEncoding()); while(in.ready()){ in.read(cs, 0, 1); inString.append(cs); } // -- execute --------------------------------------------- if( command.equals("e") || command.equals("encrypt") ){ System.err.print("** Encrypting..."); outString = cm.keyNameString + b64.bits2base64(cm.encrypt(b64.string2bitsPad(inString.toString(), true))); } else if( command.equals("d") || command.equals("decrypt") ){ System.err.print("** Decrypting..."); outString = b64.bits2stringTruncate(cm.decrypt(b64.base642bits(inString.toString())), true); } else if( command.equals("u") || command.equals("untruncated-decrypt") ){ System.err.print("** Decrypting (without truncation)..."); outString = b64.bits2stringTruncate(cm.decrypt(b64.base642bits(inString.toString())), false); } else throw new Exception("ERROR: Unknown command >>" + command + "<<"); System.err.println("done."); // -- output result --------------------------------------- if(outFileName.equals("")){ dos = System.out; } else { dos = new FileOutputStream(new File(outFileName)); } dosw = new OutputStreamWriter(dos, outCharset); System.err.println("** Writing stream with encoding " + dosw.getEncoding()); dosw.write(outString); dosw.close(); System.out.println(); } catch (Exception e){ e.printStackTrace(); printUsage();} } // -- AUX for command line ----------------------------------------------- private static void printUsage(){ System.out.println("Crypter V " + CONST.version + " \n"); System.out.println("USAGE: java Crypter COMMAND [-NAT] INFILE [OUTFILE]\n"); System.out.println(" COMMAND: one of e (encrypt) or d (decrypt)"); System.out.println(" or u (untruncated-decrypt)"); System.out.println(" INFILE can contain any sort of data."); System.out.println(" If option -NAT is set with 7 <= NAT <= 17,"); System.out.println(" a char has NAT bits (both for en/decryption)."); System.out.println(" If no OUTFILE is specified, the output goes to ."); System.exit(0); } // ----------------------------------------------------------------------- } class Listener extends Panel { WindowAdapter l; Button encryptButton,decryptButton,emailButton,clearButton,clearcButton; TextArea inputArea,outputArea; Panel controlPanel,inputPanel,outputPanel,upperPanel,lowerPanel; Label inputLabel,outputLabel,messageLabel; Listener(){ // Constructor inputLabel = new Label("Message Text:"); inputArea = new TextArea(""); try { //inputArea.setColumns(65); } catch (Exception e) {}; inputPanel = new Panel(); inputPanel.setLayout(new BorderLayout()); inputPanel.add(inputLabel, "North"); inputPanel.add(inputArea, "Center"); outputLabel = new Label("Crypto Text (as Base64):"); outputArea = new TextArea(""); try { //outputArea.setColumns(80); } catch (Exception e) {}; outputPanel = new Panel(); outputPanel.setLayout(new BorderLayout()); outputPanel.add(outputLabel, "North"); outputPanel.add(outputArea, "Center"); clearButton = new Button("Clear Message"); clearcButton = new Button("Clear Crypto"); encryptButton = new Button("Encrypt"); decryptButton = new Button("Decrypt"); emailButton = new Button("Email Cryptotext"); messageLabel = new Label("No messages."); controlPanel = new Panel(); controlPanel.setLayout(new GridLayout(5,1)); controlPanel.add(clearButton); controlPanel.add(clearcButton); controlPanel.add(encryptButton); controlPanel.add(decryptButton); //!disabled controlPanel.add(emailButton); upperPanel = new Panel(); upperPanel.setLayout(new BorderLayout()); upperPanel.add(inputPanel, "Center"); upperPanel.add(controlPanel, "East"); lowerPanel = new Panel(); lowerPanel.setLayout(new BorderLayout()); lowerPanel.add(outputPanel, "Center"); lowerPanel.add(messageLabel, "South"); this.setLayout(new GridLayout(2,1)); this.add(upperPanel); this.add(lowerPanel); } } /************************************************************ * This Class collects all the bitty details with converting * back and forth bit arraws, Strings and base64 encodings. * * There is nothing magical to it - it is just a pain in the * neck, so let's some object handle that. ************************************************************/ class Base64Handler { private static int linelength = 76; // lenght of encoding lines private static byte[] char2bits; private static char[] bits2char = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}; public Base64Handler(){ char2bits = new byte[256]; char2bits[65] = 0; char2bits[66] = 1; char2bits[67] = 2; char2bits[68] = 3; char2bits[69] = 4; char2bits[70] = 5; char2bits[71] = 6; char2bits[72] = 7; char2bits[73] = 8; char2bits[74] = 9; char2bits[75] = 10; char2bits[76] = 11; char2bits[77] = 12; char2bits[78] = 13; char2bits[79] = 14; char2bits[80] = 15; char2bits[81] = 16; char2bits[82] = 17; char2bits[83] = 18; char2bits[84] = 19; char2bits[85] = 20; char2bits[86] = 21; char2bits[87] = 22; char2bits[88] = 23; char2bits[89] = 24; char2bits[90] = 25; char2bits[97] = 26; char2bits[98] = 27; char2bits[99] = 28; char2bits[100] = 29; char2bits[101] = 30; char2bits[102] = 31; char2bits[103] = 32; char2bits[104] = 33; char2bits[105] = 34; char2bits[106] = 35; char2bits[107] = 36; char2bits[108] = 37; char2bits[109] = 38; char2bits[110] = 39; char2bits[111] = 40; char2bits[112] = 41; char2bits[113] = 42; char2bits[114] = 43; char2bits[115] = 44; char2bits[116] = 45; char2bits[117] = 46; char2bits[118] = 47; char2bits[119] = 48; char2bits[120] = 49; char2bits[121] = 50; char2bits[122] = 51; char2bits[48] = 52; char2bits[49] = 53; char2bits[50] = 54; char2bits[51] = 55; char2bits[52] = 56; char2bits[53] = 57; char2bits[54] = 58; char2bits[55] = 59; char2bits[56] = 60; char2bits[57] = 61; char2bits[43] = 62; char2bits[47] = 63; } public static boolean[] string2bitsPad(String s, boolean pad){ boolean[] bits ; int i; int count = 0; char b; if(pad) bits = new boolean[s.length()*CONST.bitsPerByte + 16]; else bits = new boolean[s.length()*CONST.bitsPerByte]; for(i = 0; i< s.length(); i++){ b = (char)s.charAt(i); if(CONST.bitsPerByte > 17) bits[count++] = ((b & 131072) != 0); if(CONST.bitsPerByte > 16) bits[count++] = ((b & 65536) != 0); if(CONST.bitsPerByte > 15) bits[count++] = ((b & 32768) != 0); if(CONST.bitsPerByte > 14) bits[count++] = ((b & 16384) != 0); if(CONST.bitsPerByte > 13) bits[count++] = ((b & 8192) != 0); if(CONST.bitsPerByte > 12) bits[count++] = ((b & 4096) != 0); if(CONST.bitsPerByte > 11) bits[count++] = ((b & 2048) != 0); if(CONST.bitsPerByte > 10) bits[count++] = ((b & 1024) != 0); if(CONST.bitsPerByte > 9) bits[count++] = ((b & 512) != 0); if(CONST.bitsPerByte > 8) bits[count++] = ((b & 256) != 0); if(CONST.bitsPerByte > 7) bits[count++] = ((b & 128) != 0) ; bits[count++] = ((b & 64) != 0); bits[count++] = ((b & 32) != 0); bits[count++] = ((b & 16) != 0); bits[count++] = ((b & 8) != 0); bits[count++] = ((b & 4) != 0); bits[count++] = ((b & 2) != 0); bits[count++] = ((b & 1) != 0); } if(pad){ // pad with fixed pattern: 1000 1000 1000 1000 bits[count++] = true; bits[count++] = false; bits[count++] = false; bits[count++] = false; bits[count++] = true; bits[count++] = false; bits[count++] = false; bits[count++] = false; bits[count++] = true; bits[count++] = false; bits[count++] = false; bits[count++] = false; bits[count++] = true; bits[count++] = false; bits[count++] = false; bits[count++] = false; } if(CONST.verbose){ System.err.println("\n -- Input String length: " + s.length()); System.err.println(" -- Input Bits: " + bits.length); } return bits; } public static boolean[] string2bits(String s){ return string2bitsPad(s, false); } public static String bits2string(boolean[] bits){ return bits2stringTruncate(bits, false); } public static String bits2stringTruncate(boolean[] bits, boolean truncate){ int i; String resString; int b = 0; char[] dummy = new char[1]; char[] chars ; int j = 0; int counter = 0; int validBits = bits.length; if(truncate){ i = validBits - 16; // padded with fixed pattern: 1000 1000 1000 1000 while( (i > 0) && (! (bits[i] && !bits[i+1] && !bits[i+2] && !bits[i+3] && bits[i+4] && !bits[i+4+1] && !bits[i+4+2] && !bits[i+4+3] && bits[i+8] && !bits[i+8+1] && !bits[i+8+2] && !bits[i+8+3] && bits[i+12] && !bits[i+12+1] && !bits[i+12+2] && !bits[i+12+3] ))){ i--; } if(i > 0){ validBits = i; } } chars = new char[(validBits)/(CONST.bitsPerByte)]; if(CONST.verbose) System.err.println("\n -- Output Bits: " + bits.length); for(i = 0; i < validBits; i++){ b = (2*b); if(bits[i])b = (int)(b|1); counter++; if(counter == CONST.bitsPerByte){ dummy[0] = (char)b; // result.append(new String(dummy)); chars[j++] = (char)b; counter = 0; b = 0;}} if((!CONST.ignoreLeftoverBits) && (counter > 0)){// do not ignore leftover bits b = (int)(((byte)b)<<(CONST.bitsPerByte-counter)); chars[j++] = (char)b; } while(j < chars.length) chars[j++] = '\0'; resString = new String(chars); if(CONST.verbose) System.err.println(" -- Output String length: " + resString.length()); return resString; } public static boolean[] base64Core2bits(String s){ boolean[] bits = new boolean[s.length()*6]; int i; int count = 0; byte b; for(i = 0; i< s.length(); i++){ b = char2bits[(int)s.charAt(i)]; bits[count++] = ((b & 32) != 0); bits[count++] = ((b & 16) != 0); bits[count++] = ((b & 8) != 0); bits[count++] = ((b & 4) != 0); bits[count++] = ((b & 2) != 0); bits[count++] = ((b & 1) != 0);} return bits; } public static String string2base64(String s){ return bits2base64(string2bits(s)); } public static String bits2base64(boolean[] bits){ int counter = 0; int linepos = 0; int i; int b = 0; StringBuffer result = new StringBuffer("Crypter Version: " + CONST.version + "\n" + "Bits per Char : " + CONST.bitsPerByte + "\n" + "begin-base64 CryptoText Input\r\n"); for(i = 0; i < bits.length; i++){ b = 2*b; if(bits[i])b = (b|1); counter++; if(counter == 6){ result.append(bits2char[b]); linepos++; if(linepos == linelength){ result.append("\n"); linepos = 0;} counter = 0; b = 0;}} if(counter > 0){ // take care of leftover bits b = b<<(6-counter); // shift it up front... result.append(bits2char[b]);} result.append("=\n==--==\n"); return result.toString(); } public boolean[] base642bits(String bs){ StringBuffer base64string = new StringBuffer(); boolean errorField[] = new boolean[0]; int i; int offset = 0; int terminator = 0; char c; int len = bs.length(); while((offset < len) && !bs.regionMatches(true,offset,"begin-base64",0,12))offset++; offset = bs.indexOf('\n',offset) + 1; terminator = bs.indexOf('=',offset); if((offset <= 0) || (terminator == -1)) return errorField; for(i=offset; i < terminator; i++){ c = bs.charAt(i); if((c != '\n') && (c != ' ')&&(c != '\t')&&(c != '\r')) base64string.append(c);} return base64Core2bits(base64string.toString()); } } /********************************************************************** * Changelog * * $Log: Crypter.java,v $ * Revision 1.15 2004/11/10 08:53:30 oli * advanced number to 2.1 * * Revision 1.14 2004/11/10 08:18:39 oli * fixed typo * * Revision 1.13 2004/11/10 08:17:08 oli * changed padding: now use bit pattern 1000 1000 1000 1000, * and assume that crypto modules pad with (01|10)* * * Revision 1.12 2004/07/12 20:51:50 oli * intermediate check-in: some tests fail. * * Revision 1.11 2004/07/07 17:47:38 oli * fixed: replaced unsupported method 'substring(,)' for StringBuffer * * Revision 1.10 2004/07/07 06:52:54 oli * removed Java 1.4 dependency again * * Revision 1.9 2004/07/07 06:16:28 oli * removed surplus Charset dependency * * Revision 1.8 2004/07/06 23:17:17 oli * changed: use java.nio (Java 1.4.2 or higher) to ensure * proper clear text file encoding * * Revision 1.7 2003/02/07 22:52:25 oli * cosmetic byte -> char * * Revision 1.6 2003/02/07 22:50:27 oli * removed import of CryptoModule (fails for newer Java versions) * * Revision 1.5 2003/02/07 07:24:50 oli * included automatic truncation after TERMINATOR string * * Revision 1.4 2003/02/06 22:25:41 oli * generalizes n-bits option; changed TERMINATOR string * * -- Log of changes from older versions -- * * 1.5 : updated to verify-it.default * 1.4 : added main() method for command-line exectution * shortened default encoding line length * made encoding apt for full 8 bits (binary files) * ignore whitespaces in Base64 parts * 1.3 : revoked setColumns (older Netscapes cannot handle) * 1.2 : made accessible for javascipt * 1.1 : added terminator Characters * **********************************************************************/