/* * [Password.java] * * Summary: Generates unguessable passwords. * * Copyright: (c) 2003-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 2003-06-23 initial version * 1.1 2006-01-01 * 1.2 2006-03-06 reformat with IntelliJ, add Javadoc * 1.3 2007-05-12 bigger type, add logo and pad, adjust spacing. * 1.4 2008-04-03 add build to title, tidy code * 1.5 2010-02-04 optionally includes punctuation in generated passwords. * 1.6 2010-07-28 ensure one char of each class appears in generated password. * 1.7 2011-12-01 configurable L&F, fine control over chars to include in passwords. * 1.8 2012-06-27 default length=12, default punct on, double check that one of each category * 1.9 2014-04-04 no longer use ? in passwords. Chapters Indigo rejects them. */ package com.mindprod.password; 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.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.JRadioButton; import javax.swing.JSpinner; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.SpinnerNumberModel; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import java.awt.Color; import java.awt.Container; import java.awt.Dimension; 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.security.SecureRandom; import java.util.Random; import static java.lang.System.*; /** * Generates unguessable passwords. *

* * @author Roedy Green, Canadian Mind Products * @version 1.9 2014-04-04 no longer use ? in passwords. Chapters Indigo rejects them. * @noinspection FieldCanBeLocal * @since 2003-06-23 */ public final class Password extends JApplet implements ActionListener, ChangeListener { /** * height of Applet box in pixels. Does not include surrounding frame. */ private static final int APPLET_HEIGHT = 420; /** * Width of Applet box in pixels. */ private static final int APPLET_WIDTH = 480; /** * basic font size */ private static final int basicFontSize = 14; private static final Font FONT_FOR_EDITABLE = FontFactory.build( "Dialog", Font.PLAIN, basicFontSize ); private static final Font FONT_FOR_LABEL = FontFactory.build( "Dialog", Font.PLAIN, basicFontSize ); private static final Font FONT_FOR_RESULT = FontFactory.build( "Dialog", Font.BOLD, basicFontSize + 2 ); // leave out 0 1 because they looks like O and I l /** * default length of generated passwords */ private static final int DEFAULT_PW_LENGTH = 12; private static final int FIRST_COPYRIGHT_YEAR = 2003; /** * leave out l. It/ looks too much like 1 * leave out 0. It looks too much like O* */ private static final String CAST_OF_DIGITS = "23456789"; // 8-possibilities /** * leave out l, looks too much like 1 */ private static final String CAST_OF_LOWER_CASE_LETTERS = "abcdefghijkmnopqrstuvwxyz"; // 25 possibilities /** * leave out | <>()[]{}/\ //,.:;? colon is often used to separate userid:pw */ private static final String CAST_OF_PUNCTUATION_LETTERS = "!#$%&*+-=@^`~"; // 13 possibilities. /** * leave out O, looks too much like 0, leave out I. */ private static final String CAST_OF_UPPER_CASE_LETTERS = "ABCDEFGHJKLMNPQRSTUVWXYZ"; // 24 possibilities /** * copyright not displayed * * @noinspection UnusedDeclaration */ private static final String EMBEDDED_COPYRIGHT = "Copyright: (c) 2003-2017 Roedy Green, Canadian Mind Products, http://mindprod.com"; private static final String RELEASE_DATE = "2014-04-04"; private static final String TITLE_STRING = "Password Generator"; private static final String VERSION_STRING = "1.9"; private static final Color BACKGROUND_FOR_APPLET = new Color( 0xfaf0e6 )/* linen */; /** * background colour for instructions. Default grey is too dark */ private static final Color BACKGROUND_FOR_INSTRUCTIONS = new Color( 0xf8f8f8 ); private static final Color BACKGROUND_FOR_LABEL = new Color( 0xfaf0e6 )/* linen */; private static final Color BACKGROUND_FOR_RESULT = new Color( 0xebfff7 ); private static final Color FOREGROUND_FOR_APPLET = new Color( 0x1e90ff )/* dodger blue */; /** * instruction normal color */ private static final Color FOREGROUND_FOR_INSTRUCTIONS = new Color( 0x339911 ); private static final Color FOREGROUND_FOR_LABEL = new Color( 0x0000b0 ); private static final Color FOREGROUND_FOR_RESULT = new Color( 0x226622 ); /** * for titles */ private static final Color FOREGROUND_FOR_TITLE = new Color( 0xdc143c ); /** * instructions font */ private static final Font FONT_FOR_INSTRUCTIONS = com.mindprod.common18.FontFactory.build( "Dialog", Font.PLAIN, 12 ); /** * for for titles and About buttons */ private static final Font FONT_FOR_TITLE = FontFactory.build( "Dialog", Font.BOLD, 16 ); /** * for for title second line */ private static final Font FONT_FOR_TITLE2 = FontFactory.build( "Dialog", Font.PLAIN, 14 ); /** * space to leave around the applet */ private static final int[] margin = { 12, 15, 12, 15 }/* t l b r */; /** * high quality random number generator, actually final, but compiler cannot be sure of that */ private static Random wheel; static { try { wheel = SecureRandom.getInstance( "SHA1PRNG", "SUN" ); } catch ( Exception e ) { out.println( "Reverting to lower quality random number generator" ); wheel = new Random(); } } /** * button to request another password */ private JButton another; /** * label for pwLength */ private JLabel pwLengthLabel; /** * Displayed title */ private JLabel title; /** * title, second line */ private JLabel title2; /** * radio button selected if include digits */ private JRadioButton includeDigits; /** * radio button selected if include lower case */ private JRadioButton includeLowerCase; /** * radio button selected if include punctuation */ private JRadioButton includePunctuation; /** * radio button selected if include upper case */ private JRadioButton includeUpperCase; /** * how long a password do you want */ private JSpinner pwLength; /** * displayed instructions on how to use */ private JTextArea instructions; /** * Generated suggested password. */ private JTextField suggestedPassword; /** * helper for pwlength. */ private SpinnerNumberModel pwLengthModel; /** * allocate all 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 ); pwLength = new JSpinner(); Dimension d = new Dimension( 60, 25 ); pwLength.setMinimumSize( d ); pwLength.setPreferredSize( d ); pwLength.setMaximumSize( d ); pwLengthModel = new SpinnerNumberModel( DEFAULT_PW_LENGTH /* initial value */, 4 /* min, don't put below 4 or impossible to get all 4 categories. */, 40 /* max */, 1 /* step */ ); pwLength.setModel( pwLengthModel ); pwLengthLabel = new JLabel( "Length" ); pwLengthLabel.setBackground( BACKGROUND_FOR_LABEL ); pwLengthLabel.setFont( FONT_FOR_LABEL ); pwLengthLabel.setForeground( FOREGROUND_FOR_LABEL ); includeDigits = new JRadioButton( "include digits", true ); includeDigits.setBackground( BACKGROUND_FOR_LABEL ); includeDigits.setFont( FONT_FOR_EDITABLE ); includeDigits.setForeground( FOREGROUND_FOR_LABEL ); includeLowerCase = new JRadioButton( "include lower case letters", true ); includeLowerCase.setBackground( BACKGROUND_FOR_LABEL ); includeLowerCase.setFont( FONT_FOR_EDITABLE ); includeLowerCase.setForeground( FOREGROUND_FOR_LABEL ); includeUpperCase = new JRadioButton( "include uppercase letters", true ); includeUpperCase.setBackground( BACKGROUND_FOR_LABEL ); includeUpperCase.setFont( FONT_FOR_EDITABLE ); includeUpperCase.setForeground( FOREGROUND_FOR_LABEL ); includePunctuation = new JRadioButton( "include punctuation", true ); includePunctuation.setBackground( BACKGROUND_FOR_LABEL ); includePunctuation.setFont( FONT_FOR_EDITABLE ); includePunctuation.setForeground( FOREGROUND_FOR_LABEL ); another = new JEButton( "Another" ); another.setToolTipText( "Generate another password" ); // make JTextField, unfortunately no copy/paste in old Javas. suggestedPassword = new JTextField( "", JLabel.LEFT ); suggestedPassword.setBackground( BACKGROUND_FOR_RESULT ); suggestedPassword.setEditable( false ); suggestedPassword.setFont( FONT_FOR_RESULT ); suggestedPassword.setForeground( FOREGROUND_FOR_RESULT ); suggestedPassword.setMargin( new Insets( 2, 2, 2, 2 ) ); suggestedPassword.setOpaque( true ); ///////////////////////////////////////////////////////////////////////////////////////////////////// instructions = new JTextArea( "Select which characters to include in your new password (the more you\n" + "include the more secure) and the password length (the longer the more\n" + "secure), then click ANOTHER. Then for one final tweak, change one or two\n" + "characters of the result before use. To avoid confusion, generated passwords\n" + "never contain the characters Il1| or O0." ); instructions.setEditable( false ); instructions.setMargin( new Insets( 4, 4, 4, 4 ) ); instructions.setBackground( BACKGROUND_FOR_INSTRUCTIONS ); instructions.setFont( FONT_FOR_INSTRUCTIONS ); 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 aboutbox frame // open aboutbox frame new CMPAboutJBox( TITLE_STRING, VERSION_STRING, "Generates random passwords.", "", "freeware", RELEASE_DATE, FIRST_COPYRIGHT_YEAR, "Roedy Green", "PASSWORD", "1.8" ); } } ); } /** * What happens after ANOTHER button hit. */ private void hit() { int pwLengthValue = ( ( Number ) pwLengthModel.getValue() ).intValue(); boolean needsDigits = false; boolean needsLowerCase = false; boolean needsUpperCase = false; boolean needsPunctuation = false; final FastCat sb = new FastCat( 4 ); if ( includeDigits.isSelected() ) { needsDigits = true; sb.append( CAST_OF_DIGITS ); } if ( includeLowerCase.isSelected() ) { needsLowerCase = true; sb.append( CAST_OF_LOWER_CASE_LETTERS ); } if ( includeUpperCase.isSelected() ) { needsUpperCase = true; sb.append( CAST_OF_UPPER_CASE_LETTERS ); } if ( includePunctuation.isSelected() ) { needsPunctuation = true; sb.append( CAST_OF_PUNCTUATION_LETTERS ); } // if they turned everything off, treat as if asked for digits only. if ( sb.length() == 0 ) { needsDigits = true; includeDigits.setSelected( true ); sb.append( CAST_OF_DIGITS ); } suggestedPassword.setText( spin( pwLengthValue, sb.toString(), needsDigits, needsLowerCase, needsUpperCase, needsPunctuation ) ); suggestedPassword.requestFocus(); } /** * Install listeners */ private void installHooks() { another.addActionListener( this ); includeDigits.addActionListener( this ); includeUpperCase.addActionListener( this ); includeLowerCase.addActionListener( this ); includePunctuation.addActionListener( this ); pwLength.addChangeListener( this ); } /** * Layout components in a GridBag * * @param contentPane JApplet contentPane. */ private void layoutComponents( Container contentPane ) { // layout // ---0---1-------------2------- 3---- 0 // 0---- title ------- ----- title2--- // 1 includedigits // 2 includelower ~length----length 2 // 3 includeupper -------------- 3 // 4 includepunct -------------------4 // 5 ----------suggested password---- 5 // 6 -------------------------ANOTHER 6 // 7 ----------instructions---------- 7 // 1 is dummy column to absorb extra space // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( title, new GridBagConstraints( 0, 0, 3, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets( margin[ 0 ], margin[ 1 ], 5, 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( margin[ 0 ], 5, 5, margin[ 3 ] ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( includeDigits, new GridBagConstraints( 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets( 1, margin[ 1 ], 2, 2 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( includeLowerCase, new GridBagConstraints( 0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets( 2, margin[ 1 ], 1, 2 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( pwLength, new GridBagConstraints( 3, 2, 1, 2, 0.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets( 2, 2, 2, margin[ 3 ] ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( includeUpperCase, new GridBagConstraints( 0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets( 1, margin[ 1 ], 2, 2 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( includePunctuation, new GridBagConstraints( 0, 4, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets( 1, margin[ 1 ], 2, 2 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( suggestedPassword, new GridBagConstraints( 0, 5, 4, 1, 1.0, .25, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets( 2, margin[ 1 ], 5, margin[ 3 ] ), 40, 20 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( another, new GridBagConstraints( 1, 6, 3, 1, 0.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets( 5, 5, 5, margin[ 3 ] ), 20, 10 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( instructions, new GridBagConstraints( 0, 7, 4, 1, 1.0, .75, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets( 5, margin[ 1 ], margin[ 2 ], margin[ 3 ] ), 0, 0 ) ); } /** * Guts of the Applet; all the rest in GUI bubblegum. Generate a random password * * @param pwlength how long a password do you want * @param possibleCharacters cast of characters allowed in generated password * @param needsDigits true if there needs to be at least one digit in the generated password. * @param needsLowerCase true if there needs to be at least one lower case char in the generated password. * @param needsUpperCase true if there needs to be at least one upper chase char in the generated password. * @param needsPunctuation true if there needs to be at least one punctuation character in the generated password. * * @return Password string containing letters and digits, lower and upper case. */ private String spin( int pwlength, String possibleCharacters, boolean needsDigits, boolean needsLowerCase, boolean needsUpperCase, boolean needsPunctuation ) { boolean missingDigits; boolean missingLowerCase; boolean missingUpperCase; boolean missingPunctuation; // ensure has at least one of each category of character requested final char[] accum = new char[ pwlength ]; do { missingDigits = needsDigits; missingLowerCase = needsLowerCase; missingUpperCase = needsUpperCase; missingPunctuation = needsPunctuation; for ( int i = 0; i < pwlength; i++ ) { int index = wheel.nextInt( possibleCharacters.length() ); char letter = possibleCharacters.charAt( index ); if ( missingDigits && CAST_OF_DIGITS.indexOf( letter ) >= 0 ) { missingDigits = false; } if ( missingLowerCase && CAST_OF_LOWER_CASE_LETTERS.indexOf( letter ) >= 0 ) { missingLowerCase = false; } if ( missingUpperCase && CAST_OF_UPPER_CASE_LETTERS.indexOf( letter ) >= 0 ) { missingUpperCase = false; } if ( missingPunctuation && CAST_OF_PUNCTUATION_LETTERS.indexOf( letter ) >= 0 ) { missingPunctuation = false; } accum[ i ] = letter; } // end for } // keep going until have a password with at least one of each of the desired categories in it. while ( missingDigits || missingLowerCase || missingUpperCase || missingPunctuation ); return new String( accum ); } /** * Allow JApplet to be run as an Application * * @param args not used. */ public static void main( String[] args ) { HybridJ.fireup( new Password(), TITLE_STRING + " " + VERSION_STRING, APPLET_WIDTH, APPLET_HEIGHT ); } /** * Invoked when an action occurs. * * @param e event, not used */ public void actionPerformed( ActionEvent e ) { hit(); } /** * 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() { another = null; instructions = null; pwLengthLabel = null; title2 = null; title = null; includeDigits = null; includeUpperCase = null; includeLowerCase = null; includePunctuation = null; pwLength = null; suggestedPassword = null; pwLengthModel = 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 ); contentPane.setForeground( FOREGROUND_FOR_APPLET ); buildComponents(); layoutComponents( contentPane ); installHooks(); this.validate(); this.setVisible( true ); } /** * Applet has scrolled onscreen. */ public void start() { hit(); validate(); } /** * Invoked when the target of the listener has changed its state. * * @param e a ChangeEvent object, not used */ public void stateChanged( ChangeEvent e ) { hit(); } /** * Applet scrolled offscreen */ public void stop() { } }