001package ca.bc.webarts.widgets;
002
003import java.security.NoSuchAlgorithmException;
004import java.security.SecureRandom;
005import java.security.spec.InvalidKeySpecException;
006import java.security.spec.KeySpec;
007import java.util.Arrays;
008
009import javax.crypto.SecretKeyFactory;
010import javax.crypto.spec.PBEKeySpec;
011
012/** An basic encryption service using strong JDK based classes  using PBKDF2.
013 *
014 * <u>The flow goes something like this:</u><br />
015 * <ol><li>When adding a new user, call generateSalt(), then getEncryptedPassword(),
016 *         and store both the encrypted password and the salt.
017 *         Do not store the clear-text password. Don't worry about keeping the salt
018 *         in a separate table or location from the encrypted password;
019 *         as discussed above, the salt is non-secret.</li>
020 *    <li>When authenticating a user, retrieve the previously encrypted password and
021 *        salt from the database, then send those and the clear-text password they entered
022 *        to authenticate(). If it returns true, authentication succeeded.</li>
023 *    <li>When a user changes their password, it's safe to reuse their old salt; you can
024 *        just call getEncryptedPassword() with the old salt.</li></ol>
025 *
026 **/
027public class EncryptionService
028{
029    // PBKDF2 with SHA-1 as the hashing algorithm. Note that the NIST
030    // specifically names SHA-1 as an acceptable hashing algorithm for PBKDF2
031    static final String PBKDF2WithHmacSHA1 =  "PBKDF2WithHmacSHA1"; //"PBKDF2WithHmacSHA1";  //"PBEWithMD5AndDES";
032    static final String PBEWithMD5AndDES =  "PBEWithMD5AndDES"; //"PBKDF2WithHmacSHA1";  //"PBEWithMD5AndDES";
033    String algorithm_ =  "PBEWithMD5AndDES"; //"PBKDF2WithHmacSHA1";  //"PBEWithMD5AndDES";
034    // SHA-1 generates 160 bit hashes, so that's what makes sense here
035    int derivedKeyLength_ = 160;
036    // Pick an iteration count that works for you. The NIST recommends at
037    // least 1,000 iterations:
038    // http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf
039    // iOS 4.x reportedly uses 10,000:
040    // http://blog.crackpassword.com/2010/09/smartphone-forensics-cracking-blackberry-backup-passwords/
041    int iterations_ = 40000;
042    int numSaltBytes_ = 32; //8
043
044
045  public boolean authenticate( String attemptedPassword,
046                               byte[] encryptedPassword,
047                               byte[] salt )
048                 throws NoSuchAlgorithmException, InvalidKeySpecException
049  {
050    // Encrypt the clear-text password using the same salt that was used to
051    // encrypt the original password
052    byte[] encryptedAttemptedPassword = getEncryptedPassword( attemptedPassword, salt );
053
054    // Authentication succeeds if encrypted password that the user entered
055    // is equal to the stored hash
056    return Arrays.equals( encryptedPassword, encryptedAttemptedPassword );
057  }
058
059
060  public byte[] getEncryptedPassword( String password, byte[] salt ) throws NoSuchAlgorithmException, InvalidKeySpecException
061  {
062    KeySpec spec = new PBEKeySpec( password.toCharArray(), salt, iterations_, derivedKeyLength_);
063
064    SecretKeyFactory f = SecretKeyFactory.getInstance( algorithm_ );
065
066    return f.generateSecret( spec ).getEncoded();
067  }
068
069
070  public byte[] generateSalt() throws NoSuchAlgorithmException
071  {
072    // VERY important to use SecureRandom instead of just Random
073    SecureRandom random = SecureRandom.getInstance( "SHA1PRNG" );
074
075    // Generate a 8 byte (64 bit) salt as recommended by RSA PKCS5
076    byte[] salt = new byte[numSaltBytes_];
077    random.nextBytes( salt );
078
079    return salt;
080  }
081
082
083  public static void main(String [] args)
084  {
085    EncryptionService instance = new EncryptionService();
086    try
087    {
088      System.out.println(instance.getEncryptedPassword(args[0], instance.generateSalt()));
089    }
090    catch ( InvalidKeySpecException iksEx)
091    {
092      System.out.println("Invalid Key Spec: PBEKeySpec with iterations_ = "  + instance.iterations_+ " derivedKeyLength_= "+instance.derivedKeyLength_);
093    }
094    catch ( NoSuchAlgorithmException nsaEx)
095    {
096      System.out.println("No Such Algorithm "+instance.algorithm_);
097    }
098  }
099}
100