summaryrefslogtreecommitdiffstats
path: root/src/main/java/com/gitblit/utils/SecurePasswordHashUtils.java
blob: de2c00845c0d8c01999abf4510eb18f27180cb67 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
package com.gitblit.utils;

import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;

import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;

/**
 * The Class SecurePasswordHashUtils provides methods to create and validate secure hashes from user passwords.
 * 
 * It uses the concept proposed by OWASP - Hashing Java: https://www.owasp.org/index.php/Hashing_Java
 */
public class SecurePasswordHashUtils {

	public static final String PBKDF2WITHHMACSHA256_TYPE = "PBKDF2WITHHMACSHA256:";

	private static final SecureRandom RANDOM = new SecureRandom();
	private static final int ITERATIONS = 10000;
	private static final int KEY_LENGTH = 256;
	private static final int SALT_LENGTH = 32;

	/** The instance. */
	private static SecurePasswordHashUtils instance;

	/**
	 * Instantiates a new secure password hash utils.
	 */
	private SecurePasswordHashUtils() {
	}

	/**
	 * Gets an instance of {@link SecurePasswordHashUtils}.
	 *
	 * @return the secure password hash utils
	 */
	public static SecurePasswordHashUtils get() {
		if (instance == null) {
			instance = new SecurePasswordHashUtils();
		}
		return instance;
	}

	/**
	 * Gets the next salt.
	 *
	 * @return the next salt
	 */
	protected byte[] getNextSalt() {
		byte[] salt = new byte[SALT_LENGTH];
		RANDOM.nextBytes(salt);
		return salt;
	}

	/**
	 * Hash.
	 *
	 * @param password
	 *            the password
	 * @param salt
	 *            the salt
	 * @return the byte[]
	 */
	protected byte[] hash(char[] password, byte[] salt) {
		PBEKeySpec spec = new PBEKeySpec(password, salt, ITERATIONS, KEY_LENGTH);
		Arrays.fill(password, Character.MIN_VALUE);
		try {
			SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
			return skf.generateSecret(spec).getEncoded();
		} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
			throw new IllegalStateException("Error while hashing password", e);
		} finally {
			spec.clearPassword();
		}
	}

	/**
	 * Checks if is password correct.
	 *
	 * @param passwordToCheck
	 *            the password to check
	 * @param salt
	 *            the salt
	 * @param expectedHash
	 *            the expected hash
	 * @return true, if is password correct
	 */
	protected boolean isPasswordCorrect(char[] passwordToCheck, byte[] salt, byte[] expectedHash) {
		byte[] hashToCheck = hash(passwordToCheck, salt);
		Arrays.fill(passwordToCheck, Character.MIN_VALUE);
		if (hashToCheck.length != expectedHash.length) {
			return false;
		}
		for (int i = 0; i < hashToCheck.length; i++) {
			if (hashToCheck[i] != expectedHash[i]) {
				return false;
			}
		}
		return true;
	}

	/**
	 * Creates the new secure hash from a password and formats it properly to be
	 * stored in a file.
	 *
	 * @param password
	 *            the password to be hashed
	 * @return the sting to be stored in a file (users.conf)
	 */
	public String createStoredPasswordFromPassword(String password) {
		byte[] salt = getNextSalt();
		return String.format("%s%s%s", SecurePasswordHashUtils.PBKDF2WITHHMACSHA256_TYPE, StringUtils.toHex(salt), StringUtils.toHex(hash(password.toCharArray(), salt)));
	}

	/**
	 * Gets the salt from stored password.
	 *
	 * @param storedPassword
	 *            the stored password
	 * @return the salt from stored password
	 */
	protected byte[] getSaltFromStoredPassword(String storedPassword) {
		byte[] pw = getStoredHashWithStrippedPrefix(storedPassword);
		return Arrays.copyOfRange(pw, 0, SALT_LENGTH);
	}

	/**
	 * Gets the hash from stored password.
	 *
	 * @param storedPassword
	 *            the stored password
	 * @return the hash from stored password
	 */
	protected byte[] getHashFromStoredPassword(String storedPassword) {
		byte[] pw = getStoredHashWithStrippedPrefix(storedPassword);
		return Arrays.copyOfRange(pw, SALT_LENGTH, pw.length);
	}

	/**
	 * Strips the prefix ({@link #PBKDF2WITHHMACSHA256_TYPE}) from a stored
	 * password and returns the decoded hash
	 *
	 * @param storedPassword
	 *            the stored password
	 * @return the stored hash with stripped prefix
	 */
	protected byte[] getStoredHashWithStrippedPrefix(String storedPassword) {
		String saltAndHash = storedPassword.substring(PBKDF2WITHHMACSHA256_TYPE.length());
		try {
			return Hex.decodeHex(saltAndHash.toCharArray());
		} catch (DecoderException e) {
			throw new IllegalStateException("Error while reading stored credentials", e);
		}
	}

	/**
	 * Checks if is password correct.
	 *
	 * @param password
	 *            the password
	 * @param storedPassword
	 *            the stored password
	 * @return true, if is password correct
	 */
	public boolean isPasswordCorrect(char[] password, String storedPassword) {
		byte[] storedSalt = getSaltFromStoredPassword(storedPassword);
		byte[] storedHash = getHashFromStoredPassword(storedPassword);
		return isPasswordCorrect(password, storedSalt, storedHash);
	}
}