/**
* Copyright (c) 2013-2016, The SeedStack authors <http://seedstack.org>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.seedstack.seed.core.internal.crypto;
import org.seedstack.seed.SeedException;
import org.seedstack.seed.crypto.Hash;
import org.seedstack.seed.crypto.HashingService;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
class PBKDF2HashingService implements HashingService {
private static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1";
private static final int SALT_BYTE_SIZE = 24;
private static final int HASH_BYTE_SIZE = 24;
private static final int PBKDF2_ITERATIONS = 1000;
@Override
public Hash createHash(String toHash) {
return createHash(toHash.toCharArray());
}
@Override
public Hash createHash(char[] toHash) {
// Generate a random salt
SecureRandom random = new SecureRandom();
byte[] salt = new byte[SALT_BYTE_SIZE];
random.nextBytes(salt);
// Hash the password
byte[] hash;
hash = pbkdf2(toHash, salt);
return new Hash(hash, salt);
}
@Override
public boolean validatePassword(String password, Hash correctHash) {
return validatePassword(password.toCharArray(), correctHash);
}
@Override
public boolean validatePassword(char[] password, Hash correctHash) {
// Compute the hash of the provided password, using the same salt
byte[] testHash = pbkdf2(password, correctHash.getSalt());
// Compare the hashes in constant time. The password is correct if both hashes match.
return ByteArrays.slowEquals(correctHash.getHash(), testHash);
}
/**
* Computes the PBKDF2 hash of a password.
*
* @param password the password to hash.
* @param salt the salt
* @return the PBDKF2 hash of the password
*/
private byte[] pbkdf2(char[] password, byte[] salt) {
try {
PBEKeySpec spec = new PBEKeySpec(password, salt, PBKDF2_ITERATIONS, HASH_BYTE_SIZE * 8);
SecretKeyFactory skf = SecretKeyFactory.getInstance(PBKDF2_ALGORITHM);
return skf.generateSecret(spec).getEncoded();
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw SeedException.wrap(e, CryptoErrorCode.UNEXPECTED_EXCEPTION);
}
}
}