/* * 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 org.junit.Test; import org.wildfly.security.password.Password; import org.wildfly.security.password.interfaces.UnixSHACryptPassword; import org.wildfly.security.password.spec.EncryptablePasswordSpec; import org.wildfly.security.password.util.ModularCrypt; import java.io.UnsupportedEncodingException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.wildfly.security.password.interfaces.UnixSHACryptPassword.ALGORITHM_CRYPT_SHA_256; import static org.wildfly.security.password.interfaces.UnixSHACryptPassword.ALGORITHM_CRYPT_SHA_512; import org.wildfly.security.password.spec.IteratedSaltedPasswordAlgorithmSpec; /** * @author <a href="mailto:jpkroehling.javadoc@redhat.com">Juraci Paixão Kröhling</a> */ @SuppressWarnings("SpellCheckingInspection") public class UnixSHACryptPasswordUtilTest { @Test public void shouldParseSpecWithoutRounds() throws NoSuchAlgorithmException, InvalidKeySpecException { assertEquals("Didn't parse the number of rounds correctly", 5_000, ((UnixSHACryptPassword) ModularCrypt.decode("$6$toolongsaltstrin$0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")).getIterationCount()); } @Test public void shouldParseSpecWithRounds() throws NoSuchAlgorithmException, InvalidKeySpecException { assertEquals("Didn't parse the number of rounds correctly", 10_000, ((UnixSHACryptPassword) ModularCrypt.decode("$6$rounds=10000$saltstring$0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")).getIterationCount()); } @Test public void shouldTruncateSaltAt16Chars() throws NoSuchAlgorithmException, InvalidKeySpecException { assertEquals("Didn't parse the number of rounds correctly", 5_000, ((UnixSHACryptPassword) ModularCrypt.decode("$6$rounds=5000$toolongsaltstrin$0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")).getIterationCount()); } @Test public void shouldIncreaseIterationCountIfLowerThan1000() throws NoSuchAlgorithmException, InvalidKeySpecException { assertEquals("Didn't increase the number of rounds", 1_000, ((UnixSHACryptPassword) ModularCrypt.decode("$6$rounds=10$roundstoobig$0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")).getIterationCount()); } @Test public void shouldDecreaseIterationCountIfBiggerThan999999999() throws NoSuchAlgorithmException, InvalidKeySpecException { // this test is being kept as a way to mark that this behavior is intended, but it's not tested with the // usual tests because it would run the hashing with 999,999,999 iterations assertEquals("Didn't decrease the number of rounds", 999_999_999, ((UnixSHACryptPassword) ModularCrypt.decode("$6$rounds=1000000000$roundstoobig$0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")).getIterationCount()); } @Test public void shouldVerifyOnMatchingHashes() throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException { String cryptString = "$5$rounds=10000$saltstringsaltst$3xv.VbSHBb41AL9AvLeujZkZRBAwqFMz2.opqey6IcA"; final PasswordFactorySpiImpl factorySpi = new PasswordFactorySpiImpl(); final Password parsed = ModularCrypt.decode(cryptString); assertEquals(cryptString, ModularCrypt.encodeAsString(parsed)); UnixSHACryptPassword password = (UnixSHACryptPassword) parsed; final String algorithm = password.getAlgorithm(); UnixSHACryptPassword comparePassword = (UnixSHACryptPassword) factorySpi.engineGeneratePassword(algorithm, new EncryptablePasswordSpec("Hello world!".toCharArray(), new IteratedSaltedPasswordAlgorithmSpec(10000, password.getSalt()))); assertEquals(cryptString, ModularCrypt.encodeAsString(comparePassword)); assertEquals(password.getIterationCount(), comparePassword.getIterationCount()); assertArrayEquals(password.getSalt(), comparePassword.getSalt()); assertArrayEquals(password.getHash(), comparePassword.getHash()); // password is in raw form, need to translate first before verifying password = (UnixSHACryptPassword) factorySpi.engineTranslatePassword(algorithm, password); assertTrue(factorySpi.engineVerify(algorithm, password, "Hello world!".toCharArray())); } @Test public void shouldNotVerifyOnNonMatchingHashes() { } private String generate(String alg, String salt, String passwd, int iterationCount) throws InvalidKeySpecException { final PasswordFactorySpiImpl spi = new PasswordFactorySpiImpl(); final Password password = spi.engineGeneratePassword(alg, new EncryptablePasswordSpec(passwd.toCharArray(), new IteratedSaltedPasswordAlgorithmSpec(iterationCount, salt.getBytes(UTF_8)))); return ModularCrypt.encodeAsString(password); } @Test public void shouldPassAllCasesFromSpecForSha256() throws NoSuchAlgorithmException, InvalidKeySpecException { assertEquals( "$5$saltstring$5B8vYYiY.CVt1RlTTf8KbXBH3hsxY/GNooZaBBGWEc5", generate(ALGORITHM_CRYPT_SHA_256, "saltstring", "Hello world!", 5_000) ); assertEquals( "$5$rounds=10000$saltstringsaltst$3xv.VbSHBb41AL9AvLeujZkZRBAwqFMz2.opqey6IcA", generate(ALGORITHM_CRYPT_SHA_256, "saltstringsaltstring", "Hello world!", 10_000) ); assertEquals( "$5$toolongsaltstrin$Un/5jzAHMgOGZ5.mWJpuVolil07guHPvOW8mGRcvxa5", generate(ALGORITHM_CRYPT_SHA_256, "toolongsaltstring", "This is just a test", 5_000) ); assertEquals( "$5$rounds=1400$anotherlongsalts$Rx.j8H.h8HjEDGomFU8bDkXm3XIUnzyxf12oP84Bnq1", generate(ALGORITHM_CRYPT_SHA_256, "anotherlongsaltstring", "a very much longer text to encrypt. This one even stretches over morethan one line.", 1_400) ); assertEquals( "$5$rounds=77777$short$JiO1O3ZpDAxGJeaDIuqCoEFysAe1mZNJRs3pw0KQRd/", generate(ALGORITHM_CRYPT_SHA_256, "short", "we have a short salt string but not a short password", 77_777) ); assertEquals( "$5$rounds=123456$asaltof16chars..$gP3VQ/6X7UUEW3HkBn2w1/Ptq2jxPyzV/cZKmF/wJvD", generate(ALGORITHM_CRYPT_SHA_256, "asaltof16chars..", "a short string", 123_456) ); assertEquals( "$5$rounds=1000$roundstoolow$yfvwcWrQ8l/K0DAWyuPMDNHpIVlTQebY9l/gL972bIC", generate(ALGORITHM_CRYPT_SHA_256, "roundstoolow", "the minimum number is still observed", 10) ); } @Test public void shouldPassAllCasesFromSpecForSha512() throws NoSuchAlgorithmException, InvalidKeySpecException { assertEquals( "$6$saltstring$svn8UoSVapNtMuq1ukKS4tPQd8iKwSMHWjl/O817G3uBnIFNjnQJuesI68u4OTLiBFdcbYEdFCoEOfaS35inz1", generate(ALGORITHM_CRYPT_SHA_512, "saltstring", "Hello world!", 5_000) ); assertEquals( "$6$rounds=10000$saltstringsaltst$OW1/O6BYHV6BcXZu8QVeXbDWra3Oeqh0sbHbbMCVNSnCM/UrjmM0Dp8vOuZeHBy/YTBmSK6H9qs/y3RnOaw5v.", generate(ALGORITHM_CRYPT_SHA_512, "saltstringsaltstring", "Hello world!", 10_000) ); assertEquals( "$6$toolongsaltstrin$lQ8jolhgVRVhY4b5pZKaysCLi0QBxGoNeKQzQ3glMhwllF7oGDZxUhx1yxdYcz/e1JSbq3y6JMxxl8audkUEm0", generate(ALGORITHM_CRYPT_SHA_512, "toolongsaltstring", "This is just a test", 5_000) ); assertEquals( "$6$rounds=1400$anotherlongsalts$POfYwTEok97VWcjxIiSOjiykti.o/pQs.wPvMxQ6Fm7I6IoYN3CmLs66x9t0oSwbtEW7o7UmJEiDwGqd8p4ur1", generate(ALGORITHM_CRYPT_SHA_512, "anotherlongsaltstring", "a very much longer text to encrypt. This one even stretches over morethan one line.", 1_400) ); assertEquals( "$6$rounds=77777$short$WuQyW2YR.hBNpjjRhpYD/ifIw05xdfeEyQoMxIXbkvr0gge1a1x3yRULJ5CCaUeOxFmtlcGZelFl5CxtgfiAc0", generate(ALGORITHM_CRYPT_SHA_512, "short", "we have a short salt string but not a short password", 77_777) ); assertEquals( "$6$rounds=123456$asaltof16chars..$BtCwjqMJGx5hrJhZywWvt0RLE8uZ4oPwcelCjmw2kSYu.Ec6ycULevoBK25fs2xXgMNrCzIMVcgEJAstJeonj1", generate(ALGORITHM_CRYPT_SHA_512, "asaltof16chars..", "a short string", 123_456) ); assertEquals( "$6$rounds=1000$roundstoolow$kUMsbe306n21p9R.FRkW3IGn.S9NPN0x50YhH1xhLsPuWGsUSklZt58jaTfF4ZEQpyUNGc0dqbpBYYBaHHrsX.", generate(ALGORITHM_CRYPT_SHA_512, "roundstoolow", "the minimum number is still observed", 10) ); } }