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