Java版本:java
1 import java.security.SecureRandom; 2 import javax.crypto.spec.PBEKeySpec; 3 import javax.crypto.SecretKeyFactory; 4 import java.security.NoSuchAlgorithmException; 5 import java.security.spec.InvalidKeySpecException; 6 import javax.xml.bind.DatatypeConverter; 7 8 public class PasswordStorage 9 { 10 11 @SuppressWarnings("serial") 12 static public class InvalidHashException extends Exception { 13 public InvalidHashException(String message) { 14 super(message); 15 } 16 public InvalidHashException(String message, Throwable source) { 17 super(message, source); 18 } 19 } 20 21 @SuppressWarnings("serial") 22 static public class CannotPerformOperationException extends Exception { 23 public CannotPerformOperationException(String message) { 24 super(message); 25 } 26 public CannotPerformOperationException(String message, Throwable source) { 27 super(message, source); 28 } 29 } 30 31 public static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1"; 32 33 // These constants may be changed without breaking existing hashes. 34 public static final int SALT_BYTE_SIZE = 24; 35 public static final int HASH_BYTE_SIZE = 18; 36 public static final int PBKDF2_ITERATIONS = 64000; 37 38 // These constants define the encoding and may not be changed. 39 public static final int HASH_SECTIONS = 5; 40 public static final int HASH_ALGORITHM_INDEX = 0; 41 public static final int ITERATION_INDEX = 1; 42 public static final int HASH_SIZE_INDEX = 2; 43 public static final int SALT_INDEX = 3; 44 public static final int PBKDF2_INDEX = 4; 45 46 public static String createHash(String password) 47 throws CannotPerformOperationException 48 { 49 return createHash(password.toCharArray()); 50 } 51 52 public static String createHash(char[] password) 53 throws CannotPerformOperationException 54 { 55 // Generate a random salt 56 SecureRandom random = new SecureRandom(); 57 byte[] salt = new byte[SALT_BYTE_SIZE]; 58 random.nextBytes(salt); 59 60 // Hash the password 61 byte[] hash = pbkdf2(password, salt, PBKDF2_ITERATIONS, HASH_BYTE_SIZE); 62 int hashSize = hash.length; 63 64 // format: algorithm:iterations:hashSize:salt:hash 65 String parts = "sha1:" + 66 PBKDF2_ITERATIONS + 67 ":" + hashSize + 68 ":" + 69 toBase64(salt) + 70 ":" + 71 toBase64(hash); 72 return parts; 73 } 74 75 public static boolean verifyPassword(String password, String correctHash) 76 throws CannotPerformOperationException, InvalidHashException 77 { 78 return verifyPassword(password.toCharArray(), correctHash); 79 } 80 81 public static boolean verifyPassword(char[] password, String correctHash) 82 throws CannotPerformOperationException, InvalidHashException 83 { 84 // Decode the hash into its parameters 85 String[] params = correctHash.split(":"); 86 if (params.length != HASH_SECTIONS) { 87 throw new InvalidHashException( 88 "Fields are missing from the password hash." 89 ); 90 } 91 92 // Currently, Java only supports SHA1. 93 if (!params[HASH_ALGORITHM_INDEX].equals("sha1")) { 94 throw new CannotPerformOperationException( 95 "Unsupported hash type." 96 ); 97 } 98 99 int iterations = 0; 100 try { 101 iterations = Integer.parseInt(params[ITERATION_INDEX]); 102 } catch (NumberFormatException ex) { 103 throw new InvalidHashException( 104 "Could not parse the iteration count as an integer.", 105 ex 106 ); 107 } 108 109 if (iterations < 1) { 110 throw new InvalidHashException( 111 "Invalid number of iterations. Must be >= 1." 112 ); 113 } 114 115 116 byte[] salt = null; 117 try { 118 salt = fromBase64(params[SALT_INDEX]); 119 } catch (IllegalArgumentException ex) { 120 throw new InvalidHashException( 121 "Base64 decoding of salt failed.", 122 ex 123 ); 124 } 125 126 byte[] hash = null; 127 try { 128 hash = fromBase64(params[PBKDF2_INDEX]); 129 } catch (IllegalArgumentException ex) { 130 throw new InvalidHashException( 131 "Base64 decoding of pbkdf2 output failed.", 132 ex 133 ); 134 } 135 136 137 int storedHashSize = 0; 138 try { 139 storedHashSize = Integer.parseInt(params[HASH_SIZE_INDEX]); 140 } catch (NumberFormatException ex) { 141 throw new InvalidHashException( 142 "Could not parse the hash size as an integer.", 143 ex 144 ); 145 } 146 147 if (storedHashSize != hash.length) { 148 throw new InvalidHashException( 149 "Hash length doesn't match stored hash length." 150 ); 151 } 152 153 // Compute the hash of the provided password, using the same salt, 154 // iteration count, and hash length 155 byte[] testHash = pbkdf2(password, salt, iterations, hash.length); 156 // Compare the hashes in constant time. The password is correct if 157 // both hashes match. 158 return slowEquals(hash, testHash); 159 } 160 161 private static boolean slowEquals(byte[] a, byte[] b) 162 { 163 int diff = a.length ^ b.length; 164 for(int i = 0; i < a.length && i < b.length; i++) 165 diff |= a[i] ^ b[i]; 166 return diff == 0; 167 } 168 169 private static byte[] pbkdf2(char[] password, byte[] salt, int iterations, int bytes) 170 throws CannotPerformOperationException 171 { 172 try { 173 PBEKeySpec spec = new PBEKeySpec(password, salt, iterations, bytes * 8); 174 SecretKeyFactory skf = SecretKeyFactory.getInstance(PBKDF2_ALGORITHM); 175 return skf.generateSecret(spec).getEncoded(); 176 } catch (NoSuchAlgorithmException ex) { 177 throw new CannotPerformOperationException( 178 "Hash algorithm not supported.", 179 ex 180 ); 181 } catch (InvalidKeySpecException ex) { 182 throw new CannotPerformOperationException( 183 "Invalid key spec.", 184 ex 185 ); 186 } 187 } 188 189 private static byte[] fromBase64(String hex) 190 throws IllegalArgumentException 191 { 192 return DatatypeConverter.parseBase64Binary(hex); 193 } 194 195 private static String toBase64(byte[] array) 196 { 197 return DatatypeConverter.printBase64Binary(array); 198 } 199 200 }
C# .PHP版本, 詳細見 https://github.com/defuse/password-hashinggit