Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save nagylzs/e2e0b068d36048fdde4c8d0fc4ec1d4a to your computer and use it in GitHub Desktop.

Select an option

Save nagylzs/e2e0b068d36048fdde4c8d0fc4ec1d4a to your computer and use it in GitHub Desktop.
Example program that shows how to check password hashes created by ASP .Net (and also create new password hashes)
package hu.nagylzs.aspnetpasswordtest;
/*
* Example program that shows how to check password hashes created by ASP .Net.
*
* References:
*
* - UserManager.FindAsync https://msdn.microsoft.com/en-us/library/dn497545(v=vs.108).aspx
* - UserManager: https://docs.microsoft.com/en-us/previous-versions/aspnet/web-frameworks/dn613290(v=vs.108)
* - Microsoft.AspNet.Identity: https://docs.microsoft.com/en-us/previous-versions/aspnet/web-frameworks/dn253016%28v%3dvs.108%29
*
*
*/
import java.math.BigInteger;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import java.util.Base64;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
/*
* PBKDF2 salted password hashing.
* Author: havoc AT defuse.ca
* www: http://crackstation.net/hashing-security.htm
*/
public class AspNetUserIdentityPasswordCheckExample {
public static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1";
public static final int SALT_BYTES = 16;
public static final int HASH_BYTES = 32;
public static final int PBKDF2_ITERATIONS = 1000;
/**
* Compares two byte arrays in length-constant time. This comparison method is
* used so that password hashes cannot be extracted from an on-line system using
* a timing attack and then attacked off-line.
*
* @param a
* the first byte array
* @param b
* the second byte array
* @return true if both byte arrays are the same, false if not
*/
private static boolean slowEquals(byte[] a, byte[] b) {
int diff = a.length ^ b.length;
for (int i = 0; i < a.length && i < b.length; i++)
diff |= a[i] ^ b[i];
return diff == 0;
}
/**
* Computes the PBKDF2 hash of a password.
*
* @param password
* the password to hash.
* @param salt
* the salt
* @param iterations
* the iteration count (slowness factor)
* @param bytes
* the length of the hash to compute in bytes
* @return the PBDKF2 hash of the password
*/
private static byte[] pbkdf2(char[] password, byte[] salt, int iterations, int bytes)
throws NoSuchAlgorithmException, InvalidKeySpecException {
PBEKeySpec spec = new PBEKeySpec(password, salt, iterations, bytes * 8);
SecretKeyFactory skf = SecretKeyFactory.getInstance(PBKDF2_ALGORITHM);
return skf.generateSecret(spec).getEncoded();
}
/**
* Returns a salted PBKDF2 hash of the password.
*
* @param password
* the password to hash
* @return a salted PBKDF2 hash of the password
*/
public static byte[] createHash(char[] password) throws NoSuchAlgorithmException, InvalidKeySpecException {
// Generate a random salt
SecureRandom random = new SecureRandom();
byte[] salt = new byte[SALT_BYTES];
random.nextBytes(salt);
// Hash the password
byte[] hash = pbkdf2(password, salt, PBKDF2_ITERATIONS, HASH_BYTES);
byte[] result = new byte[SALT_BYTES + HASH_BYTES + 1];
System.arraycopy(salt, 0, result, 1, SALT_BYTES);
System.arraycopy(hash, 0, result, SALT_BYTES + 1, HASH_BYTES);
return result;
}
/**
* Converts a string of hexadecimal characters into a byte array.
*
* @param hex
* the hex string
* @return the hex string decoded into a byte array
*/
private static byte[] fromHex(String hex) {
byte[] binary = new byte[hex.length() / 2];
for (int i = 0; i < binary.length; i++) {
binary[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16);
}
return binary;
}
/**
* Converts a byte array into a hexadecimal string.
*
* @param array
* the byte array to convert
* @return a length*2 character string encoding the byte array
*/
private static String toHex(byte[] array) {
BigInteger bi = new BigInteger(1, array);
String hex = bi.toString(16);
int paddingLength = (array.length * 2) - hex.length();
if (paddingLength > 0)
return String.format("%0" + paddingLength + "d", 0) + hex;
else
return hex;
}
/**
* Validates a password using a hash.
*
* @param password
* the password to check
* @param hashBase64Encoded
* the hash of the valid password
* @return true if the password is correct, false if not
*/
public static boolean validatePassword(String password, String goodHashBase64Encoded)
throws NoSuchAlgorithmException, InvalidKeySpecException {
byte[] goodHash = Base64.getMimeDecoder().decode(goodHashBase64Encoded);
return validatePassword(password.toCharArray(), goodHash);
}
/**
* Validates a password using a hash.
*
* @param password
* the password to check
* @param goodHash
* the hash of the valid password
* @return true if the password is correct, false if not
*/
public static boolean validatePassword(char[] password, byte[] goodHash)
throws NoSuchAlgorithmException, InvalidKeySpecException {
byte[] salt = Arrays.copyOfRange(goodHash, 1, SALT_BYTES + 1);
byte[] subKey = Arrays.copyOfRange(goodHash, SALT_BYTES + 1, goodHash.length);
//System.err.println("saltString " + toHex(salt));
//System.err.println("storedSubKeyString " + toHex(subKey));
byte[] testHash = pbkdf2(password, salt, PBKDF2_ITERATIONS, HASH_BYTES); // char[] password, byte[] salt, int
// iterations, int bytes
// System.err.println("goodHash " +
// toHex(testHash));
return slowEquals(subKey, testHash);
}
/**
* Tests the basic functionality of the PasswordHash class
*
* @param args
* ignored
*/
public static void main(String[] args) {
singleTest();
fullTest();
}
public static void singleTest() {
try {
String goodPassword = "this is the good password";
String badPassword = "this is a bad password";
byte[] goodHash = createHash(goodPassword.toCharArray());
if (validatePassword(goodPassword.toCharArray(), goodHash)) {
System.err.println("PASS: accepted good password.");
} else {
System.err.println("ERROR: did not accept good password.");
}
if (validatePassword(badPassword.toCharArray(), goodHash)) {
System.err.println("ERROR: accepted bad password.");
} else {
System.err.println("PASS: refused bad password.");
}
} catch (Exception ex) {
System.out.println("ERROR: " + ex);
}
}
public static void fullTest() {
try {
// Print out 10 hashes
for (int i = 0; i < 10; i++)
System.out.println(toHex(AspNetUserIdentityPasswordCheckExample.createHash("p\r\nassw0Rd!".toCharArray())));
// Test password validation
boolean failure = false;
System.out.println("Running tests...");
for (int i = 0; i < 1000; i++) {
String password = "" + i;
byte[] hash = createHash(password.toCharArray());
byte[] secondHash = createHash(password.toCharArray());
if (hash.equals(secondHash)) {
System.out.println("FAILURE: TWO HASHES ARE EQUAL!");
failure = true;
}
String wrongPassword = "" + (i + 1);
if (validatePassword(wrongPassword.toCharArray(), hash)) {
System.out.println("FAILURE: WRONG PASSWORD ACCEPTED!");
failure = true;
}
if (!validatePassword(password.toCharArray(), hash)) {
System.out.println("FAILURE: GOOD PASSWORD NOT ACCEPTED!");
failure = true;
}
}
if (failure)
System.out.println("TESTS FAILED!");
else
System.out.println("TESTS PASSED!");
} catch (Exception ex) {
System.out.println("ERROR: " + ex);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment