001    package com.sptci.util;
002    
003    import java.security.SecureRandom;
004    import java.util.logging.Level;
005    import java.util.logging.Logger;
006    
007    /**
008     * A utility class that can be used to generate secure random password
009     * values.  Also contains methods to check the quality of a password.
010     *
011     * <p>Copyright 2005 Sans Pareil Technologies, Inc.</p>
012     * @author Rakesh Vidyadharan 2005 August 25
013     * @version $Id: PasswordGenerator.java 3145 2007-04-24 14:14:22Z rakesh $
014     */
015    public class PasswordGenerator extends Object
016    {
017      /**
018       * The default characters that are allowed in a password value.
019       */
020      private static final char[] PASSWORD_CHARACTERS = 
021        ( "abcdefghijklmnopqrstuvqxyz" +
022        "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
023        "`1234567890~!@#$%^&*()" ).toCharArray();
024    
025      /**
026       * The random number generator.
027       */
028      private static final SecureRandom random = new SecureRandom();
029    
030      /**
031       * Default constructor.  No special actions required.
032       */
033      public PasswordGenerator() {}
034    
035      /**
036       * Generate a random password of the specified length.
037       *
038       * @see #generate( int, char[] )
039       * @param length The desired length of the password.
040       * @return char[] The array of characters that comprise the random
041       *   password.
042       */
043      public char[] generate( int length ) 
044      {
045        return generate( length, PASSWORD_CHARACTERS );
046      }
047    
048      /**
049       * Generate a random password of the specified length and restricted
050       * to the characters specified.  Checks the generated value against
051       * {@link #checkSimple} and to ensure that the generated value is good
052       * enough.
053       *
054       * <p><b>Note:</b> There is a possibility that this method can go into
055       * an infinite loop.  This can happen if the <code>characters</code>
056       * array contains only those characters that make {@link
057       * #checkComplex} always return <code>false</code>.</p>
058       *
059       * @see #checkComplex
060       * @param length The desired length of the password.
061       * @param characters The array of valid characters thay may be used
062       *   in the password.
063       * @return char[] The array of characters that comprise the random
064       *   password.
065       */
066      public char[] generate( int length, char[] characters )
067      {
068        char[] password = new char[length];
069    
070        try 
071        {
072          random.setSeed( System.currentTimeMillis() );
073    
074          byte[] intbytes = new byte[4];      
075    
076          for ( int i = 0; i < length; ++i ) 
077          {
078            random.nextBytes( intbytes );
079            password[i] = characters[ 
080              Math.abs( getIntFromByte( intbytes ) % characters.length ) ];
081          }
082          
083          while ( ! checkComplex( password ) )
084          {
085            password = generate( length, characters );
086          }
087        }
088        catch ( Throwable t ) 
089        {
090          Logger.getAnonymousLogger().log( Level.FINE,
091              "Unable to generate random password.", t );
092        }
093    
094        return password;
095      }
096    
097      /**
098       * Check the specified password value for quality using simple rules.
099       * The following rules are used:
100       *
101       * <ol>
102       *   <li>Have at least 6 characters.</li>
103       *   <li>Have at least 2 alphabetical characters.</li>
104       *   <li>Have at least 2 non-alphabetical characters.</li>
105       * </ol>
106       *
107       * @see #checkComplex
108       * @param password The password that is to be checked.
109       * @return Returns <code>true</code> if the specified password
110       *   satisfies the requirements.
111       */
112      public boolean checkSimple( char[] password )
113      {
114        boolean result = true;
115        if ( password.length < 6 )
116        {
117          result = false;
118        }
119        else
120        {
121          int alpha = 0;
122          int nonalpha = 0;
123          for ( char c : password )
124          {
125            if ( Character.isLetter( c ) )
126            {
127              alpha++;
128            }
129            else
130            {
131              nonalpha++;
132            }
133          }
134    
135          result = ( ( alpha > 1 ) && ( nonalpha > 1 ) );
136        }
137    
138        return result;
139      }
140    
141      /**
142       * Check the specified password value for quality.  The following
143       * rules are used:
144       *
145       * <ol>
146       *   <li>Have at least 6 characters.</li>
147       *   <li>Have at least 2 alphabetical characters.</li>
148       *   <li>Have at least 1 numeric characters.</li>
149       *   <li>Have at least 1 upper case character.</li>
150       *   <li>Have at least 1 lower case character.</li>
151       *   <li>Have at least 1 non-alphanumeric character.</li>
152       * </ol>
153       *
154       * <p><b>Note:</b> There is no guarantee that the password generated
155       * by the {@link #generate( int, char[] )} method will satisfy
156       * these rules.</p>
157       *
158       * @see #checkSimple
159       * @param password The password that is to be checked.
160       * @return Returns <code>true</code> if the specified password
161       *   satisfies the requirements.
162       */
163      public boolean checkComplex( char[] password )
164      {
165        boolean result = true;
166        if ( password.length < 6 )
167        {
168          result = false;
169        }
170        else
171        {
172          int alpha = 0;
173          int digit = 0;
174          int nonalphanum = 0;
175          int lower = 0;
176          int upper = 0;
177          for ( char c : password )
178          {
179            if ( Character.isLetter( c ) )
180            {
181              alpha++;
182              if ( Character.isLowerCase( c ) )
183              {
184                lower++;
185              }
186              else
187              {
188                upper++;
189              }
190            }
191            else if ( Character.isDigit( c ) )
192            {
193              digit++;
194            }
195            else
196            {
197              nonalphanum++;
198            }
199          }
200    
201          result = ( ( alpha > 1 ) && ( digit > 0 ) && ( nonalphanum > 0 ) &&
202            ( lower > 0 ) && ( upper > 0 ) );
203        }
204    
205        return result;
206      }
207    
208      /**
209       * Convert a byte value returned by the random number generator
210       * ({@link #random}) to an integer that may be used to index into
211       * the allowable characters array.
212       *
213       * @param bytes The byte values returned by the random number
214       *   generator.
215       */
216      protected int getIntFromByte( byte[] bytes ) 
217      {
218        int returnNumber = 0;
219        int index = 0;
220        returnNumber += byteToInt( bytes[index++] ) << 24;
221        returnNumber += byteToInt( bytes[index++] ) << 16;
222        returnNumber += byteToInt( bytes[index++] ) << 8;
223        returnNumber += byteToInt( bytes[index++] ) << 0;
224        return returnNumber;
225      }
226    
227      /**
228       * Convert a byte value into a postive integer.
229       *
230       * @param byteValue The byte that is to be converted into an integer.
231       * @return int The converted positive value.
232       */
233      protected int byteToInt( byte byteValue ) 
234      {
235        return (int) byteValue & 0xFF;
236      }
237    }