/* * [PGPKeywords.java] * * Summary: Interconverts PGP keys is hex and words. * * Copyright: (c) 2011-2017 Roedy Green, Canadian Mind Products, http://mindprod.com * * Licence: This software may be copied and used freely for any purpose but military. * http://mindprod.com/contact/nonmil.html * * Requires: JDK 1.8+ * * Created with: JetBrains IntelliJ IDEA IDE http://www.jetbrains.com/idea/ * * Version History: * 1.0 2011-02-16 initial version. */ package com.mindprod.pgpkeywords; import com.mindprod.common18.Build; import com.mindprod.common18.CMPAboutJBox; import com.mindprod.common18.FontFactory; import com.mindprod.common18.HybridJ; import com.mindprod.common18.JEButton; import com.mindprod.common18.Laf; import com.mindprod.common18.ST; import com.mindprod.common18.VersionCheck; import com.mindprod.fastcat.FastCat; import javax.swing.JApplet; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JTextArea; import javax.swing.JTextField; import java.awt.Color; import java.awt.Container; import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.regex.Pattern; /** * Interconverts PGP keys is hex and words. * * @author Roedy Green, Canadian Mind Products * @version 1.0 2011-02-16 initial version. * @since 2011-02-16 */ public class PGPKeywords extends JApplet { /** * height of Applet in pixels. Does not include bar at top, but does include menu bar. */ private static final int APPLET_HEIGHT = 500; /** * Width of Applet in pixels. */ private static final int APPLET_WIDTH = 800; private static final int FIRST_COPYRIGHT_YEAR = 2011; /** * undisplayed copyright notice * * @noinspection UnusedDeclaration */ private static final String EMBEDDED_COPYRIGHT = "Copyright: (c) 2011-2017 Roedy Green, Canadian Mind Products, http://mindprod.com"; /** * date this version released. */ private static final String RELEASE_DATE = "2011-02-16"; /** * sample private key to demonstrate us of program. */ private static final String SAMPLE_HEX_KEY = "9AA3 43B6 324D F154 4098 F58F EF62 A55F 92CB 3EDD"; /** * Title of the Applet */ private static final String TITLE_STRING = "PGP Keywords"; /** * Version of the Applet */ private static final String VERSION_STRING = "1.0"; private static final String[] evenWords = { "aardvark", "absurd", "accrue", "acme", "adrift", "adult", "afflict", "ahead", "aimless", "Algol", "allow", "alone", "ammo", "ancient", "apple", "artist", "assume", "Athens", "atlas", "Aztec", "baboon", "backfield", "backward", "banjo", "beaming", "bedlamp", "beehive", "beeswax", "befriend", "Belfast", "berserk", "billiard", "bison", "blackjack", "blockade", "blowtorch", "bluebird", "bombast", "bookshelf", "brackish", "breadline", "breakup", "brickyard", "briefcase", "Burbank", "button", "buzzard", "cement", "chairlift", "chatter", "checkup", "chisel", "choking", "chopper", "Christmas", "clamshell", "classic", "classroom", "cleanup", "clockwork", "cobra", "commence", "concert", "cowbell", "crackdown", "cranky", "crowfoot", "crucial", "crumpled", "crusade", "cubic", "dashboard", "deadbolt", "deckhand", "dogsled", "dragnet", "drainage", "dreadful", "drifter", "dropper", "drumbeat", "drunken", "Dupont", "dwelling", "eating", "edict", "egghead", "eightball", "endorse", "endow", "enlist", "erase", "escape", "exceed", "eyeglass", "eyetooth", "facial", "fallout", "flagpole", "flatfoot", "flytrap", "fracture", "framework", "freedom", "frighten", "gazelle", "Geiger", "glitter", "glucose", "goggles", "goldfish", "gremlin", "guidance", "hamlet", "highchair", "hockey", "indoors", "indulge", "inverse", "involve", "island", "jawbone", "keyboard", "kickoff", "kiwi", "klaxon", "locale", "lockup", "merit", "minnow", "miser", "Mohawk", "mural", "music", "necklace", "Neptune", "newborn", "nightbird", "Oakland", "obtuse", "offload", "optic", "orca", "payday", "peachy", "pheasant", "physique", "playhouse", "Pluto", "preclude", "prefer", "preshrunk", "printer", "prowler", "pupil", "puppy", "python", "quadrant", "quiver", "quota", "ragtime", "ratchet", "rebirth", "reform", "regain", "reindeer", "rematch", "repay", "retouch", "revenge", "reward", "rhythm", "ribcage", "ringbolt", "robust", "rocker", "ruffled", "sailboat", "sawdust", "scallion", "scenic", "scorecard", "Scotland", "seabird", "select", "sentence", "shadow", "shamrock", "showgirl", "skullcap", "skydive", "slingshot", "slowdown", "snapline", "snapshot", "snowcap", "snowslide", "solo", "southward", "soybean", "spaniel", "spearhead", "spellbind", "spheroid", "spigot", "spindle", "spyglass", "stagehand", "stagnate", "stairway", "standard", "stapler", "steamship", "sterling", "stockman", "stopwatch", "stormy", "sugar", "surmount", "suspense", "sweatband", "swelter", "tactics", "talon", "tapeworm", "tempest", "tiger", "tissue", "tonic", "topmost", "tracker", "transit", "trauma", "treadmill", "Trojan", "trouble", "tumor", "tunnel", "tycoon", "uncut", "unearth", "unwind", "uproot", "upset", "upshot", "vapor", "village", "virus", "Vulcan", "waffle", "wallet", "watchword", "wayside", "willow", "woodlark", "Zulu" }; private static final String[] oddWords = { "adroitness", "adviser", "aftermath", "aggregate", "alkali", "almighty", "amulet", "amusement", "antenna", "applicant", "Apollo", "armistice", "article", "asteroid", "Atlantic", "atmosphere", "autopsy", "Babylon", "backwater", "barbecue", "belowground", "bifocals", "bodyguard", "bookseller", "borderline", "bottomless", "Bradbury", "bravado", "Brazilian", "breakaway", "Burlington", "businessman", "butterfat", "Camelot", "candidate", "cannonball", "Capricorn", "caravan", "caretaker", "celebrate", "cellulose", "certify", "chambermaid", "Cherokee", "Chicago", "clergyman", "coherence", "combustion", "commando", "company", "component", "concurrent", "confidence", "conformist", "congregate", "consensus", "consulting", "corporate", "corrosion", "councilman", "crossover", "crucifix", "cumbersome", "customer", "Dakota", "decadence", "December", "decimal", "designing", "detector", "detergent", "determine", "dictator", "dinosaur", "direction", "disable", "disbelief", "disruptive", "distortion", "document", "embezzle", "enchanting", "enrollment", "enterprise", "equation", "equipment", "escapade", "Eskimo", "everyday", "examine", "existence", "exodus", "fascinate", "filament", "finicky", "forever", "fortitude", "frequency", "gadgetry", "Galveston", "getaway", "glossary", "gossamer", "graduate", "gravity", "guitarist", "hamburger", "Hamilton", "handiwork", "hazardous", "headwaters", "hemisphere", "hesitate", "hideaway", "holiness", "hurricane", "hydraulic", "impartial", "impetus", "inception", "indigo", "inertia", "infancy", "inferno", "informant", "insincere", "insurgent", "integrate", "intention", "inventive", "Istanbul", "Jamaica", "Jupiter", "leprosy", "letterhead", "liberty", "maritime", "matchmaker", "maverick", "Medusa", "megaton", "microscope", "microwave", "midsummer", "millionaire", "miracle", "misnomer", "molasses", "molecule", "Montana", "monument", "mosquito", "narrative", "nebula", "newsletter", "Norwegian", "October", "Ohio", "onlooker", "opulent", "Orlando", "outfielder", "Pacific", "pandemic", "Pandora", "paperweight", "paragon", "paragraph", "paramount", "passenger", "pedigree", "Pegasus", "penetrate", "perceptive", "performance", "pharmacy", "phonetic", "photograph", "pioneer", "pocketful", "politeness", "positive", "potato", "processor", "provincial", "proximate", "puberty", "publisher", "pyramid", "quantity", "racketeer", "rebellion", "recipe", "recover", "repellent", "replica", "reproduce", "resistor", "responsive", "retraction", "retrieval", "retrospect", "revenue", "revival", "revolver", "sandalwood", "sardonic", "Saturday", "savagery", "scavenger", "sensation", "sociable", "souvenir", "specialist", "speculate", "stethoscope", "stupendous", "supportive", "surrender", "suspicious", "sympathy", "tambourine", "telephone", "therapist", "tobacco", "tolerance", "tomorrow", "torpedo", "tradition", "travesty", "trombonist", "truncated", "typewriter", "ultimate", "undaunted", "underfoot", "unicorn", "unify", "universe", "unravel", "upcoming", "vacancy", "vagabond", "vertigo", "Virginia", "visitor", "vocalist", "voyager", "warranty", "Waterloo", "whimsical", "Wichita", "Wilmington", "Wyoming", "yesteryear", "Yucatan" }; /** * Applet background */ private static final Color BACKGROUND_FOR_APPLET = new Color( 0xfff4ff ); /** * background colour for input fields */ private static final Color BACKGROUND_FOR_INPUT = new Color( 0xffffff ); /** * foreground colour for input fields */ private static final Color FOREGROUND_FOR_INPUT = new Color( 0x1e90ff )/* dodger blue */; /** * the colour dark green. */ private static final Color FOREGROUND_FOR_INSTRUCTIONS = new Color( 0x008000 ); /** * foreground colour for labels */ private static final Color FOREGROUND_FOR_LABEL = new Color( 0x0000b0 ); /** * for titles */ private static final Color FOREGROUND_FOR_TITLE = new Color( 0xdc143c ); /** * font for input fields */ private static final Font FONT_FOR_EDITABLE_FIELDS = FontFactory.build( "Dialog", Font.BOLD, 17 ); /** * for for titles and About buttons */ private static final Font FONT_FOR_TITLE = FontFactory.build( "Dialog", Font.BOLD, 16 ); /** * for for titles and About buttons */ private static final Font FONT_FOR_TITLE2 = FontFactory.build( "Dialog", Font.PLAIN, 14 ); /** * parse individual keywords */ private static final Pattern SPLIT_ON_SPACE = Pattern.compile( "\\s+" ); /** * convert to hex button */ private JButton toHexButton; /** * convert to keywords button */ private JButton toKeywordsButton; /** * label for hex field */ private JLabel hexLabel; /** * instruction on how to use the program */ private JLabel instructions; /** * label for keywords field */ private JLabel keywordsLabel; /** * title on screen */ private JLabel title; /** * title, second line */ private JLabel title2; /** * convert to hex button */ private JTextArea keywords; /** * convert to keywords button */ private JTextField hex; /** * Constructor */ public PGPKeywords() { } /** * convert 40-bytes of hex to a list of 20 keywords * * @param hex hex string * * @return 5 lines of four words. */ private static String hexToKeywords( String hex ) { hex = ST.squish( hex ); if ( hex.length() != 40 ) { return hex.length() + " digits. Need 40 hex digits"; } if ( !ST.isLegal( hex, "0123456789abcedfABCDEF" ) ) { return "only 0-9 A-F or a-f and space allowed"; } // accumulate five lines of four words. final FastCat sb = new FastCat( 20 + 20 ); for ( int i = 0; i < 20; i++ ) { // convert next two hex digits. int wordIndex = Integer.parseInt( hex.substring( i * 2, i * 2 + 2 ), 16 ); String word = ( i & 1 ) == 0 ? evenWords[ wordIndex ] : oddWords[ wordIndex ]; sb.append( word ); if ( i % 4 == 3 ) { sb.append( '\n' ); } else { sb.append( ' ' ); } } return sb.toString(); } /** * convert 20 keywords to hex * * @param keywords list of 20 keywords * * @return one line of 40 hex chars, broken ten groups of four. */ private static String keywordsToHex( String keywords ) { String[] individualKeywords = SPLIT_ON_SPACE.split( keywords ); if ( individualKeywords.length != 20 ) { return individualKeywords.length + " keywords. Must have 20."; } StringBuilder sb = new StringBuilder( 2 * 20 + 10 ); int count = 0; for ( String ik : individualKeywords ) { boolean found = false; final String[] words = ( count & 1 ) == 0 ? evenWords : oddWords; for ( int i = 0; i < 256; i++ ) { if ( ik.equalsIgnoreCase( words[ i ] ) ) { sb.append( ST.toLZHexString( i, 2 ).toUpperCase() ); found = true; break; } } if ( !found ) { sb.append( "??" ); } if ( ( count & 1 ) != 0 ) { sb.append( " " ); } count++; } return sb.toString(); } /** * allocate all the components */ private void buildComponents() { title = new JLabel( TITLE_STRING + " " + VERSION_STRING ); title.setForeground( FOREGROUND_FOR_TITLE ); title.setFont( FONT_FOR_TITLE ); title2 = new JLabel( "released:" + RELEASE_DATE + " build:" + Build.BUILD_NUMBER ); title2.setFont( FONT_FOR_TITLE2 ); title2.setForeground( FOREGROUND_FOR_TITLE ); hexLabel = new JLabel( "hex:" ); hexLabel.setForeground( FOREGROUND_FOR_LABEL ); hex = new JTextField( SAMPLE_HEX_KEY, 40 ); hex.setEditable( true ); hex.setMargin( new Insets( 2, 2, 2, 2 ) ); hex.setFont( FONT_FOR_EDITABLE_FIELDS ); hex.setBackground( BACKGROUND_FOR_INPUT ); hex.setForeground( FOREGROUND_FOR_INPUT ); final boolean usingMac = System.getProperty( "os.name" ).equals( "Mac OS X" ); // prefer double to single arrow, must use single arrow on some Macs final char downArrow = usingMac ? '\u2193' : '\u21d3'; final char upArrow = usingMac ? '\u2191' : '\u21d1'; toKeywordsButton = new JEButton( "Convert to keywords " + downArrow ); toKeywordsButton.setToolTipText( "Convert hex representing PGP public key to keywords." ); toKeywordsButton.addActionListener( new ActionListener() { /** * called when the toKeywordsButton button hit. * @param e not used. */ public void actionPerformed( ActionEvent e ) { keywords.setText( hexToKeywords( hex.getText() ) ); } } ); keywordsLabel = new JLabel( "keywords:" ); keywordsLabel.setForeground( FOREGROUND_FOR_LABEL ); keywords = new JTextArea(); keywords.setEditable( true ); keywords.setMargin( new Insets( 2, 2, 2, 2 ) ); keywords.setFont( FONT_FOR_EDITABLE_FIELDS ); keywords.setBackground( BACKGROUND_FOR_INPUT ); keywords.setForeground( FOREGROUND_FOR_INPUT ); keywords.setLineWrap( true ); keywords.setWrapStyleWord( true ); toHexButton = new JEButton( "Convert to hex " + upArrow ); toHexButton.setToolTipText( "Convert keywords representing PGP public key to hex." ); toHexButton.addActionListener( new ActionListener() { /** * called when the toKeywordsButton button hit. * @param e not used. */ public void actionPerformed( ActionEvent e ) { hex.setText( keywordsToHex( keywords.getText() ) ); } } ); instructions = new JLabel( "Either: fill in the hex field and hit [convert to keywords " + downArrow + "], or fill in the keywords field and hit [convert to hex " + upArrow + "]." ); instructions.setForeground( FOREGROUND_FOR_INSTRUCTIONS ); } /** * build a menu with Look & Feel and About across the top */ private void buildMenu() { // turn on anti-alias System.setProperty( "swing.aatext", "true" ); final JMenuBar menubar = new JMenuBar(); setJMenuBar( menubar ); final JMenu lafMenu = Laf.buildLookAndFeelMenu(); if ( lafMenu != null ) { menubar.add( lafMenu ); } final JMenu menuHelp = new JMenu( "Help" ); menubar.add( menuHelp ); final JMenuItem aboutItem = new JMenuItem( "About" ); menuHelp.add( aboutItem ); aboutItem.addActionListener( new ActionListener() { public void actionPerformed( ActionEvent e ) { // open about frame new CMPAboutJBox( TITLE_STRING, VERSION_STRING, "Interconverts PGP keys between hex and keyword form", "", "freeware", RELEASE_DATE, FIRST_COPYRIGHT_YEAR, "Roedy Green", "PGPKEYWORDS", "1.8" ); } } ); } /** * layout fields in GridBag * * @param contentPane where to place the components */ private void layoutFields( Container contentPane ) { // 0_________1_____2__________3__ // title --------title2--------- 0 // ----------------------------- 1 // hex: hex -------toKeywordsButton- 2 // keywords: keywords ---toHexButton--3 // instructions--------------- 4 // // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( title, new GridBagConstraints( 0, 0, 2, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets( 10, 10, 0, 5 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( title2, new GridBagConstraints( 2, 0, 2, 1, 0.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets( 10, 5, 0, 10 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( hexLabel, new GridBagConstraints( 0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets( 5, 10, 0, 5 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( hex, new GridBagConstraints( 1, 2, 2, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets( 5, 5, 0, 5 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( toKeywordsButton, new GridBagConstraints( 3, 2, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets( 5, 5, 0, 10 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( keywordsLabel, new GridBagConstraints( 0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets( 5, 10, 0, 5 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( keywords, new GridBagConstraints( 1, 3, 2, 1, 100.0, 100.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets( 5, 5, 0, 5 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( toHexButton, new GridBagConstraints( 3, 3, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets( 5, 5, 0, 10 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( instructions, new GridBagConstraints( 1, 4, 4, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets( 5, 5, 10, 10 ), 0, 0 ) ); } /** * Allow this Applet to run as as application as well. * * @param args command line arguments ignored. */ public static void main( String args[] ) { HybridJ.fireup( new PGPKeywords(), TITLE_STRING + " " + VERSION_STRING, APPLET_WIDTH, APPLET_HEIGHT ); } // end main /** * Called by the browser or Applet viewer to inform * this Applet that it is being reclaimed and that it should destroy * any resources that it has allocated. */ @Override public void destroy() { toHexButton = null; toKeywordsButton = null; hexLabel = null; instructions = null; title = null; title2 = null; keywords = null; hex = null; } /** * Called by the browser or Applet viewer to inform * this Applet that it has been loaded into the system. */ @Override public void init() { Container contentPane = this.getContentPane(); if ( !VersionCheck.isJavaVersionOK( 1, 7, 0, contentPane ) ) { // effectively abort return; } buildMenu(); // also initial L&F contentPane.setLayout( new GridBagLayout() ); contentPane.setBackground( BACKGROUND_FOR_APPLET ); buildComponents(); layoutFields( contentPane ); this.validate(); this.setVisible( true ); } }