/*
* JBoss, Home of Professional Open Source
* Copyright 2014 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.wildfly.security.password.impl;
import static org.wildfly.security.password.interfaces.BCryptPassword.ALGORITHM_BCRYPT;
import static org.wildfly.security.password.interfaces.BCryptPassword.BCRYPT_SALT_SIZE;
import static org.wildfly.security.password.interfaces.BCryptPassword.DEFAULT_ITERATION_COUNT;
import java.security.Provider;
import java.security.Security;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.wildfly.security.WildFlyElytronProvider;
import org.wildfly.security.password.PasswordFactory;
import org.wildfly.security.password.interfaces.BCryptPassword;
import org.wildfly.security.password.spec.ClearPasswordSpec;
import org.wildfly.security.password.spec.EncryptablePasswordSpec;
import org.wildfly.security.password.spec.IteratedSaltedPasswordAlgorithmSpec;
import org.wildfly.security.password.spec.IteratedSaltedHashPasswordSpec;
import org.wildfly.security.password.util.ModularCrypt;
/**
* <p>
* Tests for the bcrypt password implementation. The expected results in the tests were generated using the
* <a href="http://www.mindrot.org/projects/jBCrypt/">jBCrypt project</a>.
* </p>
*
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
*/
public class BCryptPasswordTest {
private static final Provider provider = new WildFlyElytronProvider();
@BeforeClass
public static void setup() {
Security.addProvider(provider);
}
@AfterClass
public static void removeProvider() {
Security.removeProvider(provider.getName());
}
@Test
public void testGetKeySpecFromString() throws Exception {
String cryptString = "$2a$12$D4G5f18o7aMMfwasBL7GpuQWuP3pkrZrOAnqP.bmezbMng.QwJ/pG";
// get the spec by parsing the crypt string.
PasswordFactory factory = PasswordFactory.getInstance(ALGORITHM_BCRYPT);
BCryptPassword password = (BCryptPassword) factory.translate(ModularCrypt.decode(cryptString));
Assert.assertEquals(12, password.getIterationCount());
Assert.assertEquals(BCryptPassword.BCRYPT_SALT_SIZE, password.getSalt().length);
// use the spec to build a new crypt string and compare it to the original one.
Assert.assertEquals(cryptString, ModularCrypt.encodeAsString(password));
}
@Test
public void testHashEmptyString() throws Exception {
String cryptString = "$2a$08$HqWuK6/Ng6sg9gQzbLrgb.Tl.ZHfXLhvt/SgVyWhQqgqcZ7ZuUtye";
PasswordFactory factory = PasswordFactory.getInstance(ALGORITHM_BCRYPT);
BCryptPassword password = (BCryptPassword) factory.translate(ModularCrypt.decode(cryptString));
// use the obtained spec to build a BCryptPasswordImpl, then verify the hash using the correct password.
Assert.assertTrue(factory.verify(password, "".toCharArray()));
// check if an incorrect password gets rejected.
Assert.assertFalse(factory.verify(password, "wrongpassword".toCharArray()));
// now use the EncryptablePasswordSpec to build a new password and check if the hashed bytes matches those that
// were parsed and stored in the spec.
password = (BCryptPassword) factory.generatePassword(new EncryptablePasswordSpec("".toCharArray(),
new IteratedSaltedPasswordAlgorithmSpec(password.getIterationCount(), password.getSalt())));
Assert.assertArrayEquals(password.getHash(), password.getHash());
// use the new password to obtain a spec and then check if the spec yields the same crypt string.
Assert.assertEquals(cryptString, ModularCrypt.encodeAsString(password));
}
@Test
public void testHashSimpleString() throws Exception {
String cryptString = "$2a$10$fVH8e28OQRj9tqiDXs1e1uxpsjN0c7II7YPKXua2NAKYvM6iQk7dq";
char[] correctPassword = "abcdefghijklmnopqrstuvwxyz".toCharArray();
PasswordFactory factory = PasswordFactory.getInstance(ALGORITHM_BCRYPT);
BCryptPassword password = (BCryptPassword) factory.translate(ModularCrypt.decode(cryptString));
password = (BCryptPassword) factory.translate(password);
// use the obtained spec to build a BCryptPasswordImpl, then verify the hash using the correct password.
Assert.assertTrue(factory.verify(password, correctPassword));
// check if an incorrect password gets rejected.
Assert.assertFalse(factory.verify(password, "wrongpassword".toCharArray()));
// now use the EncryptablePasswordSpec to build a new password and check if the hashed bytes matches those that
// were parsed and stored in the spec.
password = (BCryptPassword) factory.generatePassword(new EncryptablePasswordSpec(correctPassword,
new IteratedSaltedPasswordAlgorithmSpec(password.getIterationCount(), password.getSalt())));
Assert.assertArrayEquals(password.getHash(), password.getHash());
// use the new password to obtain a spec and then check if the spec yields the same crypt string.
Assert.assertEquals(cryptString, ModularCrypt.encodeAsString(password));
}
@Test
public void testHashComplexString() throws Exception {
String cryptString = "$2a$12$WApznUOJfkEGSmYRfnkrPOr466oFDCaj4b6HY3EXGvfxm43seyhgC";
char[] correctPassword = "~!@#$%^&*() ~!@#$%^&*()PNBFRD".toCharArray();
PasswordFactory factory = PasswordFactory.getInstance(ALGORITHM_BCRYPT);
BCryptPassword password = (BCryptPassword) factory.translate(ModularCrypt.decode(cryptString));
// use the obtained spec to build a BCryptPasswordImpl, then verify the hash using the correct password.
Assert.assertTrue(factory.verify(password, correctPassword));
// check if an incorrect password gets rejected.
Assert.assertFalse(factory.verify(password, "wrongpassword".toCharArray()));
// now use the EncryptablePasswordSpec to build a new password and check if the hashed bytes matches those that
// were parsed and stored in the spec.
password = (BCryptPassword) factory.generatePassword(new EncryptablePasswordSpec(correctPassword,
new IteratedSaltedPasswordAlgorithmSpec(password.getIterationCount(), password.getSalt())));
Assert.assertArrayEquals(password.getHash(), password.getHash());
// use the new password to obtain a spec and then check if the spec yields the same crypt string.
Assert.assertEquals(cryptString, ModularCrypt.encodeAsString(password));
}
/**
* <p>
* Test the usage of a {@link org.wildfly.security.password.spec.ClearPasswordSpec} to hash a password. A random
* salt should be generated and the default iteration count should be used.
* </p>
*
* @throws Exception if an error occurs while running the test.
*/
@Test
public void testHashClearPassword() throws Exception {
PasswordFactory factory = PasswordFactory.getInstance(ALGORITHM_BCRYPT);
ClearPasswordSpec spec = new ClearPasswordSpec("password".toCharArray());
BCryptPassword password = (BCryptPassword) factory.generatePassword(spec);
// check if a salt has been generated and if the default iteration count is being used.
IteratedSaltedHashPasswordSpec bcryptSpec = factory.getKeySpec(password, IteratedSaltedHashPasswordSpec.class);
Assert.assertNotNull(bcryptSpec.getSalt());
Assert.assertEquals(BCRYPT_SALT_SIZE, bcryptSpec.getSalt().length);
Assert.assertEquals(DEFAULT_ITERATION_COUNT, bcryptSpec.getIterationCount());
// check if the correct password is verified while an incorrect password is rejected.
Assert.assertTrue(factory.verify(password, "password".toCharArray()));
Assert.assertFalse(factory.verify(password, "wrongpassword".toCharArray()));
}
/**
* <p>
* For this test the crypt string was generated using the Python PassLib bcrypt implementation.
* </p>
*
* @throws Exception if an error occurs while running the test.
*/
@Test
public void testHashAgainstPassLib() throws Exception {
String cryptString = "$2a$12$NT0I31Sa7ihGEWpka9ASYeEFkhuTNeBQ2xfZskIiiJeyFXhRgS.Sy";
char[] correctPassword = "password".toCharArray();
PasswordFactory factory = PasswordFactory.getInstance(ALGORITHM_BCRYPT);
BCryptPassword password = (BCryptPassword) factory.translate(ModularCrypt.decode(cryptString));
// use the obtained spec to build a BCryptPasswordImpl, then verify the hash using the correct password.
Assert.assertTrue(factory.verify(password, correctPassword));
// check if an incorrect password gets rejected.
Assert.assertFalse(factory.verify(password, "wrongpassword".toCharArray()));
// now use the EncryptablePasswordSpec to build a new password and check if the hashed bytes matches those that
// were parsed and stored in the spec.
password = (BCryptPassword) factory.generatePassword(new EncryptablePasswordSpec(correctPassword,
new IteratedSaltedPasswordAlgorithmSpec(password.getIterationCount(), password.getSalt())));
Assert.assertArrayEquals(password.getHash(), password.getHash());
// use the new password to obtain a spec and then check if the spec yields the same crypt string.
Assert.assertEquals(cryptString, ModularCrypt.encodeAsString(password));
}
@Test
public void testLongKeys() throws Exception {
byte[] salt = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10};
PasswordFactory factory = PasswordFactory.getInstance(ALGORITHM_BCRYPT);
// hash a password that is too long (size > 72 bytes).
String longKey = "01234567890123456789012345678901234567890123456789012345678901234567890123456789";
BCryptPassword password = (BCryptPassword) factory.generatePassword(
new EncryptablePasswordSpec(longKey.toCharArray(), new IteratedSaltedPasswordAlgorithmSpec(6, salt)));
// another long password that shares the first 72 bytes with the original password should yield the same hash.
String longKeyAlt = "012345678901234567890123456789012345678901234567890123456789012345678901xxxxxxxxyyyyzzzzzz";
Assert.assertTrue(factory.verify(password, longKeyAlt.toCharArray()));
}
}